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