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