1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Report</title> 6 <style> 7 * { 8 box-sizing: border-box; 9 } 10 11 html, 12 body { 13 height: 100%; 14 margin: 0; 15 padding: 0; 16 } 17 18 lit-tabs { 19 padding-top: 10px; 20 } 21 22 lit-tabpane { 23 padding: 20px; 24 } 25 </style> 26</head> 27<body> 28<script type="module"> 29 window.getShadowRoot = (el)=>{ 30 if (el.parentNode) { 31 return window.getShadowRoot(el.parentNode); 32 } else { 33 return el; 34 } 35 }; 36 window.getShadowElement = (el)=>{ 37 if (el.parentElement) { 38 return window.getShadowElement(el.parentElement); 39 } else { 40 return el; 41 } 42 }; 43 class LitIcon extends HTMLElement { 44 static get observedAttributes() { 45 return ['name', 'size', 'color', 'path']; 46 } 47 48 constructor() { 49 super(); 50 const shadowRoot = this.attachShadow({mode: 'open'}); 51 shadowRoot.innerHTML = ` 52 <style> 53 :host{ 54 font-size: inherit; 55 display: inline-block; 56 transition: .3s; 57 } 58 :host([spin]) { 59 animation: rotate 1.75s linear infinite; 60 } 61 @keyframes rotate { 62 to{ 63 transform: rotate(360deg); 64 } 65 } 66 .icon{ 67 display: block; 68 width: 1em; 69 height: 1em; 70 margin: auto; 71 fill: currentColor; 72 overflow: hidden; 73 } 74 </style> 75 <svg style="display: none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 76 <symbol id="icon-ellipsis" viewBox="0 0 1024 1024"><path d="M232 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M512 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M792 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path></symbol> 77 <symbol id="icon-doubleleft" viewBox="0 0 1024 1024"><path d="M272.9 512l265.4-339.1c4.1-5.2 0.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L186.8 492.3c-9.1 11.6-9.1 27.9 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H532c6.7 0 10.4-7.7 6.3-12.9L272.9 512z"></path><path d="M576.9 512l265.4-339.1c4.1-5.2 0.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L490.8 492.3c-9.1 11.6-9.1 27.9 0 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H836c6.7 0 10.4-7.7 6.3-12.9L576.9 512z"></path></symbol> 78 <symbol id="icon-doubleright" viewBox="0 0 1024 1024"><path d="M533.2 492.3L277.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H188c-6.7 0-10.4 7.7-6.3 12.9L447.1 512 181.7 851.1c-4.1 5.2-0.4 12.9 6.3 12.9h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z"></path><path d="M837.2 492.3L581.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H492c-6.7 0-10.4 7.7-6.3 12.9L751.1 512 485.7 851.1c-4.1 5.2-0.4 12.9 6.3 12.9h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z"></path></symbol> 79 <symbol id="icon-close-circle-fill" viewBox="0 0 1024 1024"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m165.4 618.2l-66-0.3L512 563.4l-99.3 118.4-66.1 0.3c-4.4 0-8-3.5-8-8 0-1.9 0.7-3.7 1.9-5.2l130.1-155L340.5 359c-1.2-1.5-1.9-3.3-1.9-5.2 0-4.4 3.6-8 8-8l66.1 0.3L512 464.6l99.3-118.4 66-0.3c4.4 0 8 3.5 8 8 0 1.9-0.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></symbol> 80<!-- <symbol id="icon-left" viewBox="0 0 1024 1024"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8c-16.4 12.8-16.4 37.5 0 50.3l450.8 352.1c5.3 4.1 12.9 0.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path></symbol>--> 81<!-- <symbol id="icon-right" viewBox="0 0 1024 1024"><path d="M765.7 486.8L314.9 134.7c-5.3-4.1-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-37.6 0-50.4z"></path></symbol>--> 82 </svg> 83 <svg class='icon' id='icon' aria-hidden="true" viewBox="0 0 ${this.view} ${this.view}"> 84 ${this.path ? '<path id="path"></path>' : '<use id="use"></use>'} 85 </svg> 86 `; 87 } 88 89 get view() { 90 return this.getAttribute('view') || 1024; 91 } 92 93 get name() { 94 return this.getAttribute('name'); 95 } 96 97 get path() { 98 return this.getAttribute('path'); 99 } 100 101 set name(value) { 102 this.setAttribute('name', value); 103 } 104 105 set path(value) { 106 this.setAttribute('path', value); 107 } 108 109 get size() { 110 return this.getAttribute('size') || ''; 111 } 112 113 get color() { 114 return this.getAttribute('color') || ''; 115 } 116 117 set size(value) { 118 this.setAttribute('size', value); 119 } 120 121 set color(value) { 122 this.setAttribute('color', value); 123 } 124 125 get spin() { 126 return this.hasAttribute('spin'); 127 } 128 129 set spin(value) { 130 if (value) { 131 this.setAttribute('spin',''); 132 } else { 133 this.removeAttribute('spin'); 134 } 135 } 136 137 // 当 custom element首次被插入文档DOM时,被调用。 138 connectedCallback() { 139 this.icon = this.shadowRoot.getElementById('icon'); 140 this.use = this.shadowRoot.querySelector('use'); 141 this.d = this.shadowRoot.querySelector('path'); 142 this.size && (this.size = this.size); 143 this.color && (this.color = this.color); 144 this.name && (this.name = this.name); 145 this.path && (this.path = this.path); 146 } 147 148 // 当 custom element从文档DOM中删除时,被调用。 149 disconnectedCallback() { 150 151 } 152 153 // 当 custom element被移动到新的文档时,被调用。 154 adoptedCallback() { 155 console.log('Custom square element moved to new page.'); 156 } 157 158 // 当 custom element增加、删除、修改自身属性时,被调用。 159 attributeChangedCallback(name, oldValue, newValue) { 160 if (name === 'name' && this.use) { 161 this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#icon-${newValue}`); 162 } 163 if (name === 'path' && this.d) { 164 this.d.setAttribute('d', newValue); 165 } 166 if (name === 'color' && this.icon) { 167 this.icon.style.color = newValue; 168 } 169 if (name === 'size' && this.icon) { 170 this.icon.style.fontSize = newValue + 'px'; 171 } 172 } 173 } 174 if (!customElements.get('lit-icon')) { 175 customElements.define('lit-icon', LitIcon); 176 } 177 178 class LitInput extends HTMLElement { 179 static get observedAttributes() { 180 return [ 181 'placeholder',//显示提示文字 182 'block',//属性将使按钮适合其父宽度。 183 'icon',//图标,如果配置了 slot='prefix' icon将失效 184 'bordered',//是否有边框 185 'allow-clear',//是否有清除 186 'rows',//表示行数,默认input 设置rows 后 显示为textarea 187 'maxlength',//允许输入多少个字符 默认没有限制 188 'type',//password number text 189 'pattern',//正则表达式 190 'error-text',//错误提示文字 191 'error-placement',//错误提示文字方向 192 'required',//是否开始验证 193 'value'//值 194 ]; 195 } 196 get value() { 197 return this.getAttribute('value')||''; 198 } 199 200 set value(value) { 201 this.setAttribute('value',value); 202 } 203 get required() { 204 return this.hasAttribute('required'); 205 } 206 207 set required(value) { 208 if (value) { 209 this.setAttribute('required', ''); 210 } else { 211 this.removeAttribute('required'); 212 } 213 } 214 get pattern() { 215 return this.getAttribute('pattern')||''; 216 } 217 218 set pattern(value) { 219 this.setAttribute('pattern', value); 220 } 221 get errorText() { 222 return this.getAttribute('error-text')||'该项为必填项'; 223 } 224 set errorText(value) { 225 this.setAttribute('error-text',value); 226 } 227 get errorPlacement() { 228 return this.getAttribute('error-placement')||'top'; 229 } 230 set errorPlacement(value) { 231 this.setAttribute('error-placement',value); 232 } 233 get type() { 234 return this.getAttribute('type'); 235 } 236 set type(value) { 237 this.setAttribute('type', value); 238 } 239 get maxlength() { 240 return this.getAttribute('maxlength')||''; 241 } 242 243 set maxlength(value) { 244 this.setAttribute('maxlength',value) 245 } 246 get rows() { 247 return this.getAttribute('rows'); 248 } 249 set rows(value) { 250 this.setAttribute('rows',value); 251 } 252 get allowClear() { 253 return this.hasAttribute('allow-clear'); 254 } 255 256 set allowClear(value) { 257 if (value) { 258 this.setAttribute('allow-clear', ''); 259 } else { 260 this.removeAttribute('allow-clear'); 261 } 262 } 263 264 get bordered() { 265 return this.getAttribute('bordered') || 'true'; 266 } 267 268 set bordered(value) { 269 if (value) { 270 this.setAttribute('bordered', 'true'); 271 } else { 272 this.setAttribute('bordered', 'false'); 273 } 274 } 275 276 get icon() { 277 return this.getAttribute('icon') || null; 278 } 279 280 set icon(value) { 281 this.setAttribute('icon', value); 282 } 283 284 get block() { 285 return this.hasAttribute('block'); 286 } 287 288 set block(value) { 289 if (value) { 290 this.setAttribute('block', ''); 291 } else { 292 this.removeAttribute('block'); 293 } 294 } 295 296 get placeholder() { 297 return this.getAttribute('placeholder') || ''; 298 } 299 300 set placeholder(value) { 301 this.setAttribute('placeholder', value); 302 } 303 304 constructor() { 305 super(); 306 const shadowRoot = this.attachShadow({mode: 'open'}); 307 shadowRoot.innerHTML = ` 308 <style> 309:host{ 310 ${this.bordered === 'true' ? 'border: 1px solid #e9e9e9;' : ''} 311 display: inline-flex; 312 box-sizing: border-box; 313 position:relative; 314 align-items: ${this.rows?'flex-start':'center'}; 315 transition: all .3s; 316 border-radius: 2px; 317 font-size: 14px; 318 color: #333; 319 ${this.block ? 'display: flex;' : ''} 320 box-sizing: border-box; 321 } 322 :host(:hover) { 323 ${this.bordered === 'true' ? 'border: 1px solid #65b687;' : ''} 324 ${this.bordered === 'true' ? 'box-shadow: 0 0 10px #65b68766;' : ''} 325 } 326 *{ 327 box-sizing: border-box; 328 } 329 .input{ 330 outline: none; 331 border: 0px; 332 font-size: inherit; 333 color: inherit; 334 width: 100%; 335 height: 100%; 336 vertical-align:middle; 337 line-height:inherit; 338 height:inherit; 339 padding: 6px 6px 6px ${this.icon ? '6px' : '11px'}; 340 max-height: inherit; 341 box-sizing: border-box; 342 } 343 /*去掉type=number 后面的增减箭头*/ 344input::-webkit-outer-spin-button, 345input::-webkit-inner-spin-button { 346 -webkit-appearance: none; 347 348} 349 350input[type='number'] { 351 -moz-appearance: textfield; 352} 353 354 lit-tips{ 355 flex: 1; 356 height: 100%; 357 width: 100%; 358 } 359 lit-tips{ 360 flex: 1; 361 align-items: center; 362 } 363:host(:not([allow-clear])) .clear-btn{ 364 display: flex; 365 visibility: hidden; 366 transition: all .3s; 367 opacity: 0; 368} 369:host([allow-clear]) .clear-btn{ 370 opacity: 0; 371 position:absolute; 372 right: 0; 373 top: .5rem; 374 visibility: hidden; 375 transition: all .3s; 376 display: flex; 377 margin-right: 6px; 378 transform: translateY(0%); 379 color: #bfbfbf; 380} 381:host([allow-clear]) .clear-btn:hover{ 382 color: #8c8c8c; 383} 384 385.area{ 386 outline: none; 387 border: 0px; 388 width: 100%; 389 font-size: inherit; 390 vertical-align:middle; 391 min-height: calc(2rem); 392 max-height: calc(${this.rows} * 1rem); 393 padding: 6px 6px 6px ${this.icon ? '6px' : '11px'}; 394 box-sizing: border-box; 395 resize:vertical; 396} 397 398:host(:not([type=password])) .pwd-btn, 399:host(:not([type=tel])) .pwd-btn{ 400 display: none; 401 visibility: hidden; 402 transition: all .3s; 403 opacity: 0; 404} 405:host([type=password]) .pwd-btn, 406:host([type=tel]) .pwd-btn{ 407 opacity: 1; 408 position:absolute; 409 right: 0; 410 top: .5rem; 411 visibility: visible; 412 transition: all .3s; 413 display: flex; 414 margin-right: 6px; 415 transform: translateY(0%); 416 color: #bfbfbf; 417} 418/*:host([type=password]) input{*/ 419/* -webkit-text-security:none;*/ 420/* text-security:none;*/ 421/*}*/ 422:host([type=password]) .pwd-btn:hover, 423:host([type=tel]) .pwd-btn:hover{ 424 color: #8c8c8c; 425} 426 427</style> 428<slot name="prefix">${this.icon ? `<lit-icon name="${this.icon}" style="margin-left: 11px;color: inherit;font-size: inherit;${this.rows ? 'transform: translateY(50%);' : ''}"></lit-icon>` : ``}</slot> 429<lit-tips tips="${this.errorText}" placement="${this.errorPlacement}" color="#f4615c" offset="12px" show="false"> 430 ${this.hasAttribute('rows')? 431 `<textarea class="area" rows="${this.rows}" value="${this.value}" placeholder="${this.placeholder}" cols="27" maxlength="${this.maxlength}" onscroll="this.rows++;"></textarea>` 432 : 433 `<input type="${this.type}" value="${this.value}" class="input" placeholder="${this.placeholder}" >`} 434 435 <lit-icon class="clear-btn" name="close-circle-fill"></lit-icon> 436 <lit-icon class="pwd-btn" name="eye-fill"></lit-icon> 437</lit-tips> 438<slot name="suffix" ></slot> 439 `; 440 } 441 442 // 当 custom element首次被插入文档DOM时,被调用。 443 connectedCallback() { 444 this.reg = new RegExp(this.pattern); 445 this.inputElement = this.shadowRoot.querySelector('.input'); 446 this.areaElement = this.shadowRoot.querySelector('.area'); 447 this.clearElement = this.shadowRoot.querySelector('.clear-btn'); 448 this.pwdElement = this.shadowRoot.querySelector('.pwd-btn'); 449 450 this.pwdElement.onclick=(e)=>{ 451 if (this.inputElement.getAttribute('type')==='password') { 452 this.pwdElement.name = 'eyeclose-fill'; 453 this.inputElement.setAttribute('type','tel'); 454 } else if (this.inputElement.getAttribute('type')==='tel') { 455 this.inputElement.setAttribute('type','password'); 456 this.pwdElement.name = 'eye-fill'; 457 } 458 } 459 460 if (this.areaElement) { 461 this.clearElement.onclick=e=>{ 462 this.areaElement.value = ''; 463 this.clearElement.style.visibility = 'hidden'; 464 this.clearElement.style.opacity = '0'; 465 this.value=this.areaElement.value; 466 this.dispatchEvent(new CustomEvent('onClear',e)); 467 this.clearError(); 468 }; 469 this.areaElement.oninput=e=>{ 470 if (this.allowClear) { 471 if (this.areaElement.value.length>0) { 472 this.clearElement.style.visibility = 'visible'; 473 this.clearElement.style.opacity = '1'; 474 } else { 475 this.clearElement.style.visibility = 'hidden'; 476 this.clearElement.style.opacity = '0'; 477 } 478 } 479 this.value=this.areaElement.value; 480 this.verify(); 481 this.dispatchEvent(new CustomEvent('input',e)); 482 }; 483 this.areaElement.onchange=e=>{ 484 this.value=this.areaElement.value; 485 this.verify(); 486 this.dispatchEvent(new CustomEvent('change',e)); 487 }; 488 this.areaElement.onfocus=e=>{ 489 this.value=this.areaElement.value; 490 this.verify(); 491 this.dispatchEvent(new CustomEvent('focus',e)); 492 }; 493 this.areaElement.onblur=e=>{ 494 this.value=this.areaElement.value; 495 this.dispatchEvent(new CustomEvent('blur',e)); 496 }; 497 this.areaElement.onkeydown=e=>{ 498 if (e.key==='Enter') { 499 this.value=this.areaElement.value; 500 this.verify(); 501 this.dispatchEvent(new CustomEvent('onPressEnter',e)); 502 } 503 this.dispatchEvent(new CustomEvent('keydown',e)); 504 }; 505 this.areaElement.onkeyup=e=>{ 506 this.dispatchEvent(new CustomEvent('keyup',e)); 507 }; 508 this.areaElement.onselect=e=>{ 509 this.dispatchEvent(new CustomEvent('select',e)); 510 }; 511 this.areaElement.onclick=e=>{ 512 this.dispatchEvent(new CustomEvent('click',e)); 513 }; 514 } 515 if (this.inputElement) { 516 this.clearElement.onclick=e=>{ 517 this.inputElement.value = ''; 518 this.clearElement.style.visibility = 'hidden'; 519 this.clearElement.style.opacity = '0'; 520 this.value=this.inputElement.value; 521 this.dispatchEvent(new CustomEvent('onClear',e)); 522 this.clearError(); 523 }; 524 this.inputElement.oninput=e=>{ 525 if (this.allowClear) { 526 if (this.inputElement.value.length>0) { 527 this.clearElement.style.visibility = 'visible'; 528 this.clearElement.style.opacity = '1'; 529 } else { 530 this.clearElement.style.visibility = 'hidden'; 531 this.clearElement.style.opacity = '0'; 532 } 533 } 534 this.value=this.inputElement.value; 535 this.verify(); 536 this.dispatchEvent(new CustomEvent('input',e)); 537 }; 538 this.inputElement.onchange=e=>{ 539 this.value=this.inputElement.value; 540 this.verify(); 541 this.dispatchEvent(new CustomEvent('change',e)); 542 }; 543 this.inputElement.onfocus=e=>{ 544 this.value=this.inputElement.value; 545 this.verify(); 546 this.dispatchEvent(new CustomEvent('focus',e)); 547 }; 548 this.inputElement.onblur=e=>{ 549 this.dispatchEvent(new CustomEvent('blur',e)); 550 }; 551 this.inputElement.onkeydown=e=>{ 552 if (e.key==='Enter') { 553 this.value=this.inputElement.value; 554 this.verify(); 555 this.dispatchEvent(new CustomEvent('onPressEnter',e)); 556 } 557 this.dispatchEvent(new CustomEvent('keydown',e)); 558 }; 559 this.inputElement.onkeyup=e=>{ 560 this.dispatchEvent(new CustomEvent('keyup',e)); 561 }; 562 this.inputElement.onselect=e=>{ 563 this.dispatchEvent(new CustomEvent('select',e)); 564 }; 565 this.inputElement.onclick=e=>{ 566 this.dispatchEvent(new CustomEvent('click',e)); 567 }; 568 } 569 570 } 571 572 // 当 custom element从文档DOM中删除时,被调用。 573 disconnectedCallback() { 574 575 } 576 577 // 当 custom element被移动到新的文档时,被调用。 578 adoptedCallback() { 579 console.log('Custom square element moved to new page.'); 580 } 581 582 // 当 custom element增加、删除、修改自身属性时,被调用。 583 attributeChangedCallback(name, oldValue, newValue) { 584 if (name === 'value') { 585 if (this.inputElement) { 586 this.inputElement.value=newValue; 587 } else if (this.areaElement) { 588 this.areaElement.value=newValue; 589 } 590 } 591 } 592 verify() { 593 if (this.required) { 594 let result; 595 if (this.hasAttribute('rows')) { 596 result= this.reg.test(this.areaElement.value); 597 } else { 598 result= this.reg.test(this.inputElement.value); 599 } 600 if (result) { 601 this.shadowRoot.querySelector('lit-tips').show='false'; 602 } else { 603 this.shadowRoot.querySelector('lit-tips').show='true'; 604 } 605 return result; 606 } else { 607 return true; 608 } 609 } 610 clearError() { 611 this.shadowRoot.querySelector('lit-tips').show='false'; 612 } 613 } 614 if (!customElements.get('lit-input')) { 615 customElements.define('lit-input', LitInput); 616 } 617 618 class LitLoading extends HTMLElement { 619 static get observedAttributes() { return ['color','size']; } 620 621 constructor() { 622 super(); 623 const shadowRoot = this.attachShadow({ mode: 'open' }); 624 shadowRoot.innerHTML = ` 625 <style> 626 :host{ 627 font-size:inherit; 628 display:inline-flex; 629 align-items: center; 630 justify-content:center; 631 color:var(--themeColor,#42b983); 632 } 633 .loading{ 634 display: block; 635 width: 1em; 636 height: 1em; 637 margin: auto; 638 animation: rotate 1.4s linear infinite; 639 } 640 .circle { 641 stroke: currentColor; 642 animation: progress 1.4s ease-in-out infinite; 643 stroke-dasharray: 80px, 200px; 644 stroke-dashoffset: 0px; 645 transition:.3s; 646 } 647 :host(:not(:empty)) .loading{ 648 margin:.5em; 649 } 650 @keyframes rotate{ 651 to{ 652 transform: rotate(360deg); 653 } 654 } 655 @keyframes progress { 656 0% { 657 stroke-dasharray: 1px, 200px; 658 stroke-dashoffset: 0px; 659 } 660 50% { 661 stroke-dasharray: 100px, 200px; 662 stroke-dashoffset: -15px; 663 } 664 100% { 665 stroke-dasharray: 100px, 200px; 666 stroke-dashoffset: -125px; 667 } 668 } 669 </style> 670 <svg class="loading" id="loading" viewBox="22 22 44 44"><circle class="circle" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg> 671 <slot></slot> 672 `; 673 } 674 675 get size() { 676 return this.getAttribute('size')||''; 677 } 678 679 get color() { 680 return this.getAttribute('color')||''; 681 } 682 683 set size(value) { 684 this.setAttribute('size', value); 685 } 686 687 set color(value) { 688 this.setAttribute('color', value); 689 } 690 691 connectedCallback() { 692 this.loading = this.shadowRoot.getElementById('loading'); 693 this.size && (this.size = this.size); 694 this.color && (this.color = this.color); 695 } 696 697 attributeChangedCallback (name, oldValue, newValue) { 698 if ( name === 'color' && this.loading) { 699 this.loading.style.color = newValue; 700 } 701 if ( name === 'size' && this.loading) { 702 this.loading.style.fontSize = newValue + 'px'; 703 } 704 } 705 } 706 if (!customElements.get('lit-loading')) { 707 customElements.define('lit-loading', LitLoading); 708 } 709 710 class LitPagination extends HTMLElement { 711 static get observedAttributes() { 712 return [ 713 'current',// 当前页码 714 'total',// 总条数 715 'page-size',// 每页条数 716 'disabled',// 禁用分页 717 'show-size-changer',// 显示每页条目数select 718 'show-quick-jumper',// 显示跳至多少页 719 'page-size-options',// 指定每页可以显示多少条 默认 [10,20,50,100] 720 'simple'// 简洁模式 721 ]; 722 } 723 get pageSizeOptions() { 724 return this.getAttribute('page-size-options'); 725 } 726 set pageSizeOptions(value) { 727 this.setAttribute('page-size-options', ''); 728 } 729 730 get current() { 731 return this.getAttribute('current') || '1'; 732 } 733 734 set current(value) { 735 this.setAttribute('current', value); 736 } 737 738 get total() { 739 return this.getAttribute('total') || '50'; 740 } 741 742 set total(value) { 743 this.setAttribute('total', value); 744 } 745 746 get pageSize() { 747 return this.getAttribute('page-size') || '10'; 748 } 749 750 set pageSize(value) { 751 this.setAttribute('page-size', value); 752 } 753 754 get disabled() { 755 return this.hasAttribute('disabled'); 756 } 757 758 set disabled(value) { 759 if (value) { 760 this.setAttribute('disabled', ''); 761 } else { 762 this.removeAttribute('disabled'); 763 } 764 } 765 766 get showSizeChanger() { 767 return this.hasAttribute('show-size-changer'); 768 } 769 770 set showSizeChanger(value) { 771 if (value) { 772 this.setAttribute('show-size-changer', ''); 773 } else { 774 this.removeAttribute('show-size-changer'); 775 } 776 } 777 778 get showQuickJumper() { 779 return this.hasAttribute('show-quick-jumper') 780 } 781 782 set showQuickJumper(value) { 783 if (value) { 784 this.setAttribute('show-quick-jumper', ''); 785 } else { 786 this.removeAttribute('show-quick-jumper'); 787 } 788 } 789 790 get simple() { 791 return this.hasAttribute('simple'); 792 } 793 794 set simple(value) { 795 if (value) { 796 this.setAttribute('simple', ''); 797 } else { 798 this.removeAttribute('simple'); 799 } 800 } 801 802 constructor() { 803 super(); 804 const shadowRoot = this.attachShadow({mode: 'open'}); 805 shadowRoot.innerHTML = ` 806<style> 807:host{ 808 display: flex; 809 width: max-content; 810} 811.root{ 812 display: inline-flex; 813 /*grid-template-columns: repeat(auto-fill,35px);*/ 814 /*column-gap: 8px;*/ 815 /*row-gap: 8px;*/ 816 user-select: none; 817} 818.item{ 819 box-sizing: border-box; 820 color: #333; 821 transition: all .3s; 822 width: 35px; 823 height: 35px; 824 margin-left: 6px; 825 padding: 6px 10px; 826 border: 1px solid #ececec; 827 border-radius: 3px; 828 cursor: pointer; 829 display: inline-flex; 830 align-items: center; 831 justify-content: center; 832} 833.item-dir{ 834 color: #333; 835 transition: all .3s; 836 padding: 5px 10px; 837 /*border: 1px solid #ececec;*/ 838 /*border-radius: 3px;*/ 839 cursor: pointer; 840 display: inline-flex; 841 align-items: center; 842 justify-content: center; 843} 844.item:not([disable]):hover{ 845 color:#42b983; 846 border: 1px solid #42b983; 847} 848.item[selected]{ 849 color:#42b983; 850 border: 1px solid #42b983; 851} 852.item[disable]{ 853 /*background-color: #f5f5f5;*/ 854 fill: #c7c7c7; 855 color: #c7c7c7; 856 cursor: not-allowed; 857} 858 859:host([show-quick-jumper]) .jump-root{ 860 grid-column: span 4; 861 margin-left: 6px; 862 display: inline-flex; 863 align-items: center; 864} 865:host(:not([show-quick-jumper])) .jump-root{ 866 display: none; 867} 868 869:host([show-size-changer]) .pages-change{ 870 display: inline-flex; 871 grid-column: span 3; 872 width: 120px; 873 margin-left: 6px; 874 font-size: .9rem; 875} 876:host(:not([show-size-changer])) .pages-change{ 877 display: none; 878} 879</style> 880<div style="display: grid;grid-template-columns: max-content 1fr;"> 881 <div style="display: inline-flex;align-items: center" id="showTotal"> 882 <slot id="st" name="showTotal"></slot> 883 </div> 884 <div class="root"> 885<!-- <lit-icon class="item left-arrow" name="left" ></lit-icon>--> 886 <svg class="item left-arrow" viewBox="0 0 1024 1024" aria-hidden="true" > 887 <path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8c-16.4 12.8-16.4 37.5 0 50.3l450.8 352.1c5.3 4.1 12.9 0.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path> 888 </svg> 889 <div class="item first">1</div> 890 <lit-icon class="item-dir double-left" name="ellipsis" ></lit-icon> 891<!-- <svg class="item-dir double-left" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">--> 892<!-- <path d="M232 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M512 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M792 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path>--> 893<!-- </svg>--> 894 <div class="item one">2</div> 895 <div class="item two">3</div> 896 <div class="item three">4</div> 897 <div class="item four">5</div> 898 <div class="item five">6</div> 899 <lit-icon class="item-dir double-right" name="ellipsis" ></lit-icon> 900<!-- <svg class="item-dir double-right" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">--> 901<!-- <path d="M232 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M512 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path><path d="M792 511m-56 0a56 56 0 1 0 112 0 56 56 0 1 0-112 0Z"></path>--> 902<!-- </svg>--> 903 <div class="item last">1</div> 904<!-- <lit-icon class="item right-arrow" name="right"></lit-icon>--> 905 <svg class="item right-arrow" viewBox="0 0 1024 1024" aria-hidden="true" > 906 <path d="M765.7 486.8L314.9 134.7c-5.3-4.1-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-37.6 0-50.4z"></path> 907 </svg> 908 <lit-select class="pages-change" default-value="10"></lit-select> 909 <div class="jump-root"> 910 <label style="font-size: .9rem;color: #333;margin-right: 5px">跳至</label> 911 <lit-input type="number" class="jump" style="width: 80px;height: 100%"></lit-input> 912 <label style="font-size: .9rem;color: #333;margin-left: 5px">页</label> 913 </div> 914 </div> 915 916</div> 917 918 `; 919 } 920 921 // 当 custom element首次被插入文档DOM时,被调用。 922 connectedCallback() { 923 this.slotShowTotal=null; 924 this.rootElement = this.shadowRoot.querySelector('.root'); 925 let st = this.shadowRoot.querySelector('#st'); 926 st.addEventListener('slotchange',()=>{ 927 let elements = st.assignedElements(); 928 if (elements.length>0) { 929 this.slotShowTotal = elements[0]; 930 } 931 if (this.slotShowTotal) { 932 let cloneNode = this.slotShowTotal.render({ 933 total: this._total, 934 range: [1,this._pages], 935 }).content.cloneNode(true); 936 this.shadowRoot.querySelector('#showTotal').append(cloneNode); 937 } 938 }); 939 this.leftArrow = this.shadowRoot.querySelector('.left-arrow'); 940 this.first = this.shadowRoot.querySelector('.first'); 941 this.doubleLeft = this.shadowRoot.querySelector('.double-left'); 942 this.one = this.shadowRoot.querySelector('.one'); 943 this.two = this.shadowRoot.querySelector('.two'); 944 this.three = this.shadowRoot.querySelector('.three'); 945 this.four = this.shadowRoot.querySelector('.four'); 946 this.five = this.shadowRoot.querySelector('.five'); 947 this.doubleRight = this.shadowRoot.querySelector('.double-right'); 948 this.last = this.shadowRoot.querySelector('.last'); 949 this.rightArrow = this.shadowRoot.querySelector('.right-arrow'); 950 this.pagesChange = this.shadowRoot.querySelector('.pages-change'); 951 this.jump = this.shadowRoot.querySelector('.jump'); 952 this.nodes = [this.one, this.two, this.three, this.four, this.five]; 953 954 let jsonArray = JSON.parse(this.pageSizeOptions); 955 if (jsonArray) { 956 this.pagesChange.dataSource=jsonArray.map(a=>{ 957 return {key:a, val:`${a} 条/页`,}; 958 }); 959 this.pagesChange.value = jsonArray[0]+''; 960 this.pageSize = jsonArray[0]+''; 961 } else { 962 this.pagesChange.dataSource=[10,20,50,100].map(a=>{ 963 return {key:a, val:`${a} 条/页`,}; 964 }); 965 this.pagesChange.value ='10'; 966 this.pageSize = '10'; 967 } 968 969 this.jump.addEventListener('onPressEnter',e=>{ 970 if (e.target.value) { 971 this.current=e.target.value; 972 e.target.value = ''; 973 this.match(); 974 } 975 }); 976 this.pagesChange.onchange = e => { 977 this.pageSize = e.detail.value; 978 this.match(); 979 if (this.slotShowTotal) { 980 let cloneNode = this.slotShowTotal.render({ 981 total: this._total, 982 range: [1,this._pages], 983 }).content.cloneNode(true); 984 this.shadowRoot.querySelector('#showTotal').lastElementChild.remove(); 985 this.shadowRoot.querySelector('#showTotal').append(cloneNode); 986 } 987 this.dispatchEvent(new CustomEvent('onShowSizeChange',{ 988 detail:{ 989 current:parseInt(this.current), 990 size:parseInt(this.pageSize) 991 } 992 })); 993 }; 994 995 this.leftArrow.onclick = (e) => { 996 if (this._current > 1) { 997 this.current = `${this._current - 1}`; 998 this.match(); 999 } 1000 }; 1001 this.rightArrow.onclick = (e) => { 1002 if (this._current < this._pages) { 1003 this.current = `${this._current + 1}`; 1004 this.match(); 1005 } 1006 }; 1007 let nodeClickHandler = e => { 1008 this.current = `${e.currentTarget.val}`; 1009 this.match(); 1010 }; 1011 this.first.onclick = nodeClickHandler; 1012 this.nodes.forEach(a => a.onclick = nodeClickHandler); 1013 this.last.onclick = nodeClickHandler; 1014 this.doubleLeft.onmouseover = e => { 1015 e.target.name = 'doubleleft'; 1016 }; 1017 this.doubleLeft.onmouseout = e => { 1018 e.target.name = 'ellipsis' 1019 }; 1020 this.doubleLeft.onclick = e => { 1021 let k = this._current - 5; 1022 if (k < 1) k = 1; 1023 this.current = `${k}`; 1024 this.match(); 1025 }; 1026 this.doubleRight.onmouseover = e => { 1027 e.target.name = 'doubleright'; 1028 }; 1029 this.doubleRight.onmouseout = e => { 1030 e.target.name = 'ellipsis' 1031 }; 1032 this.doubleRight.onclick = e => { 1033 let k = this._current + 5; 1034 if (k > this._pages) k = this._pages; 1035 this.current = `${k}`; 1036 this.match(); 1037 }; 1038 this.match(); 1039 } 1040 1041 match() { 1042 this._current = parseInt(this.current); 1043 this._total = parseInt(this.total); 1044 this._pageSize = parseInt(this.pageSize); 1045 this._pages = Math.ceil(this._total / this._pageSize); 1046 if (this._current>this._pages) { 1047 this._current = this._pages; 1048 this.current = `${this._pages}`; 1049 } else if (this._current<1) { 1050 this._current = 1; 1051 this.current = `1`; 1052 } 1053 // 是否展示 pageSize 切换器,当 total 大于 50 时默认为 true 1054 // if (this._total>50) this.setAttribute('show-size-changer', ''); 1055 1056 // console.log(`${this._total} ${this._current}/${this._pages} ${this._pageSize}`); 1057 if (this._current === 1) { 1058 this.leftArrow.setAttribute('disable', ''); 1059 } else { 1060 this.leftArrow.removeAttribute('disable'); 1061 } 1062 if (this._current === this._pages) { 1063 this.rightArrow.setAttribute('disable', ''); 1064 } else { 1065 this.rightArrow.removeAttribute('disable'); 1066 } 1067 this.hiddenNode(this.first, this.last, this.one, this.two, this.three, this.four, this.five, 1068 this.doubleLeft, this.doubleRight); 1069 if (this._pages <= 5) { 1070 for (let i = 0; i < this._pages; i++) { 1071 this.displayNode(this.nodes[i]); 1072 this.item(this.nodes[i], i + 1); 1073 } 1074 } else if (this._pages === 6) { 1075 this.displayNode(this.first); 1076 this.item(this.first, 1); 1077 for (let i = 0; i < this._pages; i++) { 1078 this.displayNode(this.nodes[i]); 1079 this.item(this.nodes[i], i + 2); 1080 } 1081 } else { 1082 this.displayNode(this.one, this.two, this.three, this.four, this.five); 1083 if (this._current - 3 > 1 && this._current + 3 < this._pages) { // 左边溢出 右边溢出 1084 this.displayNode(this.first, this.last, this.doubleLeft, this.doubleRight); 1085 this.item(this.first, 1); 1086 this.item(this.last, this._pages); 1087 this.item(this.one, this._current - 2); 1088 this.item(this.two, this._current - 1); 1089 this.item(this.three, this._current); 1090 this.item(this.four, this._current + 1); 1091 this.item(this.five, this._current + 2); 1092 } else if (this._current - 3 === 1 && this._current + 3 < this._pages) {//左边刚好 右边溢出 1093 this.displayNode(this.first, this.last, this.doubleRight); 1094 this.item(this.first, 1); 1095 this.item(this.last, this._pages); 1096 for (let i = 0; i < this.nodes.length; i++) { 1097 this.item(this.nodes[i], i + 2); 1098 } 1099 } else if (this._current - 3 < 1 && this._current + 3 < this._pages) { //左边不够 右边溢出 1100 this.displayNode(this.last, this.doubleRight); 1101 this.item(this.last, this._pages); 1102 for (let i = 0; i < this.nodes.length; i++) { 1103 this.item(this.nodes[i], i + 1); 1104 } 1105 } else if (this._current - 3 > 1 && this._current + 3 === this._pages) {// 左边溢出 右边刚好 1106 this.displayNode(this.first, this.last, this.doubleLeft); 1107 this.item(this.first, 1); 1108 this.item(this.last, this._pages); 1109 this.item(this.nodes[0], this._pages - 5); 1110 this.item(this.nodes[1], this._pages - 4); 1111 this.item(this.nodes[2], this._pages - 3); 1112 this.item(this.nodes[3], this._pages - 2); 1113 this.item(this.nodes[4], this._pages - 1); 1114 } else if (this._current - 3 === 1 && this._current + 3 === this._pages) {// 左边刚好 右边刚好 1115 this.displayNode(this.first, this.last); 1116 this.item(this.first, 1); 1117 for (let i = 0; i < this._pages; i++) { 1118 this.displayNode(this.nodes[i]); 1119 this.item(this.nodes[i], i + 2); 1120 } 1121 this.item(this.last, 7); 1122 } else if (this._current - 3 < 1 && this._current + 3 === this._pages) {// 左边不够 右边刚好 1123 this.displayNode(this.last); 1124 this.item(this.last, this._pages); 1125 for (let i = 0; i < this.nodes.length; i++) { 1126 this.item(this.nodes[i], i + 1); 1127 } 1128 } else if (this._current - 3 > 1 && this._current + 3 > this._pages) { //左边溢出 右边不够 1129 this.displayNode(this.first, this.doubleLeft) 1130 this.item(this.first, 1); 1131 this.item(this.nodes[0], this._pages - 4); 1132 this.item(this.nodes[1], this._pages - 3); 1133 this.item(this.nodes[2], this._pages - 2); 1134 this.item(this.nodes[3], this._pages - 1); 1135 this.item(this.nodes[4], this._pages - 0); 1136 } else if (this._current - 3 === 1 && this._current + 3 > this._pages) { //左边刚好 右边不够 1137 this.displayNode(this.first); 1138 this.item(this.first, 1); 1139 this.item(this.nodes[0], this._pages - 4); 1140 this.item(this.nodes[1], this._pages - 3); 1141 this.item(this.nodes[2], this._pages - 2); 1142 this.item(this.nodes[3], this._pages - 1); 1143 this.item(this.nodes[4], this._pages - 0); 1144 } else if (this._current - 3 < 1 && this._current + 3 > this._pages) { //左边不够 右边不够 1145 //限定了 pages>6 这种情况不存在 1146 } 1147 } 1148 } 1149 1150 item(el, val) { 1151 el.val = val; 1152 el.textContent = val; 1153 if (val === this._current) { 1154 el.setAttribute('selected', ''); 1155 } else { 1156 el.removeAttribute('selected'); 1157 } 1158 } 1159 1160 displayNode() { 1161 [...arguments].forEach(a => a.style.display = 'flex'); 1162 } 1163 1164 hiddenNode(n) { 1165 [...arguments].forEach(a => a.style.display = 'none'); 1166 } 1167 1168 // 当 custom element从文档DOM中删除时,被调用。 1169 disconnectedCallback() { 1170 } 1171 1172 // 当 custom element被移动到新的文档时,被调用。 1173 adoptedCallback() { 1174 } 1175 1176 // 当 custom element增加、删除、修改自身属性时,被调用。 1177 attributeChangedCallback(name, oldValue, newValue) { 1178 if (name === 'current') { 1179 this.dispatchEvent(new CustomEvent('onChange', { 1180 detail: { 1181 page: parseInt(newValue), 1182 pageSize: this._pageSize 1183 } 1184 })); 1185 } else if (name === 'total') { 1186 this.dispatchEvent(new CustomEvent('onChange', { 1187 detail: { 1188 total: parseInt(newValue), 1189 current: this._current, 1190 pageSize: this._pageSize 1191 } 1192 })); 1193 this.match(); 1194 } 1195 1196 } 1197 } 1198 if (!customElements.get('lit-pagination')) { 1199 customElements.define('lit-pagination', LitPagination); 1200 } 1201 1202 class LitPieChart extends HTMLElement { 1203 static get observedAttributes() { return []; } 1204 1205 constructor() { 1206 super(); 1207 const shadowRoot = this.attachShadow({ mode: 'open' }); 1208 shadowRoot.innerHTML = ` 1209 <style> 1210 :host{ 1211 font-size:inherit; 1212 display:inline-flex; 1213 align-items: center; 1214 justify-content:center; 1215 color:#42b983; 1216 } 1217 </style> 1218 <div style="display: flex;flex-direction: row;width: 100%;padding: 15px"> 1219 <div style="display: flex;flex-direction: column;align-items: center;width: 40%"> 1220 <div id="chartTitle" style="width:auto;font-size: 20px;margin-bottom: 15px;text-overflow: ellipsis;white-space: nowrap"></div> 1221 <canvas id="chartView" width="200" height="200" ></canvas> 1222 </div> 1223 <div id="labelDiv" style="margin-top: 45px"> 1224 <div id="legendDiv"></div> 1225 <div id="pageOptionDiv" style="display: none;flex-direction: row;margin-top: 15px"> 1226 <div id="previous" style="cursor: pointer">previous</div> 1227 <div id="page" style="margin-left: 10px;margin-right: 10px">1/9</div> 1228 <div id="next" style="cursor: pointer">next</div> 1229 </div> 1230 </div> 1231 <div id="tip" style="display: none;position: absolute;z-index: 999;width: auto;height: auto;background-color: #fff; 1232 border-radius: 3px;border:1px solid #eee;box-shadow: 0px 0px 2px 2px #aaa;padding: 10px"> 1233 <div id="tipName" style="width: auto;font-weight: bold;margin-bottom: 10px"></div> 1234 <div id="tipValue" style="width: auto;font-weight: bold"></div> 1235 </div> 1236 </div> 1237 <slot></slot> 1238 `; 1239 } 1240 1241 get dataSource() { 1242 return this.ds || []; 1243 } 1244 1245 set dataSource(value) { 1246 this.ds = value; 1247 if (this.ds) { 1248 this.page = 0; 1249 let p = parseInt((this.ds.length / this.pageSize).toString()); 1250 this.totalPage = this.ds.length % this.pageSize > 0 ? (p + 1) : p; 1251 if (this.totalPage > 9) { 1252 this.totalPage = 9; 1253 } 1254 let tempAngel = -1 / 4 * 2 * Math.PI; // 饼图的起始角度 1255 this.ds.forEach(item=>{ 1256 item.startAngel = tempAngel; // 起始弧度 1257 item.angel = item.value * 2 * Math.PI; 1258 item.endAngel = (tempAngel + item.angel); //结束弧度 1259 if (item.endAngel > Math.PI * 3 / 2) { 1260 item.endAngel = Math.PI * 3 / 2; 1261 } 1262 tempAngel += item.angel; 1263 }); 1264 this.updateOptionStatus(); 1265 this.addChartLegend(); 1266 } 1267 this.renderChart(); 1268 } 1269 1270 get title() { 1271 return this.ct || ''; 1272 } 1273 1274 set title(value) { 1275 this.ct = value; 1276 if (value) { 1277 this.chartTitle.innerText = value.length > 50 ? value.slice(0,49)+'...' : value; 1278 } 1279 } 1280 1281 1282 connectedCallback() { 1283 this.pageSize = 14; 1284 this.page = 0; 1285 this.chartView = this.shadowRoot.getElementById('chartView'); 1286 this.chartView.width = window.devicePixelRatio * this.chartView.width; 1287 this.chartView.height = window.devicePixelRatio * this.chartView.height; 1288 this.chartTitle = this.shadowRoot.getElementById('chartTitle'); 1289 this.labelDiv = this.shadowRoot.getElementById('legendDiv'); 1290 this.tip = this.shadowRoot.getElementById('tip'); 1291 this.tipName = this.shadowRoot.getElementById('tipName'); 1292 this.tipValue = this.shadowRoot.getElementById('tipValue'); 1293 this.pageOptionDiv = this.shadowRoot.getElementById('pageOptionDiv'); 1294 this.previousBt = this.shadowRoot.getElementById('previous'); 1295 this.pageDiv = this.shadowRoot.getElementById('page'); 1296 this.nextBt = this.shadowRoot.getElementById('next'); 1297 this.previousBt.addEventListener('click',this.previousPage.bind(this)); 1298 this.nextBt.addEventListener('click',this.nextPage.bind(this)); 1299 this.chartView.addEventListener('mousemove',this.mouseMove.bind(this)); 1300 this.chartView.addEventListener('click',this.mouseClick.bind(this)); 1301 this.chartView.addEventListener('mouseout',this.mouseOut.bind(this)); 1302 } 1303 1304 mouseMove(event) { 1305 const mousePos = this.getMousePost(event); 1306 const result = this.containPoint(mousePos); 1307 if (result) { 1308 this.tip.style.left = (event.clientX + 40) + 'px'; 1309 this.tip.style.top = (event.clientY - 40) + 'px'; 1310 this.tip.style.display = 'flex'; 1311 this.tip.style.flexDirection = 'column'; 1312 this.tipName.innerText = result.name; 1313 this.tipValue.innerText = result.time + ' (' + (result.value * 100).toFixed(1) + '%)'; 1314 } else { 1315 this.tip.style.display = 'none'; 1316 if (this.selectItem !== null) { 1317 this.selectItem = undefined; 1318 this.renderChart(); 1319 } 1320 } 1321 } 1322 1323 mouseOut(event) { 1324 this.tip.style.display = 'none'; 1325 if (this.selectItem !== null) { 1326 this.selectItem = undefined; 1327 this.renderChart(); 1328 } 1329 } 1330 1331 mouseClick(event) { 1332 if (this.selectItem && this.hasOwnProperty('chartClickListener')) { 1333 this.chartClickListener(this.selectItem); 1334 } 1335 } 1336 1337 attributeChangedCallback (name, oldValue, newValue) { 1338 1339 } 1340 1341 // clientX,clientY 1342 // 判断鼠标在 canvas 的位置 1343 getMousePost(event) { 1344 // 获取鼠标的位置 1345 const { clientX, clientY } = event; 1346 // 获取 canvas 的边界位置 1347 const { top, left } = this.chartView.getBoundingClientRect(); 1348 // 计算鼠标在 canvas 在位置 1349 const x = (clientX - left); 1350 const y = (clientY - top); 1351 return { x, y }; 1352 } 1353 1354 // 判断是不是在 canvas上 1355 containPoint(mousePos) { 1356 let selector = undefined; 1357 let centerX = this.centerX; 1358 let centerY = this.centerY; 1359 const [subX,subY] = [centerX - mousePos.x,centerY - mousePos.y]; 1360 // 圆心到鼠标 的位置 1361 const len = Math.sqrt(subX * subX + subY * subY); 1362 // 判断是否在圆内 1363 const inChart = len < this.chartRadius; 1364 // 判断是不是在 startAngle 和 endAngle 1365 if (inChart) { 1366 if (this.ds) { 1367 let angle = Math.atan2(centerY - mousePos.y, centerX - mousePos.x) * (180 / Math.PI); 1368 let pointAngle = 0; 1369 if (angle >= 90) { 1370 pointAngle = -(180 - angle); 1371 } else if (angle >= 0 && angle < 90) { 1372 pointAngle = 270 + angle; 1373 } else if (angle < 0 && angle >= -90) { 1374 pointAngle = 270 + angle; 1375 } else { 1376 pointAngle = 90 + 180 + angle; 1377 } 1378 let pa = pointAngle / 180 * Math.PI; 1379 if (pa > 0) { 1380 pa = pa - Math.PI / 2; 1381 } 1382 for (let item of this.ds) { 1383 if (pa >= item.startAngel && pa < item.endAngel) { 1384 selector = item; 1385 if (item !== this.selectItem) { 1386 this.selectItem = item; 1387 this.renderChart(); 1388 } 1389 break; 1390 } 1391 } 1392 } 1393 } 1394 return selector; 1395 } 1396 1397 renderChart() { 1398 if (this.ds) { 1399 let context = this.chartView.getContext('2d'); 1400 // 清除画布 1401 context.clearRect(0,0,this.chartView.width,this.chartView.height); 1402 this.centerX = this.chartView.width / 2; 1403 this.centerY = this.chartView.height / 2; // 圆心坐标 1404 let labelX,labelY; // label 文字绘制位置 1405 let radius = this.chartView.height / 2 - 10; // 原型半径 1406 this.chartRadius = radius; 1407 let offset = radius / 6; 1408 let size = this.ds.length; 1409 this.ds.forEach(item => { 1410 if (item === this.selectItem) { 1411 context.beginPath(); 1412 context.moveTo(this.centerX,this.centerY); 1413 context.arc(this.centerX,this.centerY,radius + 5,item.startAngel,item.endAngel); 1414 context.fillStyle = this.colorRgbWithAlpha(item.color); 1415 context.fill(); 1416 context.closePath(); 1417 } 1418 context.beginPath(); 1419 context.moveTo(this.centerX,this.centerY); 1420 context.arc(this.centerX,this.centerY,radius,item.startAngel,item.endAngel); 1421 context.fillStyle = item.color; 1422 context.fill(); 1423 if (size > 1) { 1424 context.strokeStyle = '#ffffff'; 1425 context.stroke(); 1426 } 1427 if (item.value > 0.045) { 1428 let text = (item.value * 100).toFixed(1) + '%'; 1429 context.font = '13px Microsoft Yahei'; 1430 context.moveTo(this.centerX,this.centerY); 1431 context.fillStyle = '#ffffff'; 1432 let textWidth = context.measureText(text).width; 1433 if (size === 1) { 1434 context.fillText(text,this.centerX - textWidth / 2,this.centerY); 1435 } else { 1436 let textAngel = item.startAngel + item.angel * 0.5; // 文字角度 1437 labelX = this.centerX + Math.cos(textAngel) * (radius - offset); 1438 labelY = this.centerY + Math.sin(textAngel) * (radius - offset); 1439 context.fillText(text,labelX - textWidth / 2,labelY); 1440 } 1441 } 1442 context.closePath(); 1443 }) 1444 } 1445 } 1446 1447 addChartLegend() { 1448 this.labelDiv.innerText = ''; 1449 if (this.ds.length <= 15) { 1450 this.pageOptionDiv.style.display = 'none'; 1451 this.ds.forEach(item=>{ 1452 this.labelDiv.appendChild(this.createChartLegend(item)); 1453 }); 1454 } else { 1455 this.pageOptionDiv.style.display = 'flex'; 1456 for (let i = this.page * this.pageSize; i < (this.page + 1) * this.pageSize; i++) { 1457 if (i < this.ds.length) { 1458 this.labelDiv.appendChild(this.createChartLegend(this.ds[i])); 1459 } else { 1460 break; 1461 } 1462 } 1463 if (this.page === 9 && this.ds.length > 126) { 1464 this.labelDiv.appendChild(this.createChartLegend(this.ds[this.ds.length - 1])); 1465 } 1466 } 1467 this.pageDiv.innerText = `${this.page + 1}/${this.totalPage}`; 1468 } 1469 1470 previousPage() { 1471 if (this.page > 0) { 1472 this.page = this.page - 1; 1473 } 1474 this.addChartLegend(); 1475 this.updateOptionStatus(); 1476 } 1477 1478 nextPage() { 1479 if (this.page < this.totalPage - 1) { 1480 this.page = this.page + 1; 1481 } 1482 this.addChartLegend(); 1483 this.updateOptionStatus(); 1484 } 1485 1486 updateOptionStatus() { 1487 if (this.page === 0) { 1488 this.previousBt.setAttribute('disabled','true'); 1489 this.previousBt.style.color = '#999'; 1490 } else { 1491 this.previousBt.style.color = '#42b983'; 1492 this.previousBt.removeAttribute('disabled'); 1493 } 1494 if (this.page + 1 === this.totalPage) { 1495 this.nextBt.style.color = '#999'; 1496 this.nextBt.setAttribute('disabled','true'); 1497 } else { 1498 this.nextBt.style.color = '#42b983'; 1499 this.nextBt.removeAttribute('disabled'); 1500 } 1501 } 1502 1503 createChartLegend(item) { 1504 let legend = document.createElement('div'); 1505 legend.style.display = 'flex'; 1506 legend.style.alignItems = 'center'; 1507 legend.style.marginBottom = '5px'; 1508 legend.style.cursor = 'pointer'; 1509 let icon = document.createElement('div'); 1510 icon.style.backgroundColor = item.color; 1511 icon.style.borderRadius = '5px'; 1512 icon.style.width = '10px'; 1513 icon.style.height = '10px'; 1514 icon.style.marginRight = '10px'; 1515 let span = document.createElement('span'); 1516 span.style.fontSize = '14px'; 1517 span.style.width = '800px'; 1518 span.style.whiteSpace = 'nowrap'; 1519 span.style.textOverflow = 'ellipsis'; 1520 span.style.overflow = 'hidden'; 1521 span.innerText = item.name; 1522 legend.appendChild(icon); 1523 legend.appendChild(span); 1524 legend.addEventListener('mouseover',e => { 1525 this.selectItem = item; 1526 this.renderChart(); 1527 }); 1528 legend.addEventListener('click',e => { 1529 this.selectItem = item; 1530 this.mouseClick(e); 1531 }); 1532 legend.addEventListener('mouseout',e => { 1533 this.selectItem = undefined; 1534 this.renderChart(); 1535 }); 1536 return legend; 1537 } 1538 1539 colorRgbWithAlpha(sColor) { 1540 let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/; 1541 sColor = sColor.toLowerCase(); 1542 if (sColor && reg.test(sColor)) { 1543 if (sColor.length === 4) { 1544 let sColorNew = '#'; 1545 for (let i=1; i<4; i+=1) { 1546 sColorNew += sColor.slice(i,i+1).concat(sColor.slice(i,i+1)); 1547 } 1548 sColor = sColorNew; 1549 } 1550 // 处理六位的颜色值 1551 let sColorChange = []; 1552 for (let i=1; i<7; i+=2) { 1553 sColorChange.push(parseInt('0x'+sColor.slice(i,i+2))); 1554 } 1555 sColorChange.push(0.6); 1556 return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]},${sColorChange[3]})`; 1557 } else { 1558 return sColor; 1559 } 1560 }; 1561 } 1562 if (!customElements.get('lit-pie-chart')) { 1563 customElements.define('lit-pie-chart', LitPieChart); 1564 } 1565 1566 class LitSelect extends HTMLElement { 1567 static get observedAttributes() { 1568 return [ 1569 'value',// 默认值 1570 'default-value',// 默认值 1571 'placeholder',//placeholder 1572 'disabled', 1573 'loading',// 是否处于加载状态 1574 'allow-clear',// 是否允许清除 1575 'show-search',// 是否允许搜索 1576 'list-height',// 设置弹窗滚动高度 默认256px 1577 'border',// 是否显示边框 1578 'mode',// mode='multiple'多选 1579 ]; 1580 } 1581 1582 get value() { 1583 return this.getAttribute('value') || this.defaultValue; 1584 } 1585 1586 set value(value) { 1587 this.setAttribute('value', value); 1588 } 1589 1590 get border() { 1591 return this.getAttribute('border') || 'true'; 1592 } 1593 1594 set border(value) { 1595 if (value) { 1596 this.setAttribute('border', 'true'); 1597 } else { 1598 this.setAttribute('border', 'false'); 1599 } 1600 } 1601 1602 get listHeight() { 1603 return this.getAttribute('list-height') || '256px'; 1604 } 1605 1606 set listHeight(value) { 1607 this.setAttribute('list-height', value); 1608 } 1609 1610 get defaultPlaceholder() { 1611 return this.getAttribute('placeholder') || '请选择'; 1612 } 1613 1614 get showSearch() { 1615 return this.hasAttribute('show-search'); 1616 } 1617 1618 set defaultValue(value) { 1619 this.setAttribute('default-value', value); 1620 } 1621 1622 get defaultValue() { 1623 return this.getAttribute('default-value') || ''; 1624 } 1625 1626 set placeholder(value) { 1627 this.setAttribute('placeholder', value); 1628 } 1629 1630 get placeholder() { 1631 return this.getAttribute('placeholder') || this.defaultPlaceholder; 1632 } 1633 1634 get loading() { 1635 return this.hasAttribute('loading'); 1636 } 1637 1638 set loading(value) { 1639 if (value) { 1640 this.setAttribute('loading', ''); 1641 } else { 1642 this.removeAttribute('loading'); 1643 } 1644 } 1645 1646 constructor() { 1647 super(); 1648 const shadowRoot = this.attachShadow({mode: 'open'}); 1649 shadowRoot.innerHTML = ` 1650 <style> 1651:host{ 1652 display: inline-flex; 1653 position: relative; 1654 overflow: visible; 1655 cursor: pointer; 1656 transition: all .3s; 1657 border-radius: 2px; 1658 outline: none; 1659 -webkit-user-select:none ; 1660 -moz-user-select:none; 1661 user-select:none; 1662 /*width: 100%;*/ 1663} 1664:host(:not([border])), 1665:host([border='true']) { 1666 border: 1px solid #dcdcdc; 1667} 1668input{ 1669 border: 0; 1670 outline: none; 1671 background-color: transparent; 1672 cursor: pointer; 1673 transition: all .3s; 1674 -webkit-user-select:none ; 1675 -moz-user-select:none; 1676 user-select:none; 1677 display: inline-flex; 1678} 1679:host(:not([mode])) input{ 1680 width: 100%; 1681} 1682:host([mode]) input{ 1683 padding: 6px 0px; 1684} 1685:host([mode]) .root{ 1686 padding: 1px 8px; 1687} 1688.root{ 1689 position: relative; 1690 padding: 6px 8px; 1691 display: flex; 1692 align-items: center; 1693 justify-content: space-between; 1694 transition: all .3s; 1695 border-radius: 2px; 1696 background-color: #fff; 1697 outline: none; 1698 font-size: 1rem; 1699 z-index: 2; 1700 -webkit-user-select:none ; 1701 -moz-user-select:none; 1702 user-select:none; 1703 width: 100%; 1704} 1705.body{ 1706 max-height: ${this.listHeight}; 1707 position: absolute; 1708 top: 100%; 1709 z-index: 99; 1710 padding-top: 5px; 1711 margin-top: 2px; 1712 background-color: #fff; 1713 width: 100%; 1714 transition: all 0.2s; 1715 transform: scaleY(.6); 1716 visibility: hidden; 1717 opacity: 0; 1718 transform-origin: top center; 1719 display: block; 1720 flex-direction: column; 1721 box-shadow: 0 5px 15px 0px #00000033; 1722 border-radius: 2px; 1723 overflow: auto; 1724} 1725.icon{ 1726 pointer-events: none; 1727} 1728.noSelect{ 1729 -webkit-touch-callout:none; /* iOS Safari */ 1730 -webkit-user-select:none; 1731 -khtml-user-select:none; /* Konqueror */ 1732 -moz-user-select:none; /* Firefox */ 1733 -ms-user-select:none; /* Internet Explorer/Edge */ 1734 user-select:none; /* Non-prefixed version */ 1735} 1736 1737:host(:not([border]):not([disabled]):focus), 1738:host([border='true']:not([disabled]):focus), 1739:host(:not([border]):not([disabled]):hover), 1740:host([border='true']:not([disabled]):hover) { 1741 border:1px solid #42b983 1742} 1743:host(:not([disabled]):focus) .body, 1744:host(:not([disabled]):focus-within) .body{ 1745 transform: scaleY(1); 1746 opacity: 1; 1747 z-index: 99; 1748 visibility: visible; 1749} 1750:host(:not([disabled]):focus) input{ 1751 color: #bebebe; 1752} 1753:host(:not([border])[disabled]) *, 1754:host([border='true'][disabled]) *{ 1755 background-color: #f5f5f5; 1756 color: #b7b7b7; 1757 cursor: not-allowed; 1758} 1759:host([border='false'][disabled]) *{ 1760 color: #b7b7b7; 1761 cursor: not-allowed; 1762} 1763:host([loading]) .loading{ 1764 display: flex; 1765} 1766:host([loading]) .icon{ 1767 display: none; 1768} 1769:host(:not([loading])) .loading{ 1770 display: none; 1771} 1772:host(:not([loading])) .icon{ 1773 display: flex; 1774} 1775:host(:not([allow-clear])) .clear{ 1776 display: none; 1777} 1778.clear{ 1779 display: none; 1780 color: #bfbfbf; 1781} 1782.clear:hover{ 1783 color: #8c8c8c; 1784} 1785.search{ 1786 display: none; 1787 color: #bfbfbf; 1788} 1789.multipleRoot{ 1790 display: flex; 1791 flex-direction: column; 1792 flex-wrap: wrap; 1793 flex-flow: wrap; 1794 align-items: center; 1795 width: 100%; 1796} 1797.tag{ 1798 display: inline-flex; 1799 align-items: center; 1800 background-color: #f5f5f5; 1801 padding: 1px 4px; 1802 height: auto; 1803 font-size: .75rem; 1804 font-weight: bold; 1805 color: #242424; 1806 overflow: auto; 1807 position: relative; 1808 margin-right: 4px; 1809 margin-top: 1px; 1810 margin-bottom: 1px; 1811} 1812.tag-close{ 1813 font-size: .8rem; 1814 padding: 2px; 1815 margin-left: 0px; 1816 color: #999999; 1817} 1818.tag-close:hover{ 1819 color: #333; 1820} 1821 1822</style> 1823<div class="root noSelect" tabindex="0" hidefocus="true"> 1824 <div class="multipleRoot"> 1825 <input placeholder="${this.placeholder}" style="" autocomplete="off" ${this.showSearch ? '' : 'readonly'} tabindex="0"> 1826 </div><!--多选--> 1827 <lit-loading class="loading" size="12"></lit-loading> 1828 <!--<lit-icon class='icon' name='down' color="#c3c3c3"></lit-icon>--> 1829 <svg class='icon' viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;fill: #c3c3c3"> 1830 <path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3 0.1-12.7-6.4-12.7z"></path> 1831 </svg> 1832<!-- <lit-icon class="clear" name='close-circle-fill'></lit-icon>--> 1833 <svg class="clear" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;"> 1834 <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64z m165.4 618.2l-66-0.3L512 563.4l-99.3 118.4-66.1 0.3c-4.4 0-8-3.5-8-8 0-1.9 0.7-3.7 1.9-5.2l130.1-155L340.5 359c-1.2-1.5-1.9-3.3-1.9-5.2 0-4.4 3.6-8 8-8l66.1 0.3L512 464.6l99.3-118.4 66-0.3c4.4 0 8 3.5 8 8 0 1.9-0.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path> 1835 </svg> 1836<!-- <lit-icon class="search" name='search'></lit-icon>--> 1837 <svg class="search" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;"> 1838 <path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6c3.2 3.2 8.4 3.2 11.6 0l43.6-43.5c3.2-3.2 3.2-8.4 0-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path> 1839 </svg> 1840</div> 1841<div class="body"> 1842 <slot></slot> 1843 <slot name="footer"></slot> 1844</div> 1845 `; 1846 } 1847 1848 isMultiple() { 1849 return this.hasAttribute('mode') && this.getAttribute('mode') === 'multiple'; 1850 } 1851 1852 newTag(value, text) { 1853 let tag = document.createElement('div'); 1854 let icon = document.createElement('lit-icon'); 1855 icon.classList.add('tag-close'); 1856 icon.name = 'close'; 1857 let span = document.createElement('span'); 1858 tag.classList.add('tag'); 1859 span.dataset['value'] = value; 1860 span.textContent = text; 1861 tag.append(span); 1862 tag.append(icon); 1863 icon.onclick = ev => { 1864 tag.parentElement.removeChild(tag); 1865 this.querySelector(`lit-select-option[value=${value}]`).removeAttribute('selected'); 1866 if (this.shadowRoot.querySelectorAll('.tag').length === 0) { 1867 this.inputElement.style.width = 'auto'; 1868 this.inputElement.placeholder = this.defaultPlaceholder; 1869 } 1870 ev.stopPropagation(); 1871 } 1872 tag.value = value; 1873 tag.dataset['value'] = value; 1874 tag.text = text; 1875 tag.dataset['text'] = text; 1876 return tag; 1877 } 1878 1879 // 当 custom element首次被插入文档DOM时,被调用。 1880 connectedCallback() { 1881 this.tabIndex = 0;// 设置当前组件为可以获取焦点 1882 this.focused = false; 1883 this.inputElement = this.shadowRoot.querySelector('input'); 1884 this.inputElement.style.width = '100%'; 1885 this.clearElement = this.shadowRoot.querySelector('.clear'); 1886 this.iconElement = this.shadowRoot.querySelector('.icon'); 1887 this.searchElement = this.shadowRoot.querySelector('.search'); 1888 this.multipleRootElement = this.shadowRoot.querySelector('.multipleRoot'); 1889 // console.log(this.multipleRootElement); 1890 // 点击清理 清空input值,展示placeholder, 1891 this.clearElement.onclick = ev => { 1892 if (this.isMultiple()) { 1893 let delNodes = []; 1894 this.multipleRootElement.childNodes.forEach(a => { 1895 if (a.tagName === 'DIV') { 1896 delNodes.push(a); 1897 } 1898 }); 1899 for (let i = 0; i < delNodes.length; i++) { 1900 delNodes[i].remove(); 1901 } 1902 if (this.shadowRoot.querySelectorAll('.tag').length === 0) { 1903 this.inputElement.style.width = 'auto'; 1904 this.inputElement.placeholder = this.defaultPlaceholder; 1905 } 1906 } 1907 this.querySelectorAll('lit-select-option').forEach(a => a.removeAttribute('selected')); 1908 this.inputElement.value = ''; 1909 this.clearElement.style.display = 'none'; 1910 this.iconElement.style.display = 'flex'; 1911 this.blur(); 1912 ev.stopPropagation();// 这里不会因为点击清理而触发 选择栏目显示或者隐藏 1913 this.dispatchEvent(new CustomEvent('onClear', {detail: ev}));// 向外派发清理事件 1914 } 1915 // 初始化时遍历所有的option节点 1916 this.initOptions(); 1917 // 当前控件点击时 如果时select本身 需要显示 或 隐藏选择栏目,通过this.focused变量控制(默认为false) 1918 this.onclick = ev => { 1919 if (ev.target.tagName === 'LIT-SELECT') { 1920 if (!this.focused) { 1921 this.inputElement.focus(); 1922 this.focused = true; 1923 } else { 1924 this.blur(); 1925 this.focused = false; 1926 } 1927 } 1928 }; 1929 this.onmouseover = this.onfocus = ev => { 1930 if (this.hasAttribute('allow-clear')) { 1931 if (this.inputElement.value.length > 0 || 1932 this.inputElement.placeholder !== this.defaultPlaceholder) { 1933 this.clearElement.style.display = 'flex'; 1934 this.iconElement.style.display = 'none'; 1935 } else { 1936 this.clearElement.style.display = 'none'; 1937 this.iconElement.style.display = 'flex'; 1938 } 1939 } 1940 }; 1941 this.onmouseout = this.onblur = ev => { 1942 if (this.hasAttribute('allow-clear')) { 1943 this.clearElement.style.display = 'none'; 1944 this.iconElement.style.display = 'flex'; 1945 } 1946 this.focused = false; 1947 } 1948 // 输入框获取焦点时,value值 暂存于 placeholder 然后value值清空 1949 // 这样值会以placeholder形式灰色展示,鼠标位于第一个字符 1950 this.inputElement.onfocus = ev => { 1951 if (this.hasAttribute('disabled')) return;// 如果控件处于disabled状态 直接忽略 1952 if (this.inputElement.value.length > 0) { 1953 this.inputElement.placeholder = this.inputElement.value; 1954 this.inputElement.value = ''; 1955 } 1956 if (this.hasAttribute('show-search')) {// 如果有show-search属性 需要显示放大镜,隐藏向下的箭头 1957 this.searchElement.style.display = 'flex'; 1958 this.iconElement.style.display = 'none'; 1959 } 1960 // input获取焦点时显示所有可选项,相当于清理了搜索结果 1961 this.querySelectorAll('lit-select-option').forEach(a => { 1962 a.style.display = 'flex'; 1963 }) 1964 } 1965 // 当输入框失去焦点的时候 placeholder 的值 保存到value上,input显示值 1966 this.inputElement.onblur = ev => { 1967 if (this.hasAttribute('disabled')) return;// 如果控件处于disabled状态 直接忽略 1968 if (this.isMultiple()) { 1969 // 如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标 1970 if (this.hasAttribute('show-search')) { 1971 this.searchElement.style.display = 'none'; 1972 this.iconElement.style.display = 'flex'; 1973 } 1974 } else { 1975 // 如果placeholder为 请输入(默认值)不做处理 1976 if (this.inputElement.placeholder !== this.defaultPlaceholder) { 1977 this.inputElement.value = this.inputElement.placeholder; // placeholder 保存的值放入 value中 1978 this.inputElement.placeholder = this.defaultPlaceholder;// placeholder 值为 默认值(请输入) 1979 } 1980 // 如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标 1981 if (this.hasAttribute('show-search')) { 1982 this.searchElement.style.display = 'none'; 1983 this.iconElement.style.display = 'flex'; 1984 } 1985 } 1986 } 1987 // 输入框每次文本变化 会匹配搜索的option 显示或者隐藏,达到搜索的效果 1988 this.inputElement.oninput = ev => { 1989 let els = [...this.querySelectorAll('lit-select-option')]; 1990 if (!ev.target.value) { 1991 els.forEach(a => a.style.display = 'flex'); 1992 } else { 1993 els.forEach(a => { 1994 let value = a.getAttribute('value'); 1995 if (value.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 || 1996 a.textContent.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1) { 1997 a.style.display = 'flex'; 1998 } else { 1999 a.style.display = 'none'; 2000 } 2001 }) 2002 } 2003 } 2004 // 输入框按下回车键,自动输入当前搜索出来的第一行,及display!='none'的第一个,搜索会隐藏其他行 2005 this.inputElement.onkeydown = ev => { 2006 if (ev.key === 'Backspace') { 2007 if (this.isMultiple()) { 2008 let tag = this.multipleRootElement.lastElementChild.previousElementSibling; 2009 if (tag) { 2010 console.log(tag.value); 2011 this.querySelector(`lit-select-option[value=${tag.value}]`).removeAttribute('selected'); 2012 tag.remove(); 2013 if (this.shadowRoot.querySelectorAll('.tag').length === 0) { 2014 this.inputElement.style.width = 'auto'; 2015 this.inputElement.placeholder = this.defaultPlaceholder; 2016 } 2017 } 2018 } else { 2019 this.clear(); 2020 this.dispatchEvent(new CustomEvent('onClear', {detail: ev}));// 向外派发清理事件 2021 } 2022 } else if (ev.key === 'Enter') { 2023 let filter = 2024 [...this.querySelectorAll('lit-select-option')].filter(a => a.style.display !== 'none'); 2025 if (filter.length > 0) { 2026 this.inputElement.value = filter[0].textContent; 2027 this.inputElement.placeholder = filter[0].textContent; 2028 this.blur(); 2029 this.dispatchEvent(new CustomEvent('change', { 2030 detail: { 2031 selected: true, 2032 value: filter[0].getAttribute('value'), 2033 text: filter[0].textContent 2034 } 2035 }));// 向外层派发change事件,返回当前选中项 2036 } 2037 } 2038 }; 2039 } 2040 2041 initOptions() { 2042 this.querySelectorAll('lit-select-option').forEach(a => { 2043 // 如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本 2044 if (this.isMultiple()) { 2045 a.setAttribute('check', ''); 2046 if (a.getAttribute('value') === this.defaultValue) { 2047 let tag = this.newTag(a.getAttribute('value'), a.textContent); 2048 this.multipleRootElement.insertBefore(tag, this.inputElement); 2049 this.inputElement.placeholder = ''; 2050 this.inputElement.value = ''; 2051 this.inputElement.style.width = '1px'; 2052 a.setAttribute('selected', ''); 2053 } 2054 } else { 2055 if (a.getAttribute('value') === this.defaultValue) { 2056 this.inputElement.value = a.textContent; 2057 a.setAttribute('selected', ''); 2058 } 2059 } 2060 // 每个option设置onSelected事件 接受当前点击的option 2061 a.addEventListener('onSelected', (e) => { 2062 // 所有option设置为未选中状态 2063 if (this.isMultiple()) {//多选 2064 if (a.hasAttribute('selected')) { 2065 let tag = this.shadowRoot.querySelector(`div[data-value=${e.detail.value}]`); 2066 tag.parentElement.removeChild(tag); 2067 e.detail.selected = false; 2068 } else { 2069 let tag = this.newTag(e.detail.value, e.detail.text); 2070 this.multipleRootElement.insertBefore(tag, this.inputElement); 2071 this.inputElement.placeholder = ''; 2072 this.inputElement.value = ''; 2073 this.inputElement.style.width = '1px'; 2074 } 2075 if (this.shadowRoot.querySelectorAll('.tag').length === 0) { 2076 this.inputElement.style.width = 'auto'; 2077 this.inputElement.placeholder = this.defaultPlaceholder; 2078 } 2079 this.inputElement.focus(); 2080 } else {// 单选 2081 [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected')); 2082 this.blur();// 失去焦点,隐藏选择栏目列表 2083 this.inputElement.value = e.detail.text; 2084 } 2085 // 设置当前option为选择状态 2086 if (a.hasAttribute('selected')) { 2087 a.removeAttribute('selected'); 2088 } else { 2089 a.setAttribute('selected', ''); 2090 } 2091 // 设置input的值为当前选择的文本 2092 // 向外层派发change事件,返回当前选中项 2093 this.dispatchEvent(new CustomEvent('change', {detail: e.detail})); 2094 }); 2095 }); 2096 } 2097 2098 // js调用清理选项 2099 clear() { 2100 this.inputElement.value = ''; 2101 this.inputElement.placeholder = this.defaultPlaceholder; 2102 } 2103 2104 // 重置为默认值 2105 reset() { 2106 this.querySelectorAll('lit-select-option').forEach(a => { 2107 // 如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本 2108 [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected')); 2109 if (a.getAttribute('value') === this.defaultValue) { 2110 this.inputElement.value = a.textContent; 2111 a.setAttribute('selected', ''); 2112 } 2113 }); 2114 } 2115 2116 // 当 custom element从文档DOM中删除时,被调用。 2117 disconnectedCallback() { 2118 2119 } 2120 2121 // 当 custom element被移动到新的文档时,被调用。 2122 adoptedCallback() { 2123 console.log('Custom square element moved to new page.'); 2124 } 2125 2126 // 当 custom element增加、删除、修改自身属性时,被调用。 2127 attributeChangedCallback(name, oldValue, newValue) { 2128 if (name === 'value' && this.inputElement) { 2129 if (newValue) { 2130 [...this.querySelectorAll('lit-select-option')].forEach(a => { 2131 if (a.getAttribute('value') === newValue) { 2132 a.setAttribute('selected', ''); 2133 this.inputElement.value = a.textContent; 2134 } else { 2135 a.removeAttribute('selected'); 2136 } 2137 }); 2138 } else { 2139 this.clear(); 2140 } 2141 } 2142 } 2143 2144 set dataSource(value) { 2145 value.forEach(a => { 2146 let option = document.createElement('lit-select-option'); 2147 option.setAttribute('value', a.key); 2148 option.textContent = a.val; 2149 this.append(option); 2150 }) 2151 this.initOptions(); 2152 } 2153 2154 } 2155 if (!customElements.get('lit-select')) { 2156 customElements.define('lit-select', LitSelect); 2157 } 2158 2159 class LitSelectGroup extends HTMLElement { 2160 static get observedAttributes() { 2161 return ['label']; 2162 } 2163 2164 get label() { 2165 return this.getAttribute('label') || ''; 2166 } 2167 2168 set label(value) { 2169 this.setAttribute('label', value); 2170 } 2171 2172 constructor() { 2173 super(); 2174 const shadowRoot = this.attachShadow({mode: 'open'}); 2175 shadowRoot.innerHTML = ` 2176 <style> 2177 :host{ 2178 display: flex; 2179 flex-direction: column; 2180 /*padding-left: 10px;*/ 2181 } 2182 .lab{ 2183 padding: 8px 10px 8px 10px; 2184 font-size: .5rem; 2185 color: #8c8c8c; 2186 } 2187 ::slotted(lit-select-option) { 2188 padding-left: 20px; 2189 } 2190 </style> 2191 <div class='lab'>${this.label}</div> 2192 <slot></slot> 2193 `; 2194 } 2195 2196 // 当 custom element首次被插入文档DOM时,被调用。 2197 connectedCallback() { 2198 2199 } 2200 2201 // 当 custom element从文档DOM中删除时,被调用。 2202 disconnectedCallback() { 2203 2204 } 2205 2206 // 当 custom element被移动到新的文档时,被调用。 2207 adoptedCallback() { 2208 console.log('Custom square element moved to new page.'); 2209 } 2210 2211 // 当 custom element增加、删除、修改自身属性时,被调用。 2212 attributeChangedCallback(name, oldValue, newValue) { 2213 2214 } 2215 } 2216 if (!customElements.get('lit-select-group')) { 2217 customElements.define('lit-select-group', LitSelectGroup); 2218 } 2219 2220 class LitSelectOption extends HTMLElement { 2221 static get observedAttributes() { 2222 return ['selected','disabled','check']; 2223 } 2224 2225 constructor() { 2226 super(); 2227 const shadowRoot = this.attachShadow({mode: 'open'}); 2228 shadowRoot.innerHTML = ` 2229 <style> 2230 :host{ 2231 display: flex; 2232 padding: 8px 10px; 2233 transition: all .3s; 2234 color: #333; 2235 tab-index: -1; 2236 overflow: scroll; 2237 align-items: center; 2238 justify-content: space-between; 2239 } 2240 :host(:not([disabled])[selected]) { 2241 background-color: #e9f7fe; 2242 font-weight: bold; 2243 } 2244 :host(:not([disabled]):not([selected]):hover) { 2245 background-color: #f5f5f5; 2246 } 2247 :host([disabled]) { 2248 cursor: not-allowed; 2249 color: #bfbfbf; 2250 } 2251 :host([selected][check]) .check{ 2252 display: flex; 2253 } 2254 :host(:not([selected])) .check{ 2255 display: none; 2256 } 2257 :host(:not([check])) .check{ 2258 display: none; 2259 } 2260 </style> 2261 <slot></slot> 2262 <lit-icon class='check' name='check'></lit-icon> 2263 `; 2264 } 2265 2266 // 当 custom element首次被插入文档DOM时,被调用。 2267 connectedCallback() { 2268 if (!this.hasAttribute('disabled')) { 2269 this.onclick=ev => { 2270 this.dispatchEvent(new CustomEvent('onSelected', { 2271 detail: { 2272 selected:true, 2273 value: this.getAttribute('value'), 2274 text: this.textContent 2275 } 2276 })); 2277 }; 2278 } 2279 } 2280 2281 // 当 custom element从文档DOM中删除时,被调用。 2282 disconnectedCallback() { 2283 2284 } 2285 2286 // 当 custom element被移动到新的文档时,被调用。 2287 adoptedCallback() { 2288 console.log('Custom square element moved to new page.'); 2289 } 2290 2291 // 当 custom element增加、删除、修改自身属性时,被调用。 2292 attributeChangedCallback(name, oldValue, newValue) { 2293 2294 } 2295 } 2296 if (!customElements.get('lit-select-option')) { 2297 customElements.define('lit-select-option', LitSelectOption); 2298 } 2299 2300 class LitTable extends HTMLElement { 2301 static get observedAttributes() { 2302 return ['scroll-y', 'selectable','defaultOrderColumn']; 2303 } 2304 2305 get selectable() { 2306 return this.hasAttribute('selectable'); 2307 } 2308 2309 set selectable(value) { 2310 if (value) { 2311 this.setAttribute('selectable', ''); 2312 } else { 2313 this.removeAttribute('selectable'); 2314 } 2315 } 2316 2317 get scrollY() { 2318 return this.getAttribute('scroll-y') || 'auto'; 2319 } 2320 2321 set scrollY(value) { 2322 this.setAttribute('scroll-y', value); 2323 } 2324 2325 get dataSource() { 2326 return this.ds || []; 2327 } 2328 2329 set dataSource(value) { 2330 this.ds = value; 2331 if (this.hasAttribute('tree')) { 2332 this.renderTreeTable(); 2333 } else { 2334 this.renderTable(); 2335 } 2336 } 2337 2338 constructor() { 2339 super(); 2340 const shadowRoot = this.attachShadow({mode: 'open'}); 2341 shadowRoot.innerHTML = ` 2342<style> 2343:host{ 2344 display: grid; 2345 grid-template-columns: repeat(1,1fr); 2346 overflow: auto; 2347 /*width: 500px;*/ 2348 width: 100%; 2349 height: 100%; 2350} 2351.tr{ 2352 display: grid; 2353 transition: all .3s; 2354} 2355.tr:nth-of-type(even) { 2356 background-color: #fcfcfc; 2357} 2358/*.tr:not(:last-of-type):not(:first-of-type) {*/ 2359/* border-top: 1px solid #f0f0f0;*/ 2360/*}*/ 2361/*.tr:last-of-type{*/ 2362/* border-top: 1px solid #f0f0f0;*/ 2363/* border-bottom: 1px solid #f0f0f0;*/ 2364/*}*/ 2365.tr{ 2366 background-color: #fff; 2367} 2368.tr:hover{ 2369 background-color: #f3f3f3; /*antd #fafafa 42b983*/ 2370} 2371.td{ 2372 background-color: inherit; 2373 box-sizing: border-box; 2374 padding: 10px; 2375 display: flex; 2376 justify-content: flex-start; 2377 align-items: center; 2378 width: 100%; 2379 height: auto; 2380 /*overflow: auto;*/ 2381 border-left: 1px solid #f0f0f0; 2382} 2383.td-order{ 2384 /*background: green;*/ 2385} 2386.td-order:before{ 2387 2388} 2389.td:last-of-type{ 2390 border-right: 1px solid #f0f0f0; 2391} 2392.table{ 2393 color: #262626; 2394} 2395:host(:not([noheader])) .thead{ 2396 display: grid; 2397 position: sticky; 2398 top: 0; 2399 font-weight: bold; 2400 font-size: .9rem; 2401 color: #fff; 2402 /*width: 100%;*/ 2403 background-color: #42b983; 2404 z-index: 1; 2405} 2406/*配置有 noheader 表示不限时表头,tbody上添加 border-top*/ 2407:host([noheader]) .thead{ 2408 display: none; 2409 position: sticky; 2410 top: 0; 2411 font-weight: bold; 2412 font-size: .9rem; 2413 color: #fff; 2414 /*width: 100%;*/ 2415 background-color: #42b983; 2416 z-index: 1; 2417} 2418:host([noheader]) .tbody{ 2419 border-top: 1px solid #f0f0f0; 2420} 2421 2422.tbody{ 2423 width: 100%; 2424 height: ${this.scrollY}; 2425 display: grid; 2426 grid-template-columns: 1fr; 2427 row-gap: 1px; 2428 column-gap: 1px; 2429 background-color: #f0f0f0; 2430 border-bottom: 1px solid #f0f0f0; 2431 /*overflow: auto;*/ 2432 ${this.scrollY === 'auto' ? '' : 'overflow-y: auto'}; 2433} 2434.th{ 2435 display: grid; 2436 background-color: #42b983; 2437 /*position: sticky;*/ 2438 /*top: 0;*/ 2439} 2440 2441.tree-icon{ 2442 font-size: 1.2rem; 2443 width: 20px; 2444 height: 20px; 2445 padding-right: 5px; 2446 padding-left: 5px; 2447 cursor: pointer; 2448} 2449.tree-icon:hover{ 2450 color: #42b983; 2451} 2452.row-checkbox,row-checkbox-all{ 2453 2454} 2455 2456.up-svg{ 2457 position: absolute; 2458 right: 5px; 2459 top: 8px; 2460 width: 15px; 2461 height: 15px; 2462} 2463.down-svg{ 2464 position: absolute; 2465 right: 5px; 2466 bottom: 8px; 2467 width: 15px; 2468 height: 15px; 2469} 2470</style> 2471 2472<slot id="slot" style="display: none"></slot> 2473<div class="table"> 2474 <div class="thead"></div> 2475 <div class="tbody"></div> 2476</div> 2477 `; 2478 } 2479 2480 /*根据column[]嵌套结构得到 grid css布局描述*/ 2481 2482 // 当 custom element首次被插入文档DOM时,被调用。 2483 connectedCallback() { 2484 this.st = this.shadowRoot.querySelector('#slot'); 2485 this.tableElement = this.shadowRoot.querySelector('.table'); 2486 this.theadElement = this.shadowRoot.querySelector('.thead'); 2487 this.tbodyElement = this.shadowRoot.querySelector('.tbody'); 2488 this.tableColumns = this.querySelectorAll('lit-table-column'); 2489 this.colCount = this.tableColumns.length; 2490 this.st.addEventListener('slotchange', (event) => { 2491 this.theadElement.innerHTML = ''; 2492 setTimeout(() => { 2493 this.columns = this.st.assignedElements(); 2494 2495 let rowElement = document.createElement('div'); 2496 rowElement.classList.add('th'); 2497 if (this.selectable) { 2498 let box = document.createElement('div'); 2499 box.style.display = 'flex'; 2500 box.style.justifyContent = 'center'; 2501 box.style.alignItems = 'center'; 2502 box.style.gridArea = '_checkbox_'; 2503 box.classList.add('td'); 2504 box.style.backgroundColor = '#ffffff66'; 2505 let checkbox = document.createElement('lit-checkbox'); 2506 checkbox.classList.add('row-checkbox-all'); 2507 checkbox.onchange = e => { 2508 this.shadowRoot.querySelectorAll('.row-checkbox').forEach(a => a.checked = e.detail.checked); 2509 if (e.detail.checked) { 2510 this.shadowRoot.querySelectorAll('.tr').forEach(a => a.setAttribute('checked', '')); 2511 } else { 2512 this.shadowRoot.querySelectorAll('.tr').forEach(a => a.removeAttribute('checked')); 2513 } 2514 }; 2515 2516 box.appendChild(checkbox); 2517 rowElement.appendChild(box); 2518 } 2519 2520 let area = [], gridTemplateColumns = []; 2521 let resolvingArea = (columns, x, y) => { 2522 columns.forEach((a, i) => { 2523 if (!area[y]) area[y] = []; 2524 let key = a.getAttribute('key') || a.getAttribute('title'); 2525 if (a.tagName === 'LIT-TABLE-GROUP') { 2526 let len = a.querySelectorAll('lit-table-column').length; 2527 let children = [...a.children].filter(a => a.tagName !== 'TEMPLATE'); 2528 if (children.length > 0) { 2529 resolvingArea(children, x, y + 1); 2530 } 2531 for (let j = 0; j < len; j++) { 2532 area[y][x] = {x, y, t: key}; 2533 x++; 2534 } 2535 let h = document.createElement('div'); 2536 h.classList.add('td'); 2537 h.style.justifyContent = a.getAttribute('align'); 2538 h.style.borderBottom = '1px solid #f0f0f0'; 2539 h.style.gridArea = key; 2540 h.innerText = a.title; 2541 if (a.hasAttribute('fixed')) { 2542 this.fixed(h, a.getAttribute('fixed'), '#42b983'); 2543 } 2544 rowElement.append(h); 2545 } else if (a.tagName === 'LIT-TABLE-COLUMN') { 2546 area[y][x] = {x, y, t: key}; 2547 x++; 2548 let h = document.createElement('div'); 2549 h.classList.add('td'); 2550 if (a.hasAttribute('order')) { 2551 h.sortType = 0; 2552 h.classList.add('td-order'); 2553 h.style.position = 'relative'; 2554 let NS = 'http://www.w3.org/2000/svg'; 2555 let upSvg = document.createElementNS(NS,'svg'); 2556 let upPath = document.createElementNS(NS,'path'); 2557 upSvg.setAttribute('fill', '#efefef'); 2558 upSvg.setAttribute('viewBox', '0 0 1024 1024'); 2559 upSvg.setAttribute('stroke', '#000000'); 2560 upSvg.classList.add('up-svg'); 2561 upPath.setAttribute('d', 'M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z'); 2562 2563 upSvg.appendChild(upPath); 2564 let downSvg = document.createElementNS(NS,'svg'); 2565 let downPath = document.createElementNS(NS,'path'); 2566 downSvg.setAttribute('fill', '#efefef'); 2567 downSvg.setAttribute('viewBox', '0 0 1024 1024'); 2568 downSvg.setAttribute('stroke', '#efefef'); 2569 downSvg.classList.add('down-svg'); 2570 downPath.setAttribute('d', 'M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z'); 2571 downSvg.appendChild(downPath); 2572 if (i === 0) { 2573 h.sortType = 2; // 默认以第一列 降序排序 作为默认排序 2574 upSvg.setAttribute('fill', '#efefef'); 2575 downSvg.setAttribute('fill', '#333'); 2576 } 2577 h.appendChild(upSvg); 2578 h.appendChild(downSvg); 2579 h.onclick = ev => { 2580 this.shadowRoot.querySelectorAll('.td-order svg').forEach(it=>{ 2581 it.setAttribute('fill', '#efefef'); 2582 it.setAttribute('fill', '#efefef'); 2583 it.sortType = 0; 2584 }); 2585 if (h.sortType === undefined || h.sortType === null) { 2586 h.sortType = 0; 2587 } else if (h.sortType === 2) { 2588 h.sortType = 0; 2589 } else { 2590 h.sortType += 1; 2591 } 2592 switch (h.sortType) { 2593 case 1: 2594 upSvg.setAttribute('fill', '#333'); 2595 downSvg.setAttribute('fill', '#efefef'); 2596 break; 2597 case 2: 2598 upSvg.setAttribute('fill', '#efefef'); 2599 downSvg.setAttribute('fill', '#333'); 2600 break; 2601 default: 2602 upSvg.setAttribute('fill', '#efefef'); 2603 downSvg.setAttribute('fill', '#efefef'); 2604 break; 2605 }; 2606 this.dispatchEvent(new CustomEvent('ColumnClick', { 2607 detail: { 2608 sort: h.sortType, key: key 2609 }, composed: true 2610 })); 2611 }; 2612 } 2613 h.style.justifyContent = a.getAttribute('align'); 2614 gridTemplateColumns.push(a.getAttribute('width') || '1fr'); 2615 h.style.gridArea = key; 2616 let titleLabel = document.createElement('label'); 2617 titleLabel.textContent = a.title; 2618 h.appendChild(titleLabel); 2619 if (a.hasAttribute('fixed')) { 2620 this.fixed(h, a.getAttribute('fixed'), '#42b983'); 2621 } 2622 rowElement.append(h); 2623 } 2624 }); 2625 }; 2626 resolvingArea(this.columns, 0, 0); 2627 area.forEach((rows, j, array) => { 2628 for (let i = 0; i < this.colCount; i++) { 2629 if (!rows[i]) rows[i] = array[j - 1][i]; 2630 } 2631 }); 2632 2633 this.gridTemplateColumns = gridTemplateColumns.join(' '); 2634 if (this.selectable) { 2635 let s = area.map(a => '"_checkbox_ ' + (a.map(aa => aa.t).join(' ')) + '"').join(' '); 2636 // `repeat(${this.colCount},1fr)` 2637 rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' '); 2638 rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`; 2639 rowElement.style.gridTemplateAreas = s; 2640 } else { 2641 let s = area.map(a => '"' + (a.map(aa => aa.t).join(' ')) + '"').join(' '); 2642 // `repeat(${this.colCount},1fr)` 2643 rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); 2644 rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`; 2645 rowElement.style.gridTemplateAreas = s; 2646 } 2647 this.theadElement.append(rowElement); 2648 if (this.hasAttribute('tree')) { 2649 this.renderTreeTable(); 2650 } else { 2651 this.renderTable(); 2652 } 2653 }); 2654 2655 }); 2656 } 2657 2658 // 当 custom element从文档DOM中删除时,被调用。 2659 disconnectedCallback() { 2660 2661 } 2662 2663 // 当 custom element被移动到新的文档时,被调用。 2664 adoptedCallback() { 2665 console.log('Custom square element moved to new page.'); 2666 } 2667 2668 // 当 custom element增加、删除、修改自身属性时,被调用。 2669 attributeChangedCallback(name, oldValue, newValue) { 2670 2671 } 2672 2673 fixed(td, placement, bgColor, zIndex) { 2674 td.style.position = 'sticky'; 2675 if (placement === 'left') { 2676 td.style.left = '0px'; 2677 td.style.boxShadow = '3px 0px 5px #33333333'; 2678 } else if (placement === 'right') { 2679 td.style.right = '0px'; 2680 td.style.boxShadow = '-3px 0px 5px #33333333'; 2681 } 2682 } 2683 2684 /*渲染成表格*/ 2685 renderTable() { 2686 let that = this; 2687 if (!this.columns) return; 2688 if (!this.ds) return; // 如果没有设置数据源,直接返回 2689 this.tbodyElement.innerHTML = '';// 清空表格内容 2690 this.ds.forEach(rowData => { 2691 let rowElement = document.createElement('div'); 2692 rowElement.classList.add('tr'); 2693 rowElement.data = rowData; 2694 let gridTemplateColumns = []; 2695 // 如果table配置了selectable(选择行模式) 单独在行头部添加一个 checkbox 2696 if (this.selectable) { 2697 let box = document.createElement('div'); 2698 box.style.display = 'flex'; 2699 box.style.justifyContent = 'center'; 2700 box.style.alignItems = 'center'; 2701 box.classList.add('td'); 2702 let checkbox = document.createElement('lit-checkbox'); 2703 checkbox.classList.add('row-checkbox'); 2704 checkbox.onchange = (e) => {// checkbox 的是否选中 会影响 行对应的 div上是否有 checked属性,用于标记 2705 if (e.detail.checked) { 2706 rowElement.setAttribute('checked', ''); 2707 } else { 2708 rowElement.removeAttribute('checked'); 2709 } 2710 }; 2711 box.appendChild(checkbox); 2712 rowElement.appendChild(box); 2713 } 2714 this.tableColumns.forEach(cl => { 2715 let dataIndex = cl.getAttribute('data-index'); 2716 gridTemplateColumns.push(cl.getAttribute('width') || '1fr'); 2717 if (cl.template) {// 如果自定义渲染,从模版中渲染得到节点 2718 let cloneNode = cl.template.render(rowData).content.cloneNode(true); 2719 let d = document.createElement('div'); 2720 d.classList.add('td'); 2721 d.style.justifyContent = cl.getAttribute('align'); 2722 if (cl.hasAttribute('fixed')) { 2723 this.fixed(d, cl.getAttribute('fixed'), '#ffffff'); 2724 } 2725 d.append(cloneNode); 2726 rowElement.append(d); 2727 } else { 2728 let td = document.createElement('div'); 2729 td.classList.add('td'); 2730 td.style.justifyContent = cl.getAttribute('align'); 2731 if (cl.hasAttribute('fixed')) { 2732 this.fixed(td, cl.getAttribute('fixed'), '#ffffff'); 2733 } 2734 2735 td.innerHTML = 2736 `<code style='padding:0;margin:0'>${rowData[dataIndex].toString().replace('\n','')}</code>`; 2737 rowElement.append(td); 2738 } 2739 2740 }); 2741 if (this.selectable) { // 如果 带选择的table 前面添加一个 60px的列 2742 // `repeat(${this.colCount},1fr)` 2743 rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' '); 2744 } else { 2745 // `repeat(${this.colCount},1fr)` 2746 rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); 2747 } 2748 rowElement.onclick = e => { 2749 this.dispatchEvent(new CustomEvent('onRowClick', {detail: rowData, composed: true})); 2750 }; 2751 this.tbodyElement.append(rowElement); 2752 }); 2753 } 2754 2755 /*渲染树表结构*/ 2756 renderTreeTable() { 2757 if (!this.columns) return; 2758 if (!this.ds) return; 2759 this.tbodyElement.innerHTML = ''; 2760 /*通过list 构建 tree 结构*/ 2761 let ids = JSON.parse(this.getAttribute('tree') || `['id','pid']`); 2762 let toTreeData = (data, id, pid) => { 2763 let cloneData = JSON.parse(JSON.stringify(data)); 2764 return cloneData.filter(father => { 2765 let branchArr = cloneData.filter(child => father[id] === child[pid]); 2766 branchArr.length > 0 ? father['children'] = branchArr : ''; 2767 return !father[pid]; 2768 }); 2769 }; 2770 let treeData = toTreeData(this.ds, ids[0], ids[1]); 2771 let offset = 30; 2772 let offsetVal = offset; 2773 const drawRow = (arr, parentNode) => { 2774 arr.forEach(rowData => { 2775 let rowElement = document.createElement('div'); 2776 rowElement.classList.add('tr'); 2777 rowElement.data = rowData; 2778 let gridTemplateColumns = []; 2779 if (this.selectable) { 2780 let box = document.createElement('div'); 2781 box.style.display = 'flex'; 2782 box.style.justifyContent = 'center'; 2783 box.style.alignItems = 'center'; 2784 box.classList.add('td'); 2785 let checkbox = document.createElement('lit-checkbox'); 2786 checkbox.classList.add('row-checkbox'); 2787 checkbox.onchange = (e) => { 2788 if (e.detail.checked) { 2789 rowElement.setAttribute('checked', ''); 2790 } else { 2791 rowElement.removeAttribute('checked'); 2792 } 2793 const changeChildNode = (rowElement, checked) => { 2794 let id = rowElement.getAttribute('id'); 2795 let pid = rowElement.getAttribute('pid'); 2796 this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => { 2797 a.querySelector('.row-checkbox').checked = checked; 2798 if (checked) { 2799 a.setAttribute('checked', ''); 2800 } else { 2801 a.removeAttribute('checked'); 2802 } 2803 changeChildNode(a, checked); 2804 }); 2805 }; 2806 changeChildNode(rowElement, e.detail.checked); 2807 }; 2808 box.appendChild(checkbox); 2809 rowElement.appendChild(box); 2810 } 2811 this.tableColumns.forEach((cl, index) => { 2812 let dataIndex = cl.getAttribute('data-index'); 2813 gridTemplateColumns.push(cl.getAttribute('width') || '1fr'); 2814 let td; 2815 if (cl.template) { 2816 let cloneNode = cl.template.render(rowData).content.cloneNode(true); 2817 td = document.createElement('div'); 2818 td.classList.add('td'); 2819 td.style.justifyContent = cl.getAttribute('align'); 2820 if (cl.hasAttribute('fixed')) { 2821 this.fixed(td, cl.getAttribute('fixed'), '#ffffff'); 2822 } 2823 td.append(cloneNode); 2824 } else { 2825 td = document.createElement('div'); 2826 td.classList.add('td'); 2827 td.style.justifyContent = cl.getAttribute('align') 2828 if (cl.hasAttribute('fixed')) { 2829 this.fixed(td, cl.getAttribute('fixed'), '#ffffff') 2830 } 2831 td.innerHTML = rowData[dataIndex]; 2832 } 2833 if (index === 0) { 2834 if (rowData.children && rowData.children.length > 0) { 2835 let btn = document.createElement('lit-icon'); 2836 btn.classList.add('tree-icon'); 2837 btn.name = 'minus-square'; 2838 td.insertBefore(btn, td.firstChild); 2839 td.style.paddingLeft = (offsetVal - 30) + 'px'; 2840 btn.onclick = (e) => { 2841 const foldNode = (rowElement) => { 2842 let id = rowElement.getAttribute('id'); 2843 let pid = rowElement.getAttribute('pid'); 2844 this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => { 2845 let id = a.getAttribute('id'); 2846 let pid = a.getAttribute('pid'); 2847 a.style.display = 'none'; 2848 foldNode(a); 2849 }); 2850 if (rowElement.querySelector('.tree-icon')) { 2851 rowElement.querySelector('.tree-icon').name = 'plus-square'; 2852 } 2853 rowElement.removeAttribute('expand'); 2854 }; 2855 const expendNode = (rowElement) => { 2856 let id = rowElement.getAttribute('id'); 2857 let pid = rowElement.getAttribute('pid'); 2858 this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => { 2859 let id = a.getAttribute('id'); 2860 let pid = a.getAttribute('pid'); 2861 a.style.display = ''; 2862 }); 2863 if (rowElement.querySelector('.tree-icon')) { 2864 rowElement.querySelector('.tree-icon').name = 'minus-square'; 2865 } 2866 rowElement.setAttribute('expand', ''); 2867 }; 2868 if (rowElement.hasAttribute('expand')) { 2869 foldNode(rowElement); 2870 } else { 2871 expendNode(rowElement); 2872 } 2873 }; 2874 } else { 2875 td.style.paddingLeft = offsetVal + 'px'; 2876 } 2877 } 2878 rowElement.append(td); 2879 2880 }); 2881 if (this.selectable) { 2882 // `repeat(${this.colCount},1fr)` 2883 rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' '); 2884 } else { 2885 // `repeat(${this.colCount},1fr)` 2886 rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' '); 2887 } 2888 rowElement.onclick = e => { 2889 }; 2890 parentNode.append(rowElement); 2891 rowElement.setAttribute('id', rowData[ids[0]]); 2892 rowElement.setAttribute('pid', rowData[ids[1]]); 2893 rowElement.setAttribute('expand', ''); 2894 if (rowData.children && rowData.children.length > 0) { 2895 // 有子节点的 前面加上 + 图标 表示可以展开节点 2896 offsetVal = offsetVal + offset; 2897 drawRow(rowData.children, parentNode); 2898 offsetVal = offsetVal - offset; 2899 } 2900 }); 2901 }; 2902 drawRow(treeData, this.tbodyElement); 2903 } 2904 2905 // 获取选中的行数据 2906 getCheckRows() { 2907 return [...this.shadowRoot.querySelectorAll('div[class=tr][checked]')].map(a => a.data).map(a => { 2908 delete a['children']; 2909 return a; 2910 }); 2911 } 2912 2913 deleteRowsCondition(fn) { 2914 this.shadowRoot.querySelectorAll('div[class=tr]').forEach(tr => { 2915 if (fn(tr.data)) { 2916 tr.remove(); 2917 } 2918 }); 2919 } 2920 } 2921 if (!customElements.get('lit-table')) { 2922 customElements.define('lit-table', LitTable); 2923 } 2924 2925 class LitTableColumn extends HTMLElement { 2926 static get observedAttributes() { 2927 return ['name','order']; 2928 } 2929 2930 constructor() { 2931 super(); 2932 const shadowRoot = this.attachShadow({mode: 'open'}); 2933 shadowRoot.innerHTML = ` 2934<style> 2935:host{ } 2936</style> 2937<slot id='slot'></slot> 2938 `; 2939 } 2940 // 当 custom element首次被插入文档DOM时,被调用。 2941 connectedCallback() { 2942 this.template=null; 2943 this.st = this.shadowRoot.querySelector('#slot') 2944 this.st.addEventListener('slotchange', () => { 2945 const elements = this.st.assignedElements({flatten: false}); 2946 if (elements.length>0) { 2947 this.template = elements[0]; 2948 } 2949 }) 2950 } 2951 2952 // 当 custom element从文档DOM中删除时,被调用。 2953 disconnectedCallback() { 2954 2955 } 2956 2957 // 当 custom element被移动到新的文档时,被调用。 2958 adoptedCallback() { 2959 console.log('Custom square element moved to new page.'); 2960 } 2961 2962 // 当 custom element增加、删除、修改自身属性时,被调用。 2963 attributeChangedCallback(name, oldValue, newValue) { 2964 2965 } 2966 } 2967 if (!customElements.get('lit-table-column')) { 2968 customElements.define('lit-table-column', LitTableColumn); 2969 } 2970 2971 class LitTableGroup extends HTMLElement { 2972 static get observedAttributes() { 2973 return ['title']; 2974 } 2975 2976 get title() { 2977 return this.getAttribute('title'); 2978 } 2979 2980 set title(value) { 2981 this.setAttribute('title', value); 2982 } 2983 2984 constructor() { 2985 super(); 2986 const shadowRoot = this.attachShadow({mode: 'open'}); 2987 shadowRoot.innerHTML = ` 2988 <style> 2989 :host{ } 2990 </style> 2991 <slot id='sl'></slot> 2992 `; 2993 } 2994 2995 // 当 custom element首次被插入文档DOM时,被调用。 2996 connectedCallback() { 2997 2998 } 2999 3000 // 当 custom element从文档DOM中删除时,被调用。 3001 disconnectedCallback() { 3002 3003 } 3004 3005 // 当 custom element被移动到新的文档时,被调用。 3006 adoptedCallback() { 3007 console.log('Custom square element moved to new page.'); 3008 } 3009 3010 // 当 custom element增加、删除、修改自身属性时,被调用。 3011 attributeChangedCallback(name, oldValue, newValue) { 3012 3013 } 3014 } 3015 if (!customElements.get('lit-table-group')) { 3016 customElements.define('lit-table-group', LitTableGroup); 3017 } 3018 3019 class LitTabpane extends HTMLElement { 3020 static get observedAttributes() {return ['tab','key','disabled','icon','closeable','hide'];} 3021 constructor() { 3022 super(); 3023 const shadowRoot = this.attachShadow({mode: 'open'}); 3024 shadowRoot.innerHTML = ` 3025<style> 3026:host() { 3027 scroll-behavior: smooth; 3028 -webkit-overflow-scrolling: touch; 3029 overflow: auto; 3030 width: 100%; 3031} 3032</style> 3033<slot></slot> 3034`; 3035 } 3036 3037 get tab() { 3038 return this.getAttribute('tab'); 3039 } 3040 3041 set tab(value) { 3042 this.setAttribute('tab', value); 3043 } 3044 3045 get icon() { 3046 return this.getAttribute('icon'); 3047 } 3048 3049 get disabled() { 3050 return this.getAttribute('disabled')!==null; 3051 } 3052 3053 set disabled(value) { 3054 if (value===null||value===false) { 3055 this.removeAttribute('disabled'); 3056 } else { 3057 this.setAttribute('disabled',value); 3058 } 3059 } 3060 get closeable() { 3061 return this.getAttribute('closeable')!==null; 3062 } 3063 set closeable(value) { 3064 if (value===null||value===false) { 3065 this.removeAttribute('closeable'); 3066 } else { 3067 this.setAttribute('closeable',value); 3068 } 3069 } 3070 3071 get key() { 3072 return this.getAttribute('key'); 3073 } 3074 set key(value) { 3075 this.setAttribute('key', value); 3076 } 3077 3078 get hide() { 3079 return this.getAttribute('hide')!==null; 3080 } 3081 set hide(value) { 3082 if (value===null||value===false) { 3083 this.removeAttribute('hide'); 3084 } else { 3085 this.setAttribute('hide',value); 3086 } 3087 } 3088 3089 // 当 custom element首次被插入文档DOM时,被调用。 3090 connectedCallback() {} 3091 3092 // 当 custom element从文档DOM中删除时,被调用。 3093 disconnectedCallback() {} 3094 3095 // 当 custom element被移动到新的文档时,被调用。 3096 adoptedCallback() {} 3097 3098 // 当 custom element增加、删除、修改自身属性时,被调用。 3099 attributeChangedCallback(name, oldValue, newValue) { 3100 if (oldValue!==newValue && newValue!==undefined) { 3101 if (name==='tab'&&this.parentNode) { 3102 this.parentNode.updateLabel && this.parentNode.updateLabel(this.key,newValue); 3103 } 3104 if (name==='disabled'&&this.parentNode) { 3105 this.parentNode.updateDisabled && this.parentNode.updateDisabled(this.key,newValue); 3106 } 3107 if (name==='closeable'&&this.parentNode) { 3108 this.parentNode.updateCloseable && this.parentNode.updateCloseable(this.key, newValue); 3109 } 3110 if (name==='hide'&&this.parentNode) { 3111 this.parentNode.updateCloseable && this.parentNode.updateHide(this.key, newValue); 3112 } 3113 } 3114 } 3115 } 3116 if (!customElements.get('lit-tabpane')) { 3117 customElements.define('lit-tabpane', LitTabpane); 3118 } 3119 3120 class LitTabs extends HTMLElement { 3121 static get observedAttributes() { 3122 //mode = flat(default) | card 3123 //position = top | top-left | top-center | top-right | left | left-top | left-center | left-bottom | right | bottom 3124 return ['activekey', 'mode', 'position']; 3125 } 3126 3127 constructor() { 3128 super(); 3129 const shadowRoot = this.attachShadow({mode: 'open'}); 3130 shadowRoot.innerHTML = ` 3131 <style> 3132 :host{ 3133 display: block; 3134 text-align: unset; 3135 color: #252525; 3136 background-color: #fff; 3137 box-shadow: #00000033 0 0 10px ; 3138 /*padding: 10px;*/ 3139 /*margin-right: 10px;*/ 3140 } 3141 ::slotted(lit-tabpane) { 3142 box-sizing:border-box; 3143 width:100%; 3144 height:100%; 3145 /*padding:10px;*/ 3146 flex-shrink:0; 3147 overflow:auto; 3148 } 3149 .nav-item{ 3150 display: inline-flex; 3151 justify-content: center; 3152 align-items: center; 3153 padding: 6px 0px 6px 12px; 3154 font-size: .9rem; 3155 font-weight: normal; 3156 cursor: pointer; 3157 transition: all 0.3s; 3158 flex-shrink: 0; 3159 } 3160 .nav-item lit-icon{ 3161 margin-right: 2px; 3162 font-size: inherit; 3163 } 3164 .nav-item:hover{ 3165 color: #42b983; 3166 } 3167 .nav-item[data-disabled]{ 3168 pointer-events: all; 3169 cursor: not-allowed; 3170 color: #bfbfbf; 3171 } 3172 .nav-item[data-selected]{ 3173 color: #42b983;; 3174 } 3175 .tab-content{ 3176 display: block; 3177 background-color: #fff; 3178 flex:1; 3179 } 3180 3181 /* 3182 * top top-left top-center top-right 3183 */ 3184 :host(:not([position])) .nav-root, 3185 :host([position^='top']) .nav-root{ 3186 display: flex; 3187 position: relative; 3188 justify-content: center; 3189 align-items: center; 3190 } 3191 :host(:not([mode]):not([position])) .tab-line,/*移动的线条*/ 3192 :host([mode='flat'][position^='top']) .tab-line{ 3193 position:absolute; 3194 bottom: 2px; 3195 background-color: #42b983; 3196 height: 2px; 3197 transform: translateY(100%); 3198 transition: all 0.3s; 3199 } 3200 3201 :host(:not([position])) .tab-nav-container, 3202 :host([position^='top']) .tab-nav-container{ 3203 display: flex; 3204 position: relative; 3205 flex-direction: column; 3206 overflow-y: hidden; 3207 overflow-x: auto; 3208 overflow: -moz-scrollbars-none; 3209 -ms-overflow-style: none; 3210 transition: all 0.3s; 3211 flex: 1; 3212 /*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/ 3213 /*background-repeat: no-repeat;*/ 3214 /*background-size: 50px 100%, 15px 100%;*/ 3215 /*background-attachment: local,scroll,local,scroll;*/ 3216 /*border-bottom: #f0f0f0 1px solid;*/ 3217 } 3218 :host([position='top']) .tab-nav, 3219 :host([position='top-left']) .tab-nav{ 3220 display: flex; 3221 position: relative; 3222 justify-content: flex-start; 3223 } 3224 :host([position='top-center']) .tab-nav{ 3225 display: flex; 3226 justify-content: center; 3227 } 3228 :host([position='top-right']) .tab-nav{ 3229 display: flex; 3230 justify-content: flex-end; 3231 } 3232 3233 :host([position^='top'][mode='card']) .nav-item{ 3234 border-top: 1px solid #f0f0f0; 3235 border-left: 1px solid #f0f0f0; 3236 border-right: 1px solid #f0f0f0; 3237 border-bottom: 1px solid #f0f0f0; 3238 bottom: 0px; 3239 margin-right: 2px; 3240 position: relative; 3241 } 3242 :host([position^='top']) .tab-nav-bg-line{ 3243 position: absolute;bottom: 0;height: 1px;background-color: #f0f0f0;width: 100% 3244 } 3245 :host([position^='top'][mode='card']) .nav-item:not([data-selected]) { 3246 background-color: #f6f6f6; 3247 border-bottom: 1px solid #f0f0f0; 3248 } 3249 :host([position^='top'][mode='card']) .nav-item[data-selected]{ 3250 background-color: #ffffff; 3251 border-bottom: 1px solid #fff; 3252 bottom: 0px; 3253 } 3254 /* 3255 bottom bottom-left bottom-center bottom-right 3256 */ 3257 :host([position^='bottom']) .tab{ 3258 display: flex; 3259 flex-direction: column-reverse; 3260 } 3261 :host([mode='flat'][position^='bottom']) .tab-line{ 3262 position:absolute; 3263 top: -3px; 3264 background-color: #42b983; 3265 height: 2px; 3266 transform: translateY(-100%); 3267 transition: all 0.3s; 3268 } 3269 :host([position^='bottom']) .tab-nav-container{ 3270 display: flex; 3271 position: relative; 3272 flex-direction: column; 3273 overflow-x: auto; 3274 overflow-y: visible; 3275 overflow: -moz-scrollbars-none; 3276 -ms-overflow-style: none; 3277 transition: all 0.3s; 3278 flex: 1; 3279 /*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/ 3280 /*background-repeat: no-repeat;*/ 3281 /*background-size: 50px 100%, 15px 100%;*/ 3282 /*background-attachment: local,scroll,local,scroll;*/ 3283 border-top: #f0f0f0 1px solid; 3284 } 3285 :host([position^='bottom']) .nav-root{ 3286 display: flex; 3287 justify-content: center; 3288 align-items: center; 3289 } 3290 :host([position='bottom']) .tab-nav, 3291 :host([position='bottom-left']) .tab-nav{ 3292 display: flex; 3293 position: relative; 3294 justify-content: flex-start; 3295 } 3296 :host([position='bottom-center']) .tab-nav{ 3297 display: flex; 3298 justify-content: center; 3299 } 3300 :host([position='bottom-right']) .tab-nav{ 3301 display: flex; 3302 justify-content: flex-end; 3303 } 3304 :host([position^='bottom'][mode='card']) .nav-item{ 3305 border-top: 1px solid #ffffff; 3306 border-left: 1px solid #f0f0f0; 3307 border-right: 1px solid #f0f0f0; 3308 border-bottom: 1px solid #f0f0f0; 3309 top: -1px; 3310 margin-right: 2px; 3311 position: relative; 3312 } 3313 :host([position^='bottom']) .tab-nav-bg-line{ 3314 position: absolute;top: 0;height: 1px;background-color: #f0f0f0;width: 100% 3315 } 3316 :host([position^='bottom'][mode='card']) .nav-item:not([data-selected]) { 3317 background-color: #f5f5f5; 3318 border-top: 1px solid #f0f0f0; 3319 } 3320 :host([position^='bottom'][mode='card']) .nav-item[data-selected]{ 3321 background-color: #ffffff; 3322 border-top: 1px solid #fff; 3323 top: -1px; 3324 } 3325 /* 3326 left left-top left-center left-bottom 3327 */ 3328 :host([position^='left']) .tab{ 3329 display: flex; 3330 flex-direction: row; 3331 } 3332 :host([mode='flat'][position^='left']) .tab-line{ 3333 position:absolute; 3334 right: 1px; 3335 background-color: #42b983; 3336 width: 3px; 3337 transform: translateX(100%); 3338 transition: all 0.3s; 3339 } 3340 :host([position^='left']) .tab-nav-container{ 3341 display: flex; 3342 position: relative; 3343 flex-direction: row; 3344 overflow-x: auto; 3345 overflow-y: visible; 3346 overflow: -moz-scrollbars-none; 3347 -ms-overflow-style: none; 3348 transition: all 0.3s; 3349 flex: 1; 3350 /*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/ 3351 /*background-repeat: no-repeat;*/ 3352 /*background-size: 50px 100%, 15px 100%;*/ 3353 /*background-attachment: local,scroll,local,scroll;*/ 3354 border-right: #f0f0f0 1px solid; 3355 } 3356 :host([position^='left']) .nav-root{ 3357 display: flex; 3358 flex-direction: column; 3359 justify-content: center; 3360 align-items: center; 3361 } 3362 :host([position='left']) .tab-nav, 3363 :host([position='left-top']) .tab-nav{ 3364 display: flex; 3365 position: relative; 3366 flex-direction: column; 3367 justify-content: flex-start; 3368 } 3369 :host([position='left-center']) .tab-nav{ 3370 display: flex; 3371 position: relative; 3372 flex-direction: column; 3373 justify-content: center; 3374 } 3375 :host([position='left-bottom']) .tab-nav{ 3376 display: flex; 3377 position: relative; 3378 flex-direction: column; 3379 justify-content: flex-end; 3380 } 3381 :host([position^='left'][mode='card']) .nav-item{ 3382 border-top: 1px solid #f0f0f0; 3383 border-left: 1px solid #f0f0f0; 3384 border-right: 1px solid #ffffff; 3385 border-bottom: 1px solid #f0f0f0; 3386 right: -1px; 3387 margin-bottom: 2px; 3388 position: relative; 3389 } 3390 :host([position^='left']) .tab-nav-bg-line{ 3391 position: absolute;right: 0;width: 1px;background-color: #f0f0f0;width: 100% 3392 } 3393 :host([position^='left'][mode='card']) .nav-item:not([data-selected]) { 3394 background-color: #f5f5f5; 3395 border-right: 1px solid #f0f0f0; 3396 } 3397 :host([position^='left'][mode='card']) .nav-item[data-selected]{ 3398 background-color: #ffffff; 3399 border-bottom: 1px solid #fff; 3400 right: -1px; 3401 } 3402 /* 3403 right right-top right-center right-bottom 3404 */ 3405 :host([position^='right']) .tab{ 3406 display: flex; 3407 flex-direction: row-reverse; 3408 } 3409 :host([mode='flat'][position^='right']) .tab-line{ 3410 position:absolute; 3411 left: 1px; 3412 background-color: #42b983; 3413 width: 3px; 3414 transform: translateX(-100%); 3415 transition: all 0.3s; 3416 } 3417 :host([position^='right']) .tab-nav-container{ 3418 display: flex; 3419 position: relative; 3420 flex-direction: row-reverse; 3421 overflow-x: auto; 3422 overflow-y: visible; 3423 overflow: -moz-scrollbars-none; 3424 -ms-overflow-style: none; 3425 transition: all 0.3s; 3426 flex: 1; 3427 /*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/ 3428 /*background-repeat: no-repeat;*/ 3429 /*background-size: 50px 100%, 15px 100%;*/ 3430 /*background-attachment: local,scroll,local,scroll;*/ 3431 border-left: #f0f0f0 1px solid; 3432 } 3433 :host([position^='right']) .nav-root{ 3434 display: flex; 3435 flex-direction: column; 3436 justify-content: center; 3437 align-items: center; 3438 } 3439 :host([position='right']) .tab-nav, 3440 :host([position='right-top']) .tab-nav{ 3441 display: flex; 3442 position: relative; 3443 flex-direction: column; 3444 justify-content: flex-start; 3445 } 3446 :host([position='right-center']) .tab-nav{ 3447 display: flex; 3448 position: relative; 3449 flex-direction: column; 3450 justify-content: center; 3451 } 3452 :host([position='right-bottom']) .tab-nav{ 3453 display: flex; 3454 position: relative; 3455 flex-direction: column; 3456 justify-content: flex-end; 3457 } 3458 :host([position^='right'][mode='card']) .nav-item{ 3459 border-top: 1px solid #f0f0f0; 3460 border-left: 1px solid #ffffff; 3461 border-right: 1px solid #f0f0f0; 3462 border-bottom: 1px solid #f0f0f0; 3463 left: -1px; 3464 margin-top: 2px; 3465 position: relative; 3466 } 3467 :host([position^='right']) .tab-nav-bg-line{ 3468 position: absolute;left: 0;width: 1px;background-color: #f0f0f0;width: 100% 3469 } 3470 :host([position^='right'][mode='card']) .nav-item:not([data-selected]) { 3471 background-color: #f5f5f5; 3472 border-left: 1px solid #f0f0f0; 3473 } 3474 :host([position^='right'][mode='card']) .nav-item[data-selected]{ 3475 background-color: #ffffff; 3476 left: -1px; 3477 } 3478 3479 3480 .tab-nav-container::-webkit-scrollbar { 3481 display: none; 3482 } 3483 3484 3485 /*关闭的图标*/ 3486 .close-icon:hover{ 3487 color: #000; 3488 } 3489 .nav-item[data-closeable] .close-icon{ 3490 display: block; 3491 padding: 5px 5px 5px 5px; 3492 color: #999; 3493 } 3494 .nav-item[data-closeable] .no-close-icon{ 3495 display: none; 3496 } 3497 .nav-item:not([data-closeable]) .no-close-icon{ 3498 display: block; 3499 } 3500 .nav-item:not([data-closeable]) .close-icon{ 3501 display: none; 3502 } 3503 .nav-item:not([data-hide]) { 3504 display: block; 3505 } 3506 .nav-item[data-hide]{ 3507 display: none; 3508 } 3509 3510 </style> 3511 <style id="filter"></style> 3512 <div class="tab"> 3513 <div class="nav-root"> 3514 <slot name="left" style="flex:1"></slot> 3515 <div class="tab-nav-container" > 3516 <div class="tab-nav-bg-line"></div> 3517 <div class="tab-nav" id="nav"></div> 3518 <div class="tab-line" id="tab-line"></div> 3519 </div> 3520 <slot name="right" style="flex:1"></slot> 3521 </div> 3522 <div class="tab-content"> 3523 <slot id="slot">NEED CONTENT</slot> 3524 </div> 3525 </div> 3526 `; 3527 } 3528 3529 get position() { 3530 return this.getAttribute('position') || 'top'; 3531 } 3532 3533 set position(value) { 3534 this.setAttribute('position', value); 3535 } 3536 3537 get mode() { 3538 return this.getAttribute('mode') || 'flat'; 3539 } 3540 3541 set mode(value) { 3542 this.setAttribute('mode', value); 3543 } 3544 3545 get activekey() { 3546 return this.getAttribute('activekey'); 3547 } 3548 3549 set activekey(value) { 3550 this.setAttribute('activekey', value); 3551 } 3552 3553 updateLabel(key, value) { 3554 if (this.nav) { 3555 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 3556 if (item) { 3557 item.querySelector('span').innerHTML=value; 3558 this.initTabPos(); 3559 } 3560 } 3561 } 3562 3563 updateDisabled(key, value) { 3564 if (this.nav) { 3565 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 3566 if (item) { 3567 if (value) { 3568 item.setAttribute('data-disabled',''); 3569 } else { 3570 item.removeAttribute('data-disabled'); 3571 } 3572 this.initTabPos() 3573 } 3574 } 3575 } 3576 updateCloseable(key,value) { 3577 if (this.nav) { 3578 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 3579 if (item) { 3580 if (value) { 3581 item.setAttribute('data-closeable',''); 3582 } else { 3583 item.removeAttribute('data-closeable'); 3584 } 3585 this.initTabPos(); 3586 } 3587 } 3588 } 3589 updateHide(key,value) { 3590 if (this.nav) { 3591 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 3592 if (item) { 3593 if (value) { 3594 item.setAttribute('data-hide',''); 3595 } else { 3596 item.removeAttribute('data-hide'); 3597 } 3598 this.initTabPos(); 3599 } 3600 } 3601 } 3602 3603 initTabPos() { 3604 const items = this.nav.querySelectorAll('.nav-item'); 3605 Array.from(items).forEach((a, index) => { 3606 this.tabPos[a.dataset.key] = { 3607 index: index, 3608 width: a.offsetWidth, 3609 height: a.offsetHeight, 3610 left: a.offsetLeft, 3611 top: a.offsetTop, 3612 label: a.textContent 3613 }; 3614 }); 3615 if (this.activekey) { 3616 if (this.position.startsWith('left')) { 3617 this.line.style = 3618 `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${this.tabPos[this.activekey].top}px)`; 3619 } else if (this.position.startsWith('top')) { 3620 if (this.tabPos[this.activekey]) { 3621 this.line.style = 3622 `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`; 3623 } 3624 } else if (this.position.startsWith('right')) { 3625 this.line.style = 3626 `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${this.tabPos[this.activekey].top}px)`; 3627 } else if (this.position.startsWith('bottom')) { 3628 this.line.style = 3629 `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`; 3630 } 3631 } 3632 } 3633 3634 // 当 custom element首次被插入文档DOM时,被调用。 3635 connectedCallback() { 3636 let that = this; 3637 this.tabPos = {}; 3638 this.nav = this.shadowRoot.querySelector('#nav'); 3639 this.line = this.shadowRoot.querySelector('#tab-line'); 3640 this.slots = this.shadowRoot.querySelector('#slot'); 3641 this.slots.addEventListener('slotchange', () => { 3642 const elements = this.slots.assignedElements(); 3643 let panes = this.querySelectorAll('lit-tabpane'); 3644 if (this.activekey) { 3645 panes.forEach(a => { 3646 if (a.key === this.activekey) { 3647 a.style.display = 'block'; 3648 } else { 3649 a.style.display = 'none'; 3650 } 3651 }) 3652 } else { 3653 panes.forEach((a, index) => { 3654 if (index === 0) { 3655 a.style.display = 'block'; 3656 this.activekey = a.key; 3657 } else { 3658 a.style.display = 'none'; 3659 } 3660 }) 3661 } 3662 3663 let navHtml = ''; 3664 elements.forEach(a => { 3665 if (a.disabled) { 3666 navHtml += `<div class='nav-item' data-key='${a.key}'' data-disabled ${a.closeable?'data-closeable':''} ${a.hide?'data-hide':''}> 3667 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 3668 <span>${a.tab}</span> 3669 <lit-icon class='close-icon' name='close' size='12'></lit-icon><div class='no-close-icon' style='margin-right: 12px'></div> 3670 </div>`; 3671 } else { 3672 if (a.key === this.activekey) { 3673 navHtml += `<div class='nav-item' data-key='${a.key}'' data-selected ${a.closeable?'data-closeable':''} ${a.hide?'data-hide':''}> 3674 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 3675 <span>${a.tab}</span> 3676 <lit-icon class='close-icon' name='close' size='12'></lit-icon><div class='no-close-icon' style='margin-right: 12px'></div> 3677 </div>`; 3678 } else { 3679 navHtml += `<div class='nav-item' data-key='${a.key}' ${a.closeable?'data-closeable':''} ${a.hide?'data-hide':''}> 3680 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 3681 <span>${a.tab}</span> 3682 <lit-icon class='close-icon' name='close' size='12'></lit-icon><div class='no-close-icon' style='margin-right: 12px'></div> 3683 </div>`; 3684 } 3685 3686 } 3687 }); 3688 this.nav.innerHTML = navHtml; 3689 this.initTabPos(); 3690 this.nav.querySelectorAll('.close-icon').forEach(a => { 3691 a.onclick = (e) => { 3692 e.stopPropagation(); 3693 const closeKey = e.target.parentElement.dataset.key; 3694 console.log(closeKey); 3695 console.log(e.target.parentElement.parentElement); 3696 this.nav.removeChild(e.target.parentElement); 3697 let elements = this.slots.assignedElements(); 3698 let closeElement = elements.filter(a => a.key === closeKey)[0]; 3699 closeElement.parentElement.removeChild(closeElement); 3700 if (closeElement.style.display !== 'none') { 3701 elements = this.slots.assignedElements(); 3702 let elArr = elements.filter(a => !a.hasAttribute('disabled')); 3703 if (elArr.length > 0) { 3704 elArr[0].style.display = 'block'; 3705 this.activekey = elArr[0].key; 3706 } 3707 } 3708 } 3709 }); 3710 }); 3711 this.nav.onclick = (e) => { 3712 if (e.target.closest('div').hasAttribute('data-disabled')) return; 3713 let key = e.target.closest('div').dataset.key; 3714 this.activeByKey(key); 3715 let label = e.target.closest('div').querySelector('span').textContent; 3716 this.dispatchEvent(new CustomEvent('onTabClick',{detail:{key:key,tab:label}})); 3717 }; 3718 } 3719 set onTabClick(fn) { 3720 this.addEventListener('onTabClick', fn); 3721 } 3722 activeByKey(key) { 3723 if (key === null || key === undefined) return; // 如果没有key 不做相应 3724 this.nav.querySelectorAll('.nav-item').forEach(a => { 3725 if (a.getAttribute('data-key') === key) { 3726 a.setAttribute('data-selected', 'true'); 3727 } else { 3728 a.removeAttribute('data-selected'); 3729 } 3730 }) 3731 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 3732 let panes = this.querySelectorAll('lit-tabpane'); 3733 panes.forEach(a => { 3734 if (a.key === key) { 3735 a.style.display = 'block'; 3736 this.activekey = a.key; 3737 this.initTabPos() 3738 } else { 3739 a.style.display = 'none'; 3740 } 3741 }) 3742 } 3743 /*激活选中 key 对应的 pane 成功返回true,没有找到key对应的pane 返回false*/ 3744 activePane(key) { 3745 if (key === null || key === undefined) return false; 3746 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 3747 if (tbp) { 3748 this.activeByKey(key); 3749 return true; 3750 } else { 3751 return false; 3752 } 3753 } 3754 // 当 custom element从文档DOM中删除时,被调用。 3755 disconnectedCallback() { 3756 3757 } 3758 3759 // 当 custom element被移动到新的文档时,被调用。 3760 adoptedCallback() { 3761 console.log('Custom square element moved to new page.'); 3762 } 3763 3764 // 当 custom element增加、删除、修改自身属性时,被调用。 3765 attributeChangedCallback(name, oldValue, newValue) { 3766 if (name==='activekey'&&this.nav&&oldValue!==newValue) { 3767 this.activeByKey(newValue); 3768 } 3769 } 3770 } 3771 if (!customElements.get('lit-tabs')) { 3772 customElements.define('lit-tabs', LitTabs); 3773 } 3774 3775 class AppChartFlame extends HTMLElement { 3776 draw; 3777 drawC; 3778 rowHeight = 17; 3779 3780 static get observedAttributes() { 3781 return []; 3782 } 3783 3784 constructor() { 3785 super(); 3786 const shadowRoot = this.attachShadow({mode: 'open'}); 3787 shadowRoot.innerHTML = ` 3788 <style> 3789 :host{ 3790 font-size:inherit; 3791 display:inline-flex; 3792 align-items: center; 3793 justify-content:center; 3794 height: 100%; 3795 width: 100%; 3796 margin-bottom: 40px; 3797 } 3798 canvas { border: 1px solid #e9e9e9; } 3799 #title{ 3800 font-weight: bold; 3801 height: auto; 3802 } 3803 #title span{ 3804 color: gray; 3805 } 3806 #funcNameSpan{ 3807 display: inline-block; 3808 border: 1px solid #e9e9e9; 3809 box-sizing: border-box; 3810 border-radius: 2px; 3811 padding: 2px 8px; 3812 background: #fff; 3813 color: gray; 3814 flex: 3; 3815 margin-left: 10px; 3816 } 3817 #percentSpan{ 3818 display: inline-block; 3819 border: 1px solid #e9e9e9; 3820 margin-left: 10px; 3821 box-sizing: border-box; 3822 border-radius: 2px; 3823 padding: 2px 8px; 3824 background: #fff; 3825 min-width: 160px; 3826 max-width: 160px; 3827 margin-left: 10px; 3828 width: 100px; 3829 height: 30px; 3830 color: gray; 3831 } 3832 #history{ 3833 display: none; 3834 border: 1px solid #42b983; 3835 box-sizing: border-box; 3836 border-radius: 2px; 3837 padding: 2px 8px; 3838 background: #fff; 3839 width: 100px; 3840 margin-left: 10px; 3841 height: 30px; 3842 cursor: pointer; 3843 user-select: none; 3844 } 3845 #history:hover{ 3846 background: #42b983; 3847 color: #fff; 3848 } 3849 #searchInput{ 3850 height: 30px; 3851 margin-left: 10px; 3852 margin-right: 10px; 3853 flex: 1; 3854 } 3855 </style> 3856 <div style="position: relative;width: 100%"> 3857 <div id="title">Process <span id="pid"></span> <span id="processName"></span> Thread <span id="tid"></span> <span id="threadName"></span><span id="sample"></span></div> 3858 <canvas id="panel" title=""></canvas> 3859 <div id="controller" style="position: absolute;top: 32px;display: flex;width: 100%"> 3860 <span id="history">Zoom Out</span> 3861 <span id="funcNameSpan"></span> 3862 <span id="percentSpan"></span> 3863 <lit-input id="searchInput" placeholder="search" allow-clear>Search</lit-input> 3864 </div> 3865 </div> 3866 <slot></slot> 3867 ` 3868 } 3869 3870 connectedCallback() { 3871 this.history = []; 3872 this.panel = this.shadowRoot.getElementById('panel'); 3873 this.controller = this.shadowRoot.getElementById('controller'); 3874 this.funcNameSpan = this.shadowRoot.getElementById('funcNameSpan'); 3875 this.percentSpan = this.shadowRoot.getElementById('percentSpan'); 3876 this.historySpan = this.shadowRoot.getElementById('history'); 3877 this.searchInput = this.shadowRoot.getElementById('searchInput'); 3878 this.pid = this.shadowRoot.getElementById('pid'); 3879 this.tid = this.shadowRoot.getElementById('tid'); 3880 this.processName = this.shadowRoot.getElementById('processName'); 3881 this.threadName = this.shadowRoot.getElementById('threadName'); 3882 this.sample = this.shadowRoot.getElementById('sample'); 3883 this.titleDiv = this.shadowRoot.getElementById('title'); 3884 this.context = this.panel.getContext('2d'); 3885 this.panel.width = this.shadowRoot.host.clientWidth; 3886 this.panel.height = this.shadowRoot.host.clientHeight; 3887 this.historySpan.onclick = e => { 3888 if (this.history.length > 2) { 3889 this.history.pop(); 3890 this.zoomOut(this.history[this.history.length - 1]); 3891 } else if (this.history.length === 2) { 3892 this.history.pop(); 3893 this.zoomOut(this.history[this.history.length - 1]); 3894 this.historySpan.style.display = 'none'; 3895 } else { 3896 this.historySpan.style.display = 'none'; 3897 } 3898 }; 3899 this.searchInput.addEventListener('onPressEnter', (e) => { 3900 this.keyword = e.currentTarget.value; 3901 requestAnimationFrame(this.draw); 3902 }); 3903 this.searchInput.addEventListener('onClear', e => { 3904 this.keyword = null; 3905 requestAnimationFrame(this.draw); 3906 }); 3907 this.panel.onmouseover = (e) => { 3908 this.mouseState = 'mouseOver'; 3909 }; 3910 this.panel.onmouseleave = e => { 3911 this.mouseState = 'mouseLeave'; 3912 this.mouseX = 0; 3913 this.mouseY = 0; 3914 requestAnimationFrame(this.draw) 3915 }; 3916 this.panel.onmousemove = e => { 3917 const pos = e.currentTarget.getBoundingClientRect(); 3918 this.mouseX = e.clientX - pos.left; 3919 this.mouseY = e.clientY - pos.top; 3920 this.mouseState = 'mouseMove'; 3921 requestAnimationFrame(this.draw) 3922 }; 3923 this.panel.onmousedown = e => { 3924 this.mouseState = 'mouseDown'; 3925 }; 3926 this.panel.onmouseup = e => { 3927 this.mouseState = 'mouseUp'; 3928 const pos = e.currentTarget.getBoundingClientRect(); 3929 this.mouseX = e.clientX - pos.left; 3930 this.mouseY = e.clientY - pos.top; 3931 requestAnimationFrame(this.draw); 3932 }; 3933 } 3934 3935 set data(value) { 3936 this._data = value; 3937 this.type = value.type; 3938 this.reverse = value.reverse || false; 3939 if (value.CallOrder.symbol === -1) { 3940 this._c = value.CallOrder.callStack; 3941 } else { 3942 this._c = [value.CallOrder]; 3943 } 3944 this.history.push(this._c); 3945 this.eventCountAllProcess = data.recordSampleInfo[window.eventIndex].eventCount; 3946 if (value.pid) { 3947 this.eventCountCurrentProcess = 3948 data.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === value.pid)[0].eventCount; 3949 this.pid.textContent = value.pid; 3950 } 3951 if (value.tid) { 3952 this.eventCountCurrentThread = 3953 data.recordSampleInfo[window.eventIndex].processes.filter( 3954 it => it.pid === value.pid)[0].threads.filter(it => it.tid === value.tid)[0].eventCount; 3955 this.tid.textContent = value.tid; 3956 } 3957 if (value.sampleCount) { 3958 this.sample.textContent = ' (Samples: ' + value.sampleCount + ')'; 3959 } 3960 if (value.processName) { 3961 this.processName.textContent = value.processName ? `(${value.processName})` : ''; 3962 } 3963 if (value.threadName) { 3964 this.threadName.textContent = value.threadName ? `(${value.threadName})` : ''; 3965 } 3966 if (value.funcName) { 3967 this.titleDiv.innerHTML = `${value.funcName}`; 3968 this.controller.style.top = `${this.titleDiv.clientHeight + 10}px`; 3969 } 3970 this.maxDepth = this.getMaxDepth(this.data.CallOrder.callStack) + 5; // 设置最大层级 3971 this.sumCount = this.data.CallOrder.subEvents; // 设置根节点的 sumCount 3972 this.panel.height = this.maxDepth * this.rowHeight; 3973 this.panel.width = this.shadowRoot.host.clientWidth; 3974 this.makeHighRes(this.panel); 3975 requestAnimationFrame(this.draw); 3976 } 3977 3978 get data() { 3979 return this._data; 3980 } 3981 3982 set c(value) { 3983 this.historySpan.style.display = 'block'; 3984 this._c = value; 3985 this.history.push(this._c); 3986 // 下面代码实现 canvas 随内容高度变化 3987 requestAnimationFrame(this.draw); 3988 } 3989 3990 get c() { 3991 return this._c; 3992 } 3993 3994 zoomOut(value) { 3995 this._c = value; 3996 // 下面代码实现 canvas 随内容高度变化 3997 requestAnimationFrame(this.draw); 3998 } 3999 4000 makeHighRes(canvas) { 4001 let ctx = canvas.getContext('2d'); 4002 let dpr = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1; 4003 let oldWidth = canvas.width; 4004 let oldHeight = canvas.height; 4005 canvas.width = Math.round(oldWidth * dpr); 4006 canvas.height = Math.round(oldHeight * dpr); 4007 canvas.style.width = oldWidth + 'px'; 4008 canvas.style.height = oldHeight + 'px'; 4009 ctx.scale(dpr, dpr); 4010 this.context = ctx; 4011 return ctx; 4012 } 4013 4014 draw = () => { 4015 let ctx = this.context; 4016 let grad = ctx.createLinearGradient(0, 0, 0, this.panel.height / 2); // 创建一个渐变色线性对象 4017 grad.addColorStop(0, '#eeeeee'); // 定义渐变色颜色 4018 grad.addColorStop(1, '#efefb1'); 4019 ctx.fillStyle = grad; // 设置fillStyle为当前的渐变对象 4020 ctx.fillRect(0, 0, this.panel.width, this.panel.height); 4021 if (this.data) { 4022 if (this.reverse) { 4023 this.drawCReverse( 4024 0, 4025 this.c, 4026 1, 4027 { 4028 x: 0, 4029 y: this.rowHeight * 4, 4030 w: this.panel.clientWidth, 4031 h: this.rowHeight 4032 }); 4033 } else { 4034 this.drawC( 4035 0, 4036 this.c, 4037 1, 4038 { 4039 x: 0, 4040 y: this.panel.clientHeight - this.rowHeight, 4041 w: this.panel.clientWidth, 4042 h: this.rowHeight 4043 }); 4044 } 4045 } 4046 }; 4047 4048 getCount(c) { 4049 let count;// 鼠标hover展示的百分比 4050 count = c.subEvents; 4051 count = `${count}`; 4052 return count; 4053 } 4054 4055 getStatistics(c) { 4056 let statistics;// 鼠标hover展示的百分比 4057 switch (this.type) { 4058 case 1: // current thread 4059 statistics = c.subEvents * 100 / this.eventCountCurrentThread; 4060 statistics = statistics.toFixed(2); 4061 statistics = `${statistics}%`; 4062 break; 4063 case 2: // current process 4064 statistics = c.subEvents * 100 / this.eventCountCurrentProcess; 4065 statistics = statistics.toFixed(2); 4066 statistics = `${statistics}%`; 4067 break; 4068 case 3: // all process 4069 statistics = c.subEvents * 100 / this.eventCountAllProcess; 4070 statistics = statistics.toFixed(2); 4071 statistics = `${statistics}%`; 4072 break; 4073 case 4: // event count 4074 statistics = c.subEvents; 4075 statistics = `${statistics}`; 4076 break; 4077 case 5: // event count in milliseconds 4078 statistics = c.subEvents / 1000000; 4079 statistics = statistics.toFixed(3); 4080 statistics = `${statistics} ms`; 4081 break; 4082 default: //current thread 4083 statistics = c.subEvents * 100 / this.eventCountCurrentThread; 4084 statistics = statistics.toFixed(2); 4085 statistics = `${statistics}%`; 4086 break; 4087 } 4088 return statistics; 4089 } 4090 4091 // HTML反转义 4092 htmlDecode(text) { 4093 let temp = document.createElement('div'); 4094 temp.innerHTML = text; 4095 let output = temp.innerText || temp.textContent; 4096 temp = null; 4097 return output; 4098 } 4099 4100 getFunctionName(f) { 4101 let funName = ''; 4102 if (data.SymbolMap[f]) { 4103 funName = data.SymbolMap[f].symbol; 4104 } else { 4105 let f = c[i].symbol; 4106 console.log(`processId:${this.pid.textContent} processName:${this.processName.textContent} 4107 threadId:${this.tid.textContent} threadName:${this.threadName.textContent}`, 4108 c[i], "SymbolMap中没有对应的值"); 4109 } 4110 return this.htmlDecode(funName); 4111 } 4112 4113 getColor(percent2, funName) { 4114 let heatColor; 4115 if (this.keyword && this.keyword.length > 0 && funName.indexOf(this.keyword) !== -1) { 4116 heatColor = {r: 0x66, g: 0xad, b: 0xff}; 4117 } else { 4118 heatColor = funName.includes("url:") ? this.getJsHeatColor(percent2) : this.getHeatColor(percent2); 4119 } 4120 return heatColor; 4121 } 4122 4123 drawCReverse = (parentEvents, c, dept, rect) => { 4124 let ctx = this.context; 4125 let offset = 0; 4126 if (parentEvents === 0) { 4127 parentEvents = c.reduce((acc, cur) => acc + cur.subEvents, 0); 4128 } 4129 for (let i = 0; i < c.length; i++) { 4130 let funName = this.getFunctionName(c[i].symbol); 4131 let funcId = c[i].symbol; 4132 let percent = c[i].subEvents * 100 / parentEvents; 4133 let percent2 = c[i].subEvents * 100 / this.sumCount; 4134 if (percent2 < 0.1) continue // 过滤掉 百分比为0.1一下的节点 4135 let heatColor = this.getColor(percent2, funName); 4136 let w = rect.w * (percent / 100.0); 4137 if (w < 1) { 4138 w = 1; 4139 } 4140 let _x = rect.x + offset; 4141 // 绘制填充矩形 4142 ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`; 4143 ctx.fillRect(_x, rect.y + 2, w, rect.h - 2); 4144 // 绘制文本 4145 ctx.fillStyle = 'rgba(0,0,0,1)'; 4146 let txtWidth = ctx.measureText(funName).width;// 文本长度 4147 let chartWidth = txtWidth / funName.length;// 每个字符长度 4148 let number = (w - 6) / chartWidth;// 可以显示多少字符 4149 if (number >= 4 && number < funName.length - 3) { 4150 ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6); 4151 } else if (number >= 4) { 4152 ctx.fillText(funName, _x + 3, rect.y + 13, w - 6); 4153 } 4154 let _rect = { 4155 x: _x, y: rect.y, w: w, h: rect.h 4156 }; 4157 c[i].rect = _rect; 4158 4159 if (this.mouseX > _x && this.mouseX < _x + w && this.mouseY > rect.h * 3 + (dept) * rect.h && 4160 this.mouseY < rect.h * 3 + (dept + 1) * rect.h) { 4161 if (this.mouseState === 'mouseMove') { 4162 // 绘制边框矩形 4163 ctx.lineWidth = 2; 4164 ctx.strokeStyle = `#000000`; 4165 ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h); 4166 let statisticNum = this.getStatistics(c[i]); 4167 let count = this.getCount(c[i]); 4168 this.funcNameSpan.textContent = funName; 4169 if (this.type === 1 || this.type === 2 || this.type === 3) { 4170 this.panel.title = funName + ': [' + count + ' ' + statisticNum + ']'; 4171 } else { 4172 this.panel.title = funName + ': [' + statisticNum + ']'; 4173 } 4174 this.percentSpan.textContent = statisticNum; 4175 } else { 4176 if (this.mouseState === 'mouseUp') { 4177 this.mouseState = null; 4178 if (!this.compareNodes(this.c, [c[i]])) { 4179 this.c = [c[i]]; 4180 } 4181 } 4182 } 4183 } else { 4184 ctx.lineWidth = 1; 4185 } 4186 offset += w; 4187 // 递归绘制子节点 4188 if (c[i].callStack && c[i].callStack.length > 0) { 4189 _rect.y = _rect.y + _rect.h; 4190 this.drawCReverse(c[i].subEvents, [i].callStack, dept + 1, _rect); 4191 } 4192 } 4193 } 4194 drawC = (parentEvents, c, dept, rect) => { 4195 let ctx = this.context; 4196 let offset = 0; 4197 if (parentEvents === 0) { 4198 parentEvents = c.reduce((acc, cur) => acc + cur.subEvents, 0); 4199 } 4200 for (let i = 0; i < c.length; i++) { 4201 let funName = this.getFunctionName(c[i].symbol); 4202 let funcId = c[i].symbol; 4203 let percent = c[i].subEvents * 100 / parentEvents;// sumCount; 4204 let percent2 = c[i].subEvents * 100 / this.sumCount; 4205 if (percent2 < 0.1) continue // 过滤掉 百分比为0.1一下的节点 4206 let heatColor = this.getColor(percent2, funName); 4207 let w = rect.w * (percent / 100.0); 4208 if (w < 1) { 4209 w = 1; 4210 } 4211 let _x = rect.x + offset; 4212 // 绘制填充矩形 4213 ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`; 4214 ctx.fillRect(_x, rect.y + 2, w, rect.h - 2); 4215 // 绘制文本 4216 ctx.fillStyle = 'rgba(0,0,0,1)'; 4217 let txtWidth = ctx.measureText(funName).width;// 文本长度 4218 let chartWidth = txtWidth / funName.length;// 每个字符长度 4219 let number = (w - 6) / chartWidth;// 可以显示多少字符 4220 if (number >= 4 && number < funName.length - 3) { 4221 ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6); 4222 } else if (number >= 4) { 4223 ctx.fillText(funName, _x + 3, rect.y + 13, w - 6); 4224 } 4225 let _rect = { 4226 x: _x, y: rect.y, w: w, h: rect.h 4227 }; 4228 c[i].rect = _rect; 4229 4230 if (this.mouseX > _x && this.mouseX < _x + w && this.mouseY > (this.maxDepth - dept) * rect.h && 4231 this.mouseY < (this.maxDepth - dept + 1) * rect.h) { 4232 if (this.mouseState === 'mouseMove') { 4233 // 绘制边框矩形 4234 ctx.lineWidth = 2; 4235 ctx.strokeStyle = `#000000`; 4236 ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h); 4237 let statisticNum = this.getStatistics(c[i]); 4238 let count = this.getCount(c[i]); 4239 this.funcNameSpan.textContent = funName; 4240 if (this.type === 1 || this.type === 2 || this.type === 3) { 4241 this.panel.title = funName + ': [' + count + ' ' + statisticNum + ']'; 4242 } else { 4243 this.panel.title = funName + ': [' + statisticNum + ']'; 4244 } 4245 this.percentSpan.textContent = statisticNum; 4246 } else { 4247 if (this.mouseState === 'mouseUp') { 4248 this.mouseState = null; 4249 if (!this.compareNodes(this.c, [c[i]])) { 4250 this.c = [c[i]]; 4251 } 4252 } 4253 } 4254 } else { 4255 ctx.lineWidth = 1; 4256 } 4257 offset += w; 4258 // 递归绘制子节点 4259 if (c[i].callStack && c[i].callStack.length > 0) { 4260 _rect.y = _rect.y - _rect.h; 4261 this.drawC(c[i].subEvents, c[i].callStack, dept + 1, _rect); 4262 } 4263 } 4264 } 4265 4266 compareNode(na, nb) { 4267 let res = false; 4268 if (na.selfEvents === nb.selfEvents && na.subEvents === nb.subEvents && na.symbol === nb.symbol) { 4269 res = this.compareNodes(na.callStack || [], nb.callStack || []); 4270 } 4271 return res; 4272 } 4273 4274 compareNodes(a, b) { 4275 let res = false; 4276 if (a.length === b.length) { 4277 if (a.length === 0) { 4278 return true; 4279 } 4280 for (let i = 0; i < a.length; i++) { 4281 res = this.compareNode(a[i], b[i]); 4282 } 4283 } 4284 return res; 4285 } 4286 4287 getMaxDepth(nodes) { 4288 let isArray = Array.isArray(nodes); 4289 let sumCount; 4290 if (isArray) { 4291 sumCount = nodes.reduce((acc, cur) => acc + cur.subEvents, 0); 4292 } else { 4293 sumCount = nodes.subEvents; 4294 } 4295 let width = sumCount * 100.0 / this.sumCount; 4296 if (width < 0.1) { 4297 return 0; 4298 } 4299 let children = isArray ? this.splitChildrenForNodes(nodes) : nodes.callStack; 4300 let childDepth = 0; 4301 if (children) { 4302 for (let child of children) { 4303 childDepth = Math.max(childDepth, this.getMaxDepth(child)); 4304 } 4305 } 4306 return childDepth + 1; 4307 } 4308 4309 splitChildrenForNodes(nodes) { 4310 let map = new Map(); 4311 for (let node of nodes) { 4312 for (let child of node.callStack) { 4313 let subNodes = map.get(child.symbol); 4314 if (subNodes) { 4315 subNodes.push(child); 4316 } else { 4317 map.set(child.symbol, [child]); 4318 } 4319 } 4320 } 4321 let res = []; 4322 for (let subNodes of map.values()) { 4323 res.push(subNodes.length === 1 ? subNodes[0] : subNodes); 4324 } 4325 return res; 4326 } 4327 4328 getHeatColor(widthPercentage) { 4329 return { 4330 r: Math.floor(245 + 10 * (1 - widthPercentage * 0.01)), 4331 g: Math.floor(110 + 105 * (1 - widthPercentage * 0.01)), 4332 b: 100, 4333 }; 4334 } 4335 4336 getJsHeatColor(widthPercentage) { 4337 return { 4338 r: Math.floor(20 + 120 * (1 - widthPercentage * 0.01)), 4339 g: 200, 4340 b: Math.floor(0 + 120 * (1 - widthPercentage * 0.01)), 4341 }; 4342 } 4343 attributeChangedCallback(name, oldValue, newValue) { 4344 } 4345 } 4346 if (!customElements.get('app-chart-flame')) { 4347 customElements.define('app-chart-flame', AppChartFlame); 4348 } 4349 4350 class AppChartStatistics extends HTMLElement { 4351 static get observedAttributes() { 4352 return ['data']; 4353 } 4354 4355 constructor() { 4356 super(); 4357 const shadowRoot = this.attachShadow({mode: 'open'}); 4358 this.color = [ 4359 '#3391ff', // red 4360 '#ff9201', // green 4361 '#008078', // indigo 4362 '#0094c6', // orange 4363 '#ff7500', // light green 4364 '#2db3aa', // deep purple 4365 '#0076ff', // pink 4366 '#66adff', // purple 4367 '#73e6de', // blue 4368 '#535da6', // light blue 4369 '#ffab40', // lime 4370 '#38428c', // cyan 4371 '#7cdeff', // deep orange 4372 '#fbbf00', // blue gray 4373 '#2db4e2', // amber #ffc105 4374 '#ffd44a', // brown 4375 '#7a84cc', // teal 4376 '#ffe593' // yellow 0xffec3d 4377 ]; 4378 shadowRoot.innerHTML = ` 4379 <style> 4380 :host{ 4381 font-size:inherit; 4382 display:inline-flex; 4383 align-items: center; 4384 justify-content:center; 4385 padding: 0; 4386 margin: 0; 4387 width: 100%; 4388 } 4389 </style> 4390 <div style="display: flex;flex-direction: column;width: 100%;height: auto"> 4391 <lit-table id="tbl1" noheader style="height: auto;width: 100%;"> 4392 <lit-table-column title="key" data-index="key" width="200px" key="key" ></lit-table-column> 4393 <lit-table-column title="value" data-index="value" key="value"></lit-table-column> 4394 <lit-table-column title="time" data-index="time" key="time"></lit-table-column> 4395 </lit-table> 4396 <div id="back" style="display: none;cursor: pointer; 4397 border-radius: 5px;width: 35px;justify-content: center;text-align: center; 4398 padding: 7px 15px 7px 15px;border: 1px solid #aaa;color: #555">back</div> 4399 <lit-pie-chart id="chart" style="height: auto;width: 100%"></lit-pie-chart> 4400 </div> 4401 <slot></slot> 4402 `; 4403 } 4404 4405 set data(json) { 4406 if (json.recordSampleInfo && json.recordSampleInfo.length > 0) { 4407 this.eventInfo = json.recordSampleInfo[window.eventIndex]; 4408 } 4409 this.processNameMap = json.processNameMap; 4410 this.threadNameMap = json.threadNameMap; 4411 this.symbolsFileList = json.symbolsFileList; 4412 this.SymbolMap = json.SymbolMap; 4413 let rows = []; 4414 if (json.deviceTime) { 4415 rows.push({key: 'Device Time', value: json.deviceTime,time:''}); 4416 } 4417 if (json.deviceType) { 4418 rows.push({key: 'Device Type', value: json.deviceType,time:''}); 4419 } 4420 if (json.osVersion) { 4421 rows.push({key: 'OS Version', value: json.osVersion,time:''}); 4422 } 4423 rows.push({key: 'Script Version', value: '1.0.0.240604',time:''}); 4424 if (json.deviceCommandLine) { 4425 rows.push({key:'Record cmdline',value: json.deviceCommandLine,time:''}); 4426 } 4427 rows.push({key:'Total Samples',value: '' + json.totalRecordSamples,time:''}); 4428 if (this.eventInfo) { 4429 rows.push({key:'Event Type', 4430 value:this.eventInfo.eventConfigName,time:this.getSampleWeight(this.eventInfo.eventCount)}); 4431 this.initChartData(); 4432 } 4433 this.table.dataSource = rows; 4434 } 4435 4436 getSampleWeight(count) { 4437 if (this.eventInfo.eventConfigName.includes('task-clock') || 4438 this.eventInfo.eventConfigName.includes('cpu-clock')) { 4439 return (count / 1000000.0).toFixed(3) + ' ms'; 4440 } else { 4441 return ''+count; 4442 } 4443 } 4444 4445 getProcessName(pid) { 4446 let name = this.processNameMap[pid]; 4447 return name ? `Process: ${pid} (${name})` : 'Process: '+pid.toString(); 4448 } 4449 4450 getThreadName(tid) { 4451 let name = this.threadNameMap[tid]; 4452 return name ? `Thread: ${tid} (${name})` : 'Thread: '+tid.toString(); 4453 } 4454 4455 getLibName(fileId) { 4456 return 'Library: '+this.symbolsFileList[fileId]; 4457 } 4458 4459 getFuncName(funcId) { 4460 return 'Function: '+this.SymbolMap[funcId].symbol; 4461 } 4462 4463 connectedCallback() { 4464 this.table = this.shadowRoot.getElementById('tbl1'); 4465 this.chart = this.shadowRoot.getElementById('chart'); 4466 this.backBt = this.shadowRoot.getElementById('back'); 4467 this.backBt.addEventListener('mouseover',e=>{ 4468 this.backBt.style.borderColor = '#42b983'; 4469 this.backBt.style.color = '#42b983'; 4470 }); 4471 this.backBt.addEventListener('mouseout',e=>{ 4472 this.backBt.style.borderColor = '#aaa'; 4473 this.backBt.style.color = '#555'; 4474 }); 4475 this.chartMaxCount = 126; 4476 this.backBt.addEventListener('click',this.back.bind(this)); 4477 this.chart.chartClickListener = (item) => { 4478 if (item && item.id !== -1) { 4479 let ds = this.table.dataSource; 4480 if (item.name.startsWith('Process')) { 4481 let pName = item.name.slice(8); 4482 if (!ds.find(item => item.key === 'Process')) { 4483 ds.push({key:'Process',value:pName,time:item.time}); 4484 } 4485 this.chart.title = 'Threads in process ' + pName; 4486 let find = this.eventInfo.processes.find(process => item.id === process.pid); 4487 this.clickProcess(find); 4488 } else if (item.name.startsWith('Thread')) { 4489 let tName = item.name.slice(7); 4490 if (!ds.find(item => item.key === 'Thread')) { 4491 ds.push({key:'Thread',value:tName,time:item.time}); 4492 } 4493 this.chart.title = 'Libraries in thread ' + tName; 4494 let find = this.clickProcessData.threads.find(thread => item.id === thread.tid); 4495 this.clickThread(find); 4496 } else if (item.name.startsWith('Library')) { 4497 let libName = item.name.slice(8); 4498 if (!ds.find(item => item.key === 'Library')) { 4499 ds.push({key:'Library',value:libName,time:item.time}); 4500 } 4501 this.chart.title = 'Function in library ' + libName; 4502 let find = this.clickThreadData.libs.find(lib => item.id === lib.fileId); 4503 this.clickLib(find); 4504 } 4505 this.table.dataSource = ds; 4506 } 4507 }; 4508 } 4509 4510 initChartData() { 4511 if (Array.isArray(this.eventInfo.processes)) { 4512 function compare(property) { 4513 return function (a,b) { 4514 return b[property] - a[property]; 4515 } 4516 } 4517 this.eventInfo.processes.sort(compare('eventCount')); 4518 let chartSource = []; 4519 let otherValue = 0; 4520 this.eventInfo.processes.forEach((process,index) => { 4521 if (index < 14) { 4522 chartSource.push({ 4523 id:process.pid, 4524 name: this.getProcessName(process.pid), 4525 value: (process.eventCount / this.eventInfo.eventCount).toFixed(3), 4526 color: this.color[index], 4527 time: this.getSampleWeight(process.eventCount) 4528 }); 4529 } else { 4530 otherValue += process.eventCount; 4531 } 4532 }); 4533 if (otherValue > 0) { 4534 chartSource.push({ 4535 id : -1, 4536 name: 'Other: Represents a collection of items which proporiton of count ranked very low', 4537 value: (otherValue / this.eventInfo.eventCount).toFixed(3), 4538 color: '#888888', 4539 time: this.getSampleWeight(otherValue) 4540 }); 4541 } 4542 chartSource.sort((a, b) => { return b.time - a.time }); 4543 this.processDs = chartSource; 4544 this.chart.dataSource = chartSource; 4545 this.chart.title = 'Processes in event type ' + this.eventInfo.eventConfigName; 4546 } 4547 } 4548 4549 back() { 4550 if (this.currentLevel === 'Function in Library') { 4551 this.chart.title = 'Libraries in thread ' + this.getThreadName(this.clickThreadData.tid).slice(7); 4552 this.chart.dataSource = this.libDs; 4553 this.currentLevel = 'Library in Thread'; 4554 } else if (this.currentLevel === 'Library in Thread') { 4555 this.chart.title = 'Threads in process ' + this.getProcessName(this.clickProcessData.pid).slice(8); 4556 this.chart.dataSource = this.threadDs; 4557 this.currentLevel = 'Thread in Process'; 4558 } else { 4559 this.chart.title = 'Processes in event type ' + this.eventInfo.eventConfigName; 4560 this.chart.dataSource = this.processDs; 4561 this.backBt.style.display = 'none'; 4562 } 4563 let row = this.table.dataSource; 4564 if (Array.isArray(row)) { 4565 row.pop(); 4566 this.table.dataSource = row; 4567 } 4568 } 4569 4570 /** 4571 * chart click process 4572 * 4573 * @param process 4574 */ 4575 clickProcess(process) 4576 { 4577 if (process && process.threads) { 4578 this.backBt.style.display = 'flex'; 4579 this.currentLevel = 'Thread in Process'; 4580 this.clickProcessData = process; 4581 let chartSource = []; 4582 let chartTotal = 0; 4583 let count = 0; 4584 let filter = process.threads.filter(item => item.eventCount / process.eventCount > 0.001); 4585 let total = 0; 4586 filter.forEach(item=>{ total += item.eventCount }); 4587 total = process.eventCount; 4588 for (let item of filter) { 4589 if (count >= this.chartMaxCount) { 4590 break; 4591 } 4592 chartSource.push({ 4593 id:item.tid, 4594 name: this.getThreadName(item.tid), 4595 value: (item.eventCount / total).toFixed(6), 4596 color: this.color[count % this.color.length], 4597 time: this.getSampleWeight(item.eventCount) 4598 }); 4599 chartTotal += item.eventCount; 4600 count++; 4601 } 4602 if (count < this.chartMaxCount && chartTotal < process.eventCount) { 4603 chartSource.push({ 4604 id : -1, 4605 name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low', 4606 value: ((process.eventCount - chartTotal) / process.eventCount).toFixed(6), 4607 color: '#888888', 4608 time: this.getSampleWeight(process.eventCount - chartTotal) 4609 }); 4610 } else { 4611 this.addOtherItem(count, total, chartTotal, chartSource); 4612 } 4613 chartSource.sort((a, b) => { return b.time - a.time }); 4614 this.threadDs = chartSource; 4615 this.chart.dataSource = chartSource; 4616 } 4617 } 4618 4619 /** 4620 * chart click thread 4621 * @param thread 4622 */ 4623 clickThread(thread) 4624 { 4625 if (thread && thread.libs) { 4626 this.currentLevel = 'Library in Thread'; 4627 let chartSource = []; 4628 let chartTotal = 0; 4629 let count = 0; 4630 this.clickThreadData = thread; 4631 let filter = thread.libs.filter(item => item.eventCount / thread.eventCount > 0.001); 4632 let total = 0; 4633 filter.forEach(item=>{ total += item.eventCount }); 4634 total = thread.eventCount; 4635 for (let item of filter) { 4636 if (count < this.chartMaxCount) { 4637 chartSource.push({ 4638 id:item.fileId, 4639 name: this.getLibName(item.fileId), 4640 value: (item.eventCount / total).toFixed(6), 4641 color: this.color[count % this.color.length], 4642 time: this.getSampleWeight(item.eventCount) 4643 }); 4644 chartTotal += item.eventCount; 4645 count ++; 4646 } 4647 if (count >= this.chartMaxCount) { 4648 break; 4649 } 4650 } 4651 if (count < this.chartMaxCount && chartTotal < thread.eventCount) { 4652 chartSource.push({ 4653 id : -1, 4654 name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low', 4655 value: ((thread.eventCount - chartTotal) / thread.eventCount).toFixed(6), 4656 color: '#888888', 4657 time: this.getSampleWeight(thread.eventCount - chartTotal) 4658 }); 4659 } else { 4660 this.addOtherItem(count, total, chartTotal, chartSource); 4661 } 4662 chartSource.sort((a, b) => { return b.time - a.time }) 4663 this.libDs = chartSource; 4664 this.chart.dataSource = chartSource; 4665 } 4666 } 4667 4668 /** 4669 * chart click lib 4670 * @param lib 4671 */ 4672 clickLib(lib) { 4673 if (lib && lib.functions) { 4674 this.currentLevel = 'Function in Library' 4675 let chartSource = []; 4676 let chartTotal = 0; 4677 let count = 0; 4678 let filter = lib.functions.filter(item => item.counts[1] / lib.eventCount > 0.001); 4679 let total = lib.eventCount; 4680 let countTotal = 0; 4681 filter.forEach(item=>{ countTotal += item.counts[1]}); 4682 for (let item of filter) { 4683 if (count < this.chartMaxCount) { 4684 chartSource.push({ 4685 id:item.symbol, 4686 name: this.getFuncName(item.symbol), 4687 value: (item.counts[1] / total).toFixed(6), 4688 color: this.color[count % this.color.length], 4689 time: this.getSampleWeight(item.counts[1]) 4690 }); 4691 chartTotal += item.counts[1]; 4692 count++; 4693 } 4694 if (count >= this.chartMaxCount) { 4695 break; 4696 } 4697 } 4698 if (count < this.chartMaxCount && countTotal < lib.eventCount) { 4699 chartSource.push({ 4700 id : -1, 4701 name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low', 4702 value: ((lib.eventCount - countTotal) / lib.eventCount).toFixed(6), 4703 color: '#888888', 4704 time: this.getSampleWeight(lib.eventCount - countTotal) 4705 }); 4706 } else { 4707 this.addOtherItem(count,total,chartTotal,chartSource); 4708 } 4709 chartSource.sort((a, b) => { return b.time - a.time }); 4710 this.chart.dataSource = chartSource; 4711 } 4712 } 4713 4714 addOtherItem(count,total,chartTotal,chartSource) { 4715 if (count >= this.chartMaxCount && total > chartTotal) { 4716 chartSource.push({ 4717 id : -1, 4718 name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low', 4719 value: ((total - chartTotal) / total).toFixed(6), 4720 color: '#888888', 4721 time: this.getSampleWeight(total - chartTotal) 4722 }); 4723 } 4724 } 4725 4726 attributeChangedCallback(name, oldValue, newValue) { 4727 if (name === 'color' && this.loading) { 4728 this.loading.style.color = newValue; 4729 } 4730 if (name === 'size' && this.loading) { 4731 this.loading.style.fontSize = newValue + 'px'; 4732 } 4733 } 4734 } 4735 if (!customElements.get('app-chart-statistics')) { 4736 customElements.define('app-chart-statistics', AppChartStatistics); 4737 } 4738 4739 class AppFlameGraph extends HTMLElement { 4740 static get observedAttributes() { 4741 return ['color', 'size']; 4742 } 4743 4744 constructor() { 4745 super(); 4746 const shadowRoot = this.attachShadow({mode: 'open'}); 4747 shadowRoot.innerHTML = ` 4748 <style> 4749 :host{ 4750 font-size:inherit; 4751 display:inline-flex; 4752 align-items: center; 4753 justify-content:center; 4754 width: 100%; 4755 } 4756 </style> 4757 <div style="width: 100%;display: flex;flex-direction: column"> 4758 <lit-select id="typeSelect" default-value="1" mode="single" style="width:40vw;margin-bottom: 10px;align-self: flex-end"> 4759 <lit-select-option value="1">Show percentage of event count relative to the current thread</lit-select-option> 4760 <lit-select-option value="2">Show percentage of event count relative to the current process</lit-select-option> 4761 <lit-select-option value="3">Show percentage of event count relative to all process</lit-select-option> 4762 <lit-select-option value="4">show event count</lit-select-option> 4763 <lit-select-option value="5">show event count in milliseconds</lit-select-option> 4764 </lit-select> 4765 <div id="panel" style="width: 100%"></div> 4766 </div> 4767 <slot></slot> 4768 `; 4769 } 4770 4771 get data() { 4772 return this._json || null; 4773 } 4774 4775 set data(json) { 4776 // 如果已经给过值,不重新刷新 4777 if (this.isFinished) { 4778 return; 4779 } 4780 this._json = json; 4781 this.panel = this.shadowRoot.getElementById('panel'); 4782 this.panel.innerHTML = ''; 4783 let processes = json.recordSampleInfo[window.eventIndex].processes; 4784 processes.slice(0).forEach(it => { 4785 it.threads.sort((a, b) => { return b.eventCount - a.eventCount }); 4786 it.threads.slice(0).forEach(th => { 4787 let pid = it.pid; 4788 let processName = json.processNameMap[it.pid]; 4789 let tid = th.tid; 4790 let threadName = json.threadNameMap[th.tid]; 4791 let eventCount = th.eventCount; 4792 let sampleCount = th.sampleCount; 4793 let g = th.CallOrder; 4794 let flame = document.createElement('app-chart-flame'); 4795 flame.style.width = '100%'; 4796 flame.style.height = 'auto'; 4797 flame.style.display = 'flex'; 4798 this.panel.appendChild(flame); 4799 flame.data = { 4800 type:this.type||1, 4801 pid, processName, tid, threadName, eventCount, sampleCount, CallOrder:g 4802 }; 4803 }) 4804 }) 4805 this.isFinished = true; 4806 } 4807 4808 connectedCallback() { 4809 this.isFinished = false; 4810 this.panel = this.shadowRoot.getElementById('panel'); 4811 this.typeSelect = this.shadowRoot.getElementById('typeSelect'); 4812 this.typeSelect.onchange = ev => { 4813 this.type = parseInt(ev.detail.value); 4814 this.isFinished = false; 4815 this.data = window.data; 4816 } 4817 } 4818 4819 attributeChangedCallback(name, oldValue, newValue) { 4820 if (name === 'color' && this.loading) { 4821 this.loading.style.color = newValue; 4822 } 4823 if (name === 'size' && this.loading) { 4824 this.loading.style.fontSize = newValue + 'px'; 4825 } 4826 } 4827 } 4828 if (!customElements.get('app-flame-graph')) { 4829 customElements.define('app-flame-graph', AppFlameGraph); 4830 } 4831 4832 class AppFunction extends HTMLElement { 4833 static get observedAttributes() { 4834 return ['color', 'size']; 4835 } 4836 4837 constructor() { 4838 super(); 4839 const shadowRoot = this.attachShadow({mode: 'open'}); 4840 shadowRoot.innerHTML = ` 4841 <style> 4842 :host{ 4843 font-size:inherit; 4844 display:inline-flex; 4845 align-items: center; 4846 justify-content:center; 4847 padding: 0; 4848 margin: 0; 4849 width: 100%; 4850 } 4851 </style> 4852 <div style="width: 100%;display: flex;flex-direction: column"> 4853 <lit-table id="table" noheader style="width: 100%;"> 4854 <lit-table-column title="key" data-index="key" width="200px" key="key" ></lit-table-column> 4855 <lit-table-column title="value" data-index="value" width="1fr" key="value"></lit-table-column> 4856 </lit-table> 4857 <lit-select id="typeSelect" default-value="1" mode="single" style="width:500px;margin-bottom: 10px;margin-top:10px;align-self: flex-end"> 4858 <lit-select-option value="1">Show percentage of event count relative to the current thread</lit-select-option> 4859 <lit-select-option value="2">Show percentage of event count relative to the current process</lit-select-option> 4860 <lit-select-option value="3">Show percentage of event count relative to all process</lit-select-option> 4861 <lit-select-option value="4">show event count</lit-select-option> 4862 <lit-select-option value="5">show event count in milliseconds</lit-select-option> 4863 </lit-select> 4864 <app-chart-flame id="flame1"></app-chart-flame> 4865 <app-chart-flame id="flame2"></app-chart-flame> 4866 </div> 4867 <slot></slot> 4868 `; 4869 } 4870 4871 getNodesMatchingFuncId(root, funcId) { 4872 let nodes = []; 4873 4874 function recursiveFn(node) { 4875 if (node.symbol === funcId) { 4876 nodes.push(node); 4877 } else { 4878 for (let child of node.callStack) { 4879 recursiveFn(child); 4880 } 4881 } 4882 } 4883 4884 recursiveFn(root); 4885 return nodes; 4886 } 4887 4888 get dataSource() { 4889 return this._dataSource; 4890 } 4891 4892 getReverseData(rg, funId) { 4893 4894 } 4895 4896 set dataSource(val) { 4897 this._dataSource = val; 4898 this.table.dataSource = [ 4899 {key: 'Event Type', value: data.recordSampleInfo[window.eventIndex].eventConfigName}, 4900 {key: 'Process', value: val.process}, 4901 {key: 'Thread', value: val.thread}, 4902 {key: 'Library', value: val.library}, 4903 {key: 'Function', value: val.fun} 4904 ]; 4905 let filterProcess = 4906 data.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === val.processId); 4907 let filterThread = filterProcess[0].threads.filter(it => it.tid === val.threadId); 4908 let filterG = filterThread[0].CallOrder; 4909 let filterRG = filterThread[0].CalledOrder; 4910 let c; 4911 let rc; 4912 let findF = (obj) => { 4913 if (Array.isArray(obj)) { 4914 obj.forEach(it => { 4915 if (it.symbol === val.funId) { 4916 c = it; 4917 return; 4918 } else { 4919 if (it.callStack && it.callStack.length > 0) { 4920 findF(it.callStack); 4921 } 4922 } 4923 }); 4924 } else { 4925 findF(obj.callStack); 4926 } 4927 }; 4928 // 合并倒树结构 4929 let mergeRc = (obj) => { 4930 let rc = {}; 4931 let _rc = this.getNodesMatchingFuncId(obj, val.funId);// 将rg树中 为funId值的节点 形成一个数组 4932 let _sumCount = _rc.reduce((acc, cur) => acc + cur.subEvents, 0);// 计算eventCount值 4933 let splitChildrenForNodes = (nodes) => { 4934 let map = new Map(); 4935 for (let node of nodes) { 4936 if (node.callStack) { 4937 for (let child of node.callStack) { 4938 let subNodes = map.get(child.symbol); 4939 if (subNodes) { 4940 subNodes.push(child); 4941 } else { 4942 map.set(child.symbol, [child]); 4943 } 4944 } 4945 } 4946 } 4947 let res = []; 4948 for (let key of map.keys()) { 4949 let subNodes = map.get(key); 4950 res.push({ 4951 selfEvents: 0, 4952 subEvents: subNodes.reduce((acc, cur) => acc + cur.subEvents, 0), 4953 symbol: key, 4954 callStack: splitChildrenForNodes(subNodes) 4955 }); 4956 } 4957 return res; 4958 }; 4959 let children = splitChildrenForNodes(_rc); 4960 return { 4961 selfEvents: 0, 4962 subEvents: _sumCount, 4963 symbol: val.funId, 4964 callStack: children 4965 }; 4966 } 4967 findF(filterG); 4968 rc = mergeRc(filterRG); 4969 c = mergeRc(filterG); 4970 this.flame1.data = { 4971 pid: val.processId, 4972 processName: val.processName, 4973 tid: val.threadId, 4974 threadName: val.threadName, 4975 eventCount: null, 4976 sampleCount: null, 4977 type: this.type || 1, 4978 funcName: `Functions called by ${val.fun}`, 4979 CallOrder: c 4980 }; 4981 4982 this.flame2.data = { 4983 pid: val.processId, 4984 processName: val.processName, 4985 tid: val.threadId, 4986 threadName: val.threadName, 4987 eventCount: null, 4988 sampleCount: null, 4989 reverse: true, 4990 type: this.type || 1, 4991 funcName: `Functions calling ${val.fun}`, 4992 CallOrder: rc 4993 }; 4994 } 4995 4996 connectedCallback() { 4997 this.table = this.shadowRoot.getElementById('table'); 4998 this.flame1 = this.shadowRoot.getElementById('flame1'); 4999 this.flame2 = this.shadowRoot.getElementById('flame2'); 5000 this.typeSelect = this.shadowRoot.getElementById('typeSelect'); 5001 this.typeSelect.onchange = (e) => { 5002 this.type = parseInt(e.detail.value); 5003 this.dataSource = this.dataSource; 5004 }; 5005 } 5006 5007 attributeChangedCallback(name, oldValue, newValue) { 5008 5009 } 5010 } 5011 if (!customElements.get('app-function')) { 5012 customElements.define('app-function', AppFunction); 5013 } 5014 5015 class AppSimpleTable extends HTMLElement { 5016 static get observedAttributes() { 5017 return ['color', 'size']; 5018 } 5019 5020 constructor() { 5021 super(); 5022 const shadowRoot = this.attachShadow({mode: 'open'}); 5023 shadowRoot.innerHTML = ` 5024 <style> 5025 :host{ 5026 font-size:inherit; 5027 display:inline-flex; 5028 align-items: center; 5029 justify-content:center; 5030 } 5031 .loading{ 5032 display: block; 5033 width: 1em; 5034 height: 1em; 5035 margin: auto; 5036 animation: rotate 1.4s linear infinite; 5037 } 5038 .circle { 5039 stroke: currentColor; 5040 animation: progress 1.4s ease-in-out infinite; 5041 stroke-dasharray: 80px, 200px; 5042 stroke-dashoffset: 0px; 5043 transition:.3s; 5044 } 5045 :host(:not(:empty)) .loading{ 5046 margin:.5em; 5047 } 5048 @keyframes rotate{ 5049 to{ 5050 transform: rotate(360deg); 5051 } 5052 } 5053 @keyframes progress { 5054 0% { 5055 stroke-dasharray: 1px, 200px; 5056 stroke-dashoffset: 0px; 5057 } 5058 50% { 5059 stroke-dasharray: 100px, 200px; 5060 stroke-dashoffset: -15px; 5061 } 5062 100% { 5063 stroke-dasharray: 100px, 200px; 5064 stroke-dashoffset: -125px; 5065 } 5066 } 5067 </style> 5068 <div style="width: 100%;height: auto;"> 5069 <svg class="loading" id="loading" viewBox="22 22 44 44"><circle class="circle" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg> 5070 <div style="display: flex;flex-direction: column;align-items: flex-end;width: 100%;"> 5071 <div style="display: flex;flex-direction: row;align-items: center;justify-content: space-between;width: 100%;margin-bottom: 10px"> 5072 <lit-input id="keyword" icon="search" placeholder="Please enter a keyword" style="width: 300px" allow-clear></lit-input> 5073 <lit-select id="typeSelect" default-value="1" mode="single" style="width: 400px;margin-bottom: 10px"> 5074 <lit-select-option value="1">show percentage of event count</lit-select-option> 5075 <lit-select-option value="2">show event count</lit-select-option> 5076 <lit-select-option value="3">show event count in milliseconds</lit-select-option> 5077 </lit-select> 5078 </div> 5079 <lit-table id="table" style="width: calc(100vw - 40px);"> 5080 <lit-table-column title="Total" data-index="total" width="100px" key="total" order></lit-table-column> 5081 <lit-table-column title="Self" data-index="self" width="100px" key="self" order></lit-table-column> 5082 <lit-table-column title="Samples" data-index="samples" width="100px" key="samples" order></lit-table-column> 5083 <lit-table-column title="Process" data-index="process" width="250px" key="process" order></lit-table-column> 5084 <lit-table-column title="Thread" data-index="thread" width="250px" key="thread" order></lit-table-column> 5085 <lit-table-column title="Library" data-index="library" width="250px" key="library" order></lit-table-column> 5086 <lit-table-column title="Function" data-index="fun" key="fun" order></lit-table-column> 5087 </lit-table> 5088 <div style="height: 140px"> 5089 <lit-pagination id="pagination" show-size-changer page-size="10" page-size-options="[20,50,200]" style="margin-top: 10px;"> 5090 <!-- <template slot="showTotal"><label>{{range[0]}}-{{range[1]}} of {{total}} items</label></template>--> 5091 </lit-pagination> 5092 </div> 5093 </div> 5094 5095 </div> 5096 <slot></slot> 5097 `; 5098 } 5099 5100 set data(json) { 5101 if (json.recordSampleInfo && json.recordSampleInfo.length > 0) { 5102 this.processNameMap = json.processNameMap; 5103 this.threadNameMap = json.threadNameMap; 5104 this.symbolsFileList = json.symbolsFileList; 5105 this.SymbolMap = json.SymbolMap; 5106 this.eventInfo = json.recordSampleInfo[window.eventIndex]; 5107 this.initTableData().then(() => { 5108 this.loading.style.display = 'none'; 5109 this.pagination.current = 1; 5110 this.pagination.total = this.source.length; 5111 this.table.dataSource = this.paginationHandler(1,this.pagination.pageSize); 5112 }); 5113 } 5114 } 5115 5116 paginationHandler(page,pageSize,data) { 5117 let offset = (page - 1) * pageSize; 5118 let arr = []; 5119 if (this.searchKey && this.searchKey.length > 0 && data) { 5120 arr = (offset + pageSize >= data.length) ? 5121 data.slice(offset, data.length) : data.slice(offset, offset + pageSize); 5122 } else { 5123 arr = (offset + pageSize >= this.source.length) ? 5124 this.source.slice(offset, this.source.length) : this.source.slice(offset, offset + pageSize); 5125 } 5126 arr.forEach(item=>{ 5127 item.total = this.getSampleWeight(item.totalCount); 5128 item.self = this.getSampleWeight(item.selfCount); 5129 }) 5130 return arr; 5131 } 5132 5133 async initTableData() { 5134 this.source = []; 5135 this.eventInfo.processes.forEach(process => { 5136 process.threads.forEach(thread => { 5137 thread.libs.forEach(lib => { 5138 lib.functions.forEach(fun => { 5139 this.source.push({ 5140 process: this.getProcessName(process.pid), 5141 processId:process.pid, 5142 thread: this.getThreadName(thread.tid), 5143 threadId:thread.tid, 5144 library: this.getLibName(lib.fileId), 5145 libraryId:lib.fileId, 5146 fun: this.getFuncName(fun.symbol), 5147 funId: fun.symbol, 5148 totalCount: fun.counts[2], 5149 selfCount: fun.counts[1], 5150 samples: fun.counts[0], 5151 total:this.getSampleWeight(fun.counts[2]), 5152 self:this.getSampleWeight(fun.counts[1]), 5153 }); 5154 }); 5155 }); 5156 }); 5157 }); 5158 function compare(property) { 5159 return function (a, b) { 5160 return b[property] - a[property]; 5161 }; 5162 } 5163 this.source.sort(compare('totalCount')); 5164 } 5165 5166 getSampleWeight(count) { 5167 if (this.eventType.value === '1') { 5168 return (count * 100.0 / this.eventInfo.eventCount).toFixed(2) + '%'; 5169 } else if (this.eventType.value === '2') { 5170 return count + ''; 5171 } else { 5172 return (count / 1000000.0).toFixed(3); 5173 } 5174 } 5175 5176 getProcessName(pid) { 5177 let name = this.processNameMap[pid]; 5178 return name ? `${pid} (${name})` : pid.toString(); 5179 } 5180 5181 getThreadName(tid) { 5182 let name = this.threadNameMap[tid]; 5183 return name ? `${tid} (${name})` : tid.toString(); 5184 } 5185 5186 getLibName(fileId) { 5187 return this.symbolsFileList[fileId]; 5188 } 5189 5190 getFuncName(funcId) { 5191 return this.SymbolMap[funcId].symbol; 5192 } 5193 5194 get size() { 5195 return this.getAttribute('size') || ''; 5196 } 5197 5198 get color() { 5199 return this.getAttribute('color') || ''; 5200 } 5201 5202 set size(value) { 5203 this.setAttribute('size', value); 5204 } 5205 5206 set color(value) { 5207 this.setAttribute('color', value); 5208 } 5209 5210 connectedCallback() { 5211 this.suffix = ''; 5212 this.loading = this.shadowRoot.getElementById('loading'); 5213 this.keyword = this.shadowRoot.getElementById('keyword'); 5214 this.eventType = this.shadowRoot.getElementById('typeSelect'); 5215 this.pagination = this.shadowRoot.getElementById('pagination'); 5216 this.table = this.shadowRoot.getElementById('table'); 5217 this.eventType.addEventListener('change',this.updateTableSource.bind(this)); 5218 this.pagination.addEventListener('onChange',this.updateTableSource.bind(this)); 5219 this.pagination.addEventListener('onShowSizeChange',this.updateTableSource.bind(this)); 5220 this.size && (this.size = this.size); 5221 this.color && (this.color = this.color); 5222 this.ds = []; 5223 this.keyword.addEventListener('input',e=>{ 5224 if (this.searchKey !== this.keyword.value) { 5225 this.searchKey = this.keyword.value; 5226 this.pagination.current = 1; 5227 this.ds = this.source.filter(item => item.process.indexOf(this.searchKey) !== -1 5228 || item.thread.indexOf(this.searchKey) !== -1 5229 || item.library.indexOf(this.searchKey) !== -1 || item.fun.indexOf(this.searchKey) !== -1); 5230 this.pagination.total = this.ds.length; 5231 this.table.dataSource = 5232 this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize),this.ds); 5233 } 5234 }); 5235 this.keyword.addEventListener('onClear',e=>{ 5236 this.searchKey = undefined; 5237 this.pagination.current = 1; 5238 this.pagination.total = this.source.length; 5239 this.table.dataSource = 5240 this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize)); 5241 }); 5242 this.table.addEventListener('ColumnClick',evt => { 5243 this.sortByColumn(evt.detail); 5244 }); 5245 } 5246 5247 sortByColumn(detail) { 5248 function compare(property,sort,type) { 5249 return function (a, b) { 5250 if (type === 'number') { 5251 return sort === 2 ? b[property] - a[property] : a[property] - b[property]; 5252 } else { 5253 if (b[property] > a[property]) { 5254 return sort === 2 ? 1 : -1; 5255 } else if (b[property] === a[property]) { 5256 return 0; 5257 } else { 5258 return sort === 2 ? -1 : 1; 5259 } 5260 } 5261 }; 5262 } 5263 5264 console.log(detail.key); 5265 if (detail.key === 'total') { 5266 this.source.sort(compare('totalCount',detail.sort,'number')); 5267 } else if (detail.key === 'self') { 5268 this.source.sort(compare('selfCount',detail.sort,'number')); 5269 } else if (detail.key === 'samples') { 5270 this.source.sort(compare('samples',detail.sort,'number')); 5271 } else { 5272 this.source.sort(compare(detail.key,detail.sort,'string')); 5273 } 5274 this.pagination.current = 1; 5275 this.pagination.total = this.source.length; 5276 this.table.dataSource = this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize)); 5277 } 5278 5279 updateTableSource(e) { 5280 if (e.type === 'change') { 5281 this.eventType.value = e.detail.value; 5282 this.suffix = e.detail.value === '3' ? '(in ms)' : ''; 5283 } 5284 if (this.ds.length !== 0) { 5285 this.pagination.total = this.ds.length; 5286 this.table.dataSource = 5287 this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize),this.ds); 5288 } else { 5289 this.table.dataSource = 5290 this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize)); 5291 } 5292 } 5293 5294 attributeChangedCallback(name, oldValue, newValue) { 5295 if (name === 'color' && this.loading) { 5296 this.loading.style.color = newValue; 5297 } 5298 if (name === 'size' && this.loading) { 5299 this.loading.style.fontSize = newValue + 'px'; 5300 } 5301 } 5302 } 5303 if (!customElements.get('app-simple-table')) { 5304 customElements.define('app-simple-table', AppSimpleTable); 5305 } 5306 5307 (function () { 5308 5309 function createPromise(callback) { 5310 if (callback) { 5311 return new Promise((resolve, _) => callback(resolve)); 5312 } 5313 return new Promise((resolve, _) => resolve()); 5314 } 5315 5316 function initGlobalObjects() { 5317 let recordData = document.querySelector('#record_data').textContent; 5318 if (recordData.trim().length>0) { 5319 return new Promise((resolve, reject) => { 5320 resolve(JSON.parse(recordData)); 5321 }); 5322 } else { 5323 return fetch('data.json').then(response => response.json()); 5324 } 5325 } 5326 function waitDocumentReady() { 5327 return createPromise((resolve) => document.addEventListener('DOMContentLoaded', resolve)); 5328 } 5329 createPromise() 5330 .then(waitDocumentReady) 5331 .then(initGlobalObjects) 5332 .then((json) => { 5333 window.data = json; 5334 window.eventIndex = 0; 5335 let eventSelector = document.querySelector('#events'); 5336 if (json.recordSampleInfo && json.recordSampleInfo.length > 0) { 5337 let events = []; 5338 json.recordSampleInfo.forEach((e, index) => { 5339 events.push({key:index+'',val:e.eventConfigName}) 5340 }); 5341 eventSelector.dataSource = events; 5342 } 5343 let chart = document.querySelector('#chart-statistics'); 5344 let loading = document.querySelector('#loading'); 5345 let table = document.querySelector('#sample-table'); 5346 let appFunc = document.querySelector('#function'); 5347 let flame = document.querySelector('#flame-graph'); 5348 let tabs = document.querySelector('#tabs') 5349 let pane4 = document.querySelector('#pane4') 5350 chart.data = json; 5351 table.data = json; 5352 table.addEventListener('onRowClick', e => { 5353 pane4.hide = false; 5354 tabs.activePane('4'); 5355 appFunc.dataSource = e.detail; 5356 }); 5357 tabs.onTabClick = (e) => { 5358 if (e.detail.key === '3') { 5359 flame.isFinished = false; 5360 flame.data = json; 5361 } 5362 }; 5363 eventSelector.addEventListener('change',(e)=>{ 5364 loading.style.display = 'flex'; 5365 pane4.hide = true; 5366 window.eventIndex = parseInt( e.detail.value); 5367 chart.data = json; 5368 table.data = json; 5369 flame.isFinished = false; 5370 flame.data = json; 5371 if (tabs.activekey === '4') { 5372 tabs.activePane('1'); 5373 } 5374 loading.style.display = 'none'; 5375 }); 5376 }); 5377 }()) 5378</script> 5379<div style="width: 100%;height: 100%"> 5380 <div style="width: 100%;display: flex;flex-direction: column;align-items: center"> 5381 <lit-loading id="loading" size="32" style="display: none"></lit-loading> 5382 </div> 5383 <div style="display: flex;flex-direction: row;align-items: center;padding: 15px"> 5384 <span style="font-weight: bold;margin-right: 10px">Event Type :</span> 5385 <lit-select id="events" default-value="0" style="width: 400px"></lit-select> 5386 </div> 5387 <lit-tabs id='tabs' position="top-left" activekey="1" mode="flat"> 5388 <lit-tabpane id="pane1" tab="Chart Statistics" key="1"> 5389 <app-chart-statistics id="chart-statistics"></app-chart-statistics> 5390 </lit-tabpane> 5391 <lit-tabpane id="pane2" tab="Sample Table" key="2"> 5392 <app-simple-table id="sample-table"></app-simple-table> 5393 </lit-tabpane> 5394 <lit-tabpane id="pane3" tab="Flame Graph" key="3"> 5395 <app-flame-graph id="flame-graph"></app-flame-graph> 5396 </lit-tabpane> 5397 <lit-tabpane id="pane4" tab="Function" key="4" hide> 5398 <app-function id="function" style="width: 100%"></app-function> 5399 </lit-tabpane> 5400 </lit-tabs> 5401</div> 5402<script id="record_data" type="application/json"> 5403