developtools_hiperf/script/report-diff.html
wenlong12 d37f12f8d4 fix codehub issues
Signed-off-by: wenlong12 <wenlong12@huawei.com>

Signed-off-by: wenlong12 <wwx1097114@DESKTOP-2021EGU.localdomain>
2022-03-29 16:38:59 +08:00

5072 lines
220 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Report-Diff</title>
<style>
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
lit-tabs {
padding-top: 10px;
}
lit-tabpane {
padding: 20px;
}
</style>
</head>
<body>
<script type="module">
window.getShadowRoot = (el) => {
if (el.parentNode) {
return window.getShadowRoot(el.parentNode)
} else {
return el;
}
};
window.getShadowElement = (el) => {
if (el.parentElement) {
return window.getShadowElement(el.parentElement)
} else {
return el;
}
};
class LitIcon extends HTMLElement {
static get observedAttributes() {
return ["name", "size", "color", "path"]
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
font-size: inherit;
display: inline-block;
transition: .3s;
}
:host([spin]){
animation: rotate 1.75s linear infinite;
}
@keyframes rotate {
to{
transform: rotate(360deg);
}
}
.icon{
display: block;
width: 1em;
height: 1em;
margin: auto;
fill: currentColor;
overflow: hidden;
}
</style>
<svg style="display: none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<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>
<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>
<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>
<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>
<!-- <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>-->
<!-- <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>-->
</svg>
<svg class="icon" id="icon" aria-hidden="true" viewBox="0 0 ${this.view} ${this.view}">
${this.path ? '<path id="path"></path>' : '<use id="use"></use>'}
</svg>
`
}
get view() {
return this.getAttribute("view") || 1024;
}
get name() {
return this.getAttribute("name");
}
get path() {
return this.getAttribute("path");
}
set name(value) {
this.setAttribute("name", value);
}
set path(value) {
this.setAttribute("path", value);
}
get size() {
return this.getAttribute("size") || "";
}
get color() {
return this.getAttribute("color") || "";
}
set size(value) {
this.setAttribute("size", value);
}
set color(value) {
this.setAttribute("color", value);
}
get spin() {
return this.hasAttribute('spin');
}
set spin(value) {
if (value) {
this.setAttribute('spin', '')
} else {
this.removeAttribute('spin');
}
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.icon = this.shadowRoot.getElementById("icon");
this.use = this.shadowRoot.querySelector("use")
this.d = this.shadowRoot.querySelector("path");
this.size && (this.size = this.size);
this.color && (this.color = this.color);
this.name && (this.name = this.name);
this.path && (this.path = this.path);
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (name === "name" && this.use) {
this.use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', `#icon-${newValue}`);
}
if (name === "path" && this.d) {
this.d.setAttribute("d", newValue);
}
if (name === "color" && this.icon) {
this.icon.style.color = newValue;
}
if (name === "size" && this.icon) {
this.icon.style.fontSize = newValue + "px";
}
}
}
if (!customElements.get('lit-icon')) {
customElements.define('lit-icon', LitIcon);
}
class LitInput extends HTMLElement {
static get observedAttributes() {
return [
'placeholder',//显示提示文字
'block',//属性将使按钮适合其父宽度。
'icon',//图标,如果配置了 slot='prefix' icon将失效
'bordered',//是否有边框
'allow-clear',//是否有清除
'rows',//表示行数默认input 设置rows 后 显示为textarea
'maxlength',//允许输入多少个字符 默认没有限制
'type',//password number text
'pattern',//正则表达式
'error-text',//错误提示文字
'error-placement',//错误提示文字方向
'required',//是否开始验证
'value',//值
];
}
get value() {
return this.getAttribute('value') || '';
}
set value(value) {
this.setAttribute('value', value);
}
get required() {
return this.hasAttribute('required');
}
set required(value) {
if (value) {
this.setAttribute('required', "");
} else {
this.removeAttribute('required');
}
}
get pattern() {
return this.getAttribute('pattern') || '';
}
set pattern(value) {
this.setAttribute('pattern', value);
}
get errorText() {
return this.getAttribute('error-text') || '该项为必填项';
}
set errorText(value) {
this.setAttribute('error-text', value)
}
get errorPlacement() {
return this.getAttribute('error-placement') || 'top';
}
set errorPlacement(value) {
this.setAttribute('error-placement', value)
}
get type() {
return this.getAttribute('type');
}
set type(value) {
this.setAttribute('type', value);
}
get maxlength() {
return this.getAttribute('maxlength') || '';
}
set maxlength(value) {
this.setAttribute('maxlength', value)
}
get rows() {
return this.getAttribute('rows');
}
set rows(value) {
this.setAttribute('rows', value);
}
get allowClear() {
return this.hasAttribute('allow-clear');
}
set allowClear(value) {
if (value) {
this.setAttribute('allow-clear', '');
} else {
this.removeAttribute('allow-clear');
}
}
get bordered() {
return this.getAttribute('bordered') || "true";
}
set bordered(value) {
if (value) {
this.setAttribute('bordered', 'true');
} else {
this.setAttribute('bordered', 'false')
}
}
get icon() {
return this.getAttribute('icon') || null;
}
set icon(value) {
this.setAttribute('icon', value)
}
get block() {
return this.hasAttribute('block')
}
set block(value) {
if (value) {
this.setAttribute('block', '');
} else {
this.removeAttribute('block')
}
}
get placeholder() {
return this.getAttribute('placeholder') || '';
}
set placeholder(value) {
this.setAttribute('placeholder', value);
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
${this.bordered === 'true' ? 'border: 1px solid #e9e9e9;' : ''}
display: inline-flex;
box-sizing: border-box;
position:relative;
align-items: ${this.rows ? 'flex-start' : 'center'};
transition: all .3s;
border-radius: 2px;
font-size: 14px;
color: #333;
${this.block ? 'display: flex;' : ''}
box-sizing: border-box;
}
:host(:hover){
${this.bordered === 'true' ? 'border: 1px solid #65b687;' : ''}
${this.bordered === 'true' ? 'box-shadow: 0 0 10px #65b68766;' : ''}
}
*{
box-sizing: border-box;
}
.input{
outline: none;
border: 0px;
font-size: inherit;
color: inherit;
width: 100%;
height: 100%;
vertical-align:middle;
line-height:inherit;
height:inherit;
padding: 6px 6px 6px ${this.icon ? '6px' : '11px'};
max-height: inherit;
box-sizing: border-box;
}
/*去掉type=number 后面的增减箭头*/
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type='number'] {
-moz-appearance: textfield;
}
lit-tips{
flex: 1;
height: 100%;
width: 100%;
}
lit-tips{
flex: 1;
align-items: center;
}
:host(:not([allow-clear])) .clear-btn{
display: flex;
visibility: hidden;
transition: all .3s;
opacity: 0;
}
:host([allow-clear]) .clear-btn{
opacity: 0;
position:absolute;
right: 0;
top: .5rem;
visibility: hidden;
transition: all .3s;
display: flex;
margin-right: 6px;
transform: translateY(0%);
color: #bfbfbf;
}
:host([allow-clear]) .clear-btn:hover{
color: #8c8c8c;
}
.area{
outline: none;
border: 0px;
width: 100%;
font-size: inherit;
vertical-align:middle;
min-height: calc(2rem);
max-height: calc(${this.rows} * 1rem);
padding: 6px 6px 6px ${this.icon ? '6px' : '11px'};
box-sizing: border-box;
resize:vertical;
}
:host(:not([type=password])) .pwd-btn,
:host(:not([type=tel])) .pwd-btn{
display: none;
visibility: hidden;
transition: all .3s;
opacity: 0;
}
:host([type=password]) .pwd-btn,
:host([type=tel]) .pwd-btn{
opacity: 1;
position:absolute;
right: 0;
top: .5rem;
visibility: visible;
transition: all .3s;
display: flex;
margin-right: 6px;
transform: translateY(0%);
color: #bfbfbf;
}
/*:host([type=password]) input{*/
/* -webkit-text-security:none;*/
/* text-security:none;*/
/*}*/
:host([type=password]) .pwd-btn:hover,
:host([type=tel]) .pwd-btn:hover{
color: #8c8c8c;
}
</style>
<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>
<lit-tips tips="${this.errorText}" placement="${this.errorPlacement}" color="#f4615c" offset="12px" show="false">
${this.hasAttribute('rows') ?
`<textarea class="area" rows="${this.rows}" value="${this.value}" placeholder="${this.placeholder}" cols="27" maxlength="${this.maxlength}" onscroll="this.rows++;"></textarea>`
:
`<input type="${this.type}" value="${this.value}" class="input" placeholder="${this.placeholder}" >`}
<lit-icon class="clear-btn" name="close-circle-fill"></lit-icon>
<lit-icon class="pwd-btn" name="eye-fill"></lit-icon>
</lit-tips>
<slot name="suffix" ></slot>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.reg = new RegExp(this.pattern);
this.inputElement = this.shadowRoot.querySelector('.input');
this.areaElement = this.shadowRoot.querySelector('.area');
this.clearElement = this.shadowRoot.querySelector('.clear-btn');
this.pwdElement = this.shadowRoot.querySelector('.pwd-btn');
this.pwdElement.onclick = (e) => {
if (this.inputElement.getAttribute('type') === 'password') {
this.pwdElement.name = 'eyeclose-fill';
this.inputElement.setAttribute('type', 'tel');
} else if (this.inputElement.getAttribute('type') === 'tel') {
this.inputElement.setAttribute('type', 'password');
this.pwdElement.name = 'eye-fill';
}
}
if (this.areaElement) {
this.clearElement.onclick = e => {
this.areaElement.value = '';
this.clearElement.style.visibility = 'hidden';
this.clearElement.style.opacity = '0';
this.value = this.areaElement.value;
this.dispatchEvent(new CustomEvent('onClear', e));
this.clearError();
}
this.areaElement.oninput = e => {
if (this.allowClear) {
if (this.areaElement.value.length > 0) {
this.clearElement.style.visibility = 'visible';
this.clearElement.style.opacity = '1';
} else {
this.clearElement.style.visibility = 'hidden';
this.clearElement.style.opacity = '0';
}
}
this.value = this.areaElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('input', e));
}
this.areaElement.onchange = e => {
this.value = this.areaElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('change', e));
}
this.areaElement.onfocus = e => {
this.value = this.areaElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('focus', e));
}
this.areaElement.onblur = e => {
this.value = this.areaElement.value;
this.dispatchEvent(new CustomEvent('blur', e));
}
this.areaElement.onkeydown = e => {
if (e.key === 'Enter') {
this.value = this.areaElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('onPressEnter', e));
}
this.dispatchEvent(new CustomEvent('keydown', e));
}
this.areaElement.onkeyup = e => {
this.dispatchEvent(new CustomEvent('keyup', e));
}
this.areaElement.onselect = e => {
this.dispatchEvent(new CustomEvent('select', e));
}
this.areaElement.onclick = e => {
this.dispatchEvent(new CustomEvent('click', e));
}
}
if (this.inputElement) {
this.clearElement.onclick = e => {
this.inputElement.value = '';
this.clearElement.style.visibility = 'hidden';
this.clearElement.style.opacity = '0';
this.value = this.inputElement.value;
this.dispatchEvent(new CustomEvent('onClear', e));
this.clearError();
}
this.inputElement.oninput = e => {
if (this.allowClear) {
if (this.inputElement.value.length > 0) {
this.clearElement.style.visibility = 'visible';
this.clearElement.style.opacity = '1';
} else {
this.clearElement.style.visibility = 'hidden';
this.clearElement.style.opacity = '0';
}
}
this.value = this.inputElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('input', e));
}
this.inputElement.onchange = e => {
this.value = this.inputElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('change', e));
}
this.inputElement.onfocus = e => {
this.value = this.inputElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('focus', e));
}
this.inputElement.onblur = e => {
this.dispatchEvent(new CustomEvent('blur', e));
}
this.inputElement.onkeydown = e => {
if (e.key === 'Enter') {
this.value = this.inputElement.value;
this.verify();
this.dispatchEvent(new CustomEvent('onPressEnter', e));
}
this.dispatchEvent(new CustomEvent('keydown', e));
}
this.inputElement.onkeyup = e => {
this.dispatchEvent(new CustomEvent('keyup', e));
}
this.inputElement.onselect = e => {
this.dispatchEvent(new CustomEvent('select', e));
}
this.inputElement.onclick = e => {
this.dispatchEvent(new CustomEvent('click', e));
}
}
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'value') {
// console.log(name,oldValue,newValue);
// console.log(this.inputElement);
if (this.inputElement) {
this.inputElement.value = newValue
} else if (this.areaElement) {
this.areaElement.value = newValue;
}
}
}
verify() {
if (this.required) {
let result;
if (this.hasAttribute('rows')) {
result = this.reg.test(this.areaElement.value);
} else {
result = this.reg.test(this.inputElement.value);
}
if (result) {
this.shadowRoot.querySelector('lit-tips').show = 'false'
} else {
this.shadowRoot.querySelector('lit-tips').show = 'true'
}
return result;
} else {
return true;
}
}
clearError() {
this.shadowRoot.querySelector('lit-tips').show = 'false'
}
}
if (!customElements.get('lit-input')) {
customElements.define('lit-input', LitInput);
}
class LitLoading extends HTMLElement {
static get observedAttributes() { return ['color', 'size'] }
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
font-size:inherit;
display:inline-flex;
align-items: center;
justify-content:center;
color:var(--themeColor,#42b983);
}
.loading{
display: block;
width: 1em;
height: 1em;
margin: auto;
animation: rotate 1.4s linear infinite;
}
.circle {
stroke: currentColor;
animation: progress 1.4s ease-in-out infinite;
stroke-dasharray: 80px, 200px;
stroke-dashoffset: 0px;
transition:.3s;
}
:host(:not(:empty)) .loading{
margin:.5em;
}
@keyframes rotate{
to{
transform: rotate(360deg);
}
}
@keyframes progress {
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0px;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -125px;
}
}
</style>
<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>
<slot></slot>
`
}
get size() {
return this.getAttribute('size') || '';
}
get color() {
return this.getAttribute('color') || '';
}
set size(value) {
this.setAttribute('size', value);
}
set color(value) {
this.setAttribute('color', value);
}
connectedCallback() {
this.loading = this.shadowRoot.getElementById('loading');
this.size && (this.size = this.size);
this.color && (this.color = this.color);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'color' && this.loading) {
this.loading.style.color = newValue;
}
if (name == 'size' && this.loading) {
this.loading.style.fontSize = newValue + 'px';
}
}
}
if (!customElements.get('lit-loading')) {
customElements.define('lit-loading', LitLoading);
}
class LitPagination extends HTMLElement {
static get observedAttributes() {
return [
"current",//当前页码
"total",//总条数
"page-size",//每页条数
"disabled",//禁用分页
'show-size-changer',//显示每页条目数select
'show-quick-jumper',//显示跳至多少页
'page-size-options',//指定每页可以显示多少条 默认 [10,20,50,100]
'simple',//简洁模式
]
}
get pageSizeOptions() {
return this.getAttribute('page-size-options');
}
set pageSizeOptions(value) {
this.setAttribute('page-size-options', '');
}
get current() {
return this.getAttribute('current') || '1';
}
set current(value) {
this.setAttribute('current', value);
}
get total() {
return this.getAttribute('total') || '50';
}
set total(value) {
this.setAttribute('total', value);
}
get pageSize() {
return this.getAttribute('page-size') || '10';
}
set pageSize(value) {
this.setAttribute('page-size', value);
}
get disabled() {
return this.hasAttribute('disabled');
}
set disabled(value) {
if (value) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
get showSizeChanger() {
return this.hasAttribute('show-size-changer')
}
set showSizeChanger(value) {
if (value) {
this.setAttribute('show-size-changer', '');
} else {
this.removeAttribute('show-size-changer');
}
}
get showQuickJumper() {
return this.hasAttribute('show-quick-jumper')
}
set showQuickJumper(value) {
if (value) {
this.setAttribute('show-quick-jumper', '');
} else {
this.removeAttribute('show-quick-jumper');
}
}
get simple() {
return this.hasAttribute('simple');
}
set simple(value) {
if (value) {
this.setAttribute('simple', '');
} else {
this.removeAttribute('simple');
}
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: flex;
width: max-content;
}
.root{
display: inline-flex;
/*grid-template-columns: repeat(auto-fill,35px);*/
/*column-gap: 8px;*/
/*row-gap: 8px;*/
user-select: none;
}
.item{
box-sizing: border-box;
color: #333;
transition: all .3s;
width: 35px;
height: 35px;
margin-left: 6px;
padding: 6px 10px;
border: 1px solid #ececec;
border-radius: 3px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.item-dir{
color: #333;
transition: all .3s;
padding: 5px 10px;
/*border: 1px solid #ececec;*/
/*border-radius: 3px;*/
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.item:not([disable]):hover{
color:#42b983;
border: 1px solid #42b983;
}
.item[selected]{
color:#42b983;
border: 1px solid #42b983;
}
.item[disable]{
/*background-color: #f5f5f5;*/
fill: #c7c7c7;
color: #c7c7c7;
cursor: not-allowed;
}
:host([show-quick-jumper]) .jump-root{
grid-column: span 4;
margin-left: 6px;
display: inline-flex;
align-items: center;
}
:host(:not([show-quick-jumper])) .jump-root{
display: none;
}
:host([show-size-changer]) .pages-change{
display: inline-flex;
grid-column: span 3;
width: 120px;
margin-left: 6px;
font-size: .9rem;
}
:host(:not([show-size-changer])) .pages-change{
display: none;
}
</style>
<div style="display: grid;grid-template-columns: max-content 1fr;">
<div style="display: inline-flex;align-items: center" id="showTotal">
<slot id="st" name="showTotal"></slot>
</div>
<div class="root">
<!-- <lit-icon class="item left-arrow" name="left" ></lit-icon>-->
<svg class="item left-arrow" viewBox="0 0 1024 1024" aria-hidden="true" >
<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>
</svg>
<div class="item first">1</div>
<lit-icon class="item-dir double-left" name="ellipsis" ></lit-icon>
<!-- <svg class="item-dir double-left" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">-->
<!-- <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>-->
<!-- </svg>-->
<div class="item one">2</div>
<div class="item two">3</div>
<div class="item three">4</div>
<div class="item four">5</div>
<div class="item five">6</div>
<lit-icon class="item-dir double-right" name="ellipsis" ></lit-icon>
<!-- <svg class="item-dir double-right" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">-->
<!-- <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>-->
<!-- </svg>-->
<div class="item last">1</div>
<!-- <lit-icon class="item right-arrow" name="right"></lit-icon>-->
<svg class="item right-arrow" viewBox="0 0 1024 1024" aria-hidden="true" >
<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>
</svg>
<lit-select class="pages-change" default-value="10"></lit-select>
<div class="jump-root">
<label style="font-size: .9rem;color: #333;margin-right: 5px">跳至</label>
<lit-input type="number" class="jump" style="width: 80px;height: 100%"></lit-input>
<label style="font-size: .9rem;color: #333;margin-left: 5px">页</label>
</div>
</div>
</div>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.slotShowTotal = null;
this.rootElement = this.shadowRoot.querySelector('.root');
let st = this.shadowRoot.querySelector('#st');
st.addEventListener('slotchange', () => {
let elements = st.assignedElements();
if (elements.length > 0) {
this.slotShowTotal = elements[0];
}
if (this.slotShowTotal) {
let cloneNode = this.slotShowTotal.render({
total: this._total,
range: [1, this._pages],
}).content.cloneNode(true);
this.shadowRoot.querySelector('#showTotal').append(cloneNode);
}
})
this.leftArrow = this.shadowRoot.querySelector('.left-arrow');
this.first = this.shadowRoot.querySelector('.first');
this.doubleLeft = this.shadowRoot.querySelector('.double-left');
this.one = this.shadowRoot.querySelector('.one');
this.two = this.shadowRoot.querySelector('.two');
this.three = this.shadowRoot.querySelector('.three');
this.four = this.shadowRoot.querySelector('.four');
this.five = this.shadowRoot.querySelector('.five');
this.doubleRight = this.shadowRoot.querySelector('.double-right');
this.last = this.shadowRoot.querySelector('.last');
this.rightArrow = this.shadowRoot.querySelector('.right-arrow');
this.pagesChange = this.shadowRoot.querySelector('.pages-change');
this.jump = this.shadowRoot.querySelector('.jump');
this.nodes = [this.one, this.two, this.three, this.four, this.five]
let jsonArray = JSON.parse(this.pageSizeOptions);
if (jsonArray) {
this.pagesChange.dataSource = jsonArray.map(a => {
return { key: a, val: `${a} 条/页`, }
});
this.pagesChange.value = jsonArray[0] + '';
this.pageSize = jsonArray[0] + '';
} else {
this.pagesChange.dataSource = [10, 20, 50, 100].map(a => {
return { key: a, val: `${a} 条/页`, }
});
this.pagesChange.value = '10';
this.pageSize = '10';
}
this.jump.addEventListener('onPressEnter', e => {
if (e.target.value) {
this.current = e.target.value;
e.target.value = '';
this.match();
}
});
this.pagesChange.onchange = e => {
// console.log(e.detail);
this.pageSize = e.detail.value;
this.match();
if (this.slotShowTotal) {
let cloneNode = this.slotShowTotal.render({
total: this._total,
range: [1, this._pages],
}).content.cloneNode(true);
this.shadowRoot.querySelector('#showTotal').lastElementChild.remove();
this.shadowRoot.querySelector('#showTotal').append(cloneNode);
}
this.dispatchEvent(new CustomEvent('onShowSizeChange', {
detail: {
current: parseInt(this.current),
size: parseInt(this.pageSize)
}
}))
}
this.leftArrow.onclick = (e) => {
if (this._current > 1) {
this.current = `${this._current - 1}`
this.match();
}
}
this.rightArrow.onclick = (e) => {
if (this._current < this._pages) {
this.current = `${this._current + 1}`
this.match();
}
}
let nodeClickHandler = e => {
this.current = `${e.currentTarget.val}`;
this.match();
};
this.first.onclick = nodeClickHandler;
this.nodes.forEach(a => a.onclick = nodeClickHandler);
this.last.onclick = nodeClickHandler;
this.doubleLeft.onmouseover = e => {
e.target.name = 'doubleleft';
}
this.doubleLeft.onmouseout = e => {
e.target.name = 'ellipsis'
}
this.doubleLeft.onclick = e => {
let k = this._current - 5;
if (k < 1) k = 1;
this.current = `${k}`;
this.match();
}
this.doubleRight.onmouseover = e => {
e.target.name = 'doubleright';
}
this.doubleRight.onmouseout = e => {
e.target.name = 'ellipsis'
}
this.doubleRight.onclick = e => {
let k = this._current + 5;
if (k > this._pages) k = this._pages;
this.current = `${k}`;
this.match();
}
this.match();
}
match() {
this._current = parseInt(this.current);
this._total = parseInt(this.total);
this._pageSize = parseInt(this.pageSize);
this._pages = Math.ceil(this._total / this._pageSize)
if (this._current > this._pages) {
this._current = this._pages;
this.current = `${this._pages}`;
} else if (this._current < 1) {
this._current = 1;
this.current = `1`;
}
//是否展示 pageSize 切换器,当 total 大于 50 时默认为 true
// if(this._total>50) this.setAttribute('show-size-changer', '');
// console.log(`${this._total} ${this._current}/${this._pages} ${this._pageSize}`);
if (this._current === 1) {
this.leftArrow.setAttribute('disable', '');
} else {
this.leftArrow.removeAttribute('disable');
}
if (this._current === this._pages) {
this.rightArrow.setAttribute('disable', '');
} else {
this.rightArrow.removeAttribute('disable');
}
this.hiddenNode(this.first, this.last, this.one, this.two, this.three, this.four, this.five, this.doubleLeft, this.doubleRight)
if (this._pages <= 5) {
for (let i = 0; i < this._pages; i++) {
this.displayNode(this.nodes[i]);
this.item(this.nodes[i], i + 1);
}
} else if (this._pages === 6) {
this.displayNode(this.first);
this.item(this.first, 1);
for (let i = 0; i < this._pages; i++) {
this.displayNode(this.nodes[i]);
this.item(this.nodes[i], i + 2);
}
} else {
this.displayNode(this.one, this.two, this.three, this.four, this.five)
if (this._current - 3 > 1 && this._current + 3 < this._pages) { // 左边溢出 右边溢出
this.displayNode(this.first, this.last, this.doubleLeft, this.doubleRight);
this.item(this.first, 1);
this.item(this.last, this._pages);
this.item(this.one, this._current - 2);
this.item(this.two, this._current - 1);
this.item(this.three, this._current);
this.item(this.four, this._current + 1);
this.item(this.five, this._current + 2);
} else if (this._current - 3 === 1 && this._current + 3 < this._pages) {//左边刚好 右边溢出
this.displayNode(this.first, this.last, this.doubleRight);
this.item(this.first, 1)
this.item(this.last, this._pages)
for (let i = 0; i < this.nodes.length; i++) {
this.item(this.nodes[i], i + 2);
}
} else if (this._current - 3 < 1 && this._current + 3 < this._pages) { //左边不够 右边溢出
this.displayNode(this.last, this.doubleRight);
this.item(this.last, this._pages)
for (let i = 0; i < this.nodes.length; i++) {
this.item(this.nodes[i], i + 1);
}
} else if (this._current - 3 > 1 && this._current + 3 === this._pages) {// 左边溢出 右边刚好
this.displayNode(this.first, this.last, this.doubleLeft);
this.item(this.first, 1);
this.item(this.last, this._pages);
this.item(this.nodes[0], this._pages - 5);
this.item(this.nodes[1], this._pages - 4);
this.item(this.nodes[2], this._pages - 3);
this.item(this.nodes[3], this._pages - 2);
this.item(this.nodes[4], this._pages - 1);
} else if (this._current - 3 === 1 && this._current + 3 === this._pages) {// 左边刚好 右边刚好
this.displayNode(this.first, this.last);
this.item(this.first, 1);
for (let i = 0; i < this._pages; i++) {
this.displayNode(this.nodes[i]);
this.item(this.nodes[i], i + 2);
}
this.item(this.last, 7);
} else if (this._current - 3 < 1 && this._current + 3 === this._pages) {// 左边不够 右边刚好
this.displayNode(this.last);
this.item(this.last, this._pages)
for (let i = 0; i < this.nodes.length; i++) {
this.item(this.nodes[i], i + 1);
}
} else if (this._current - 3 > 1 && this._current + 3 > this._pages) { //左边溢出 右边不够
this.displayNode(this.first, this.doubleLeft)
this.item(this.first, 1);
this.item(this.nodes[0], this._pages - 4);
this.item(this.nodes[1], this._pages - 3);
this.item(this.nodes[2], this._pages - 2);
this.item(this.nodes[3], this._pages - 1);
this.item(this.nodes[4], this._pages - 0);
} else if (this._current - 3 === 1 && this._current + 3 > this._pages) { //左边刚好 右边不够
this.displayNode(this.first);
this.item(this.first, 1);
this.item(this.nodes[0], this._pages - 4);
this.item(this.nodes[1], this._pages - 3);
this.item(this.nodes[2], this._pages - 2);
this.item(this.nodes[3], this._pages - 1);
this.item(this.nodes[4], this._pages - 0);
} else if (this._current - 3 < 1 && this._current + 3 > this._pages) { //左边不够 右边不够
//限定了 pages>6 这种情况不存在
}
}
}
item(el, val) {
el.val = val;
el.textContent = val;
if (val === this._current) {
el.setAttribute('selected', '');
} else {
el.removeAttribute('selected');
}
}
displayNode() {
[...arguments].forEach(a => a.style.display = 'flex');
}
hiddenNode(n) {
[...arguments].forEach(a => a.style.display = 'none');
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'current') {
this.dispatchEvent(new CustomEvent('onChange', {
detail: {
page: parseInt(newValue),
pageSize: this._pageSize
}
}));
} else if (name === 'total') {
this.dispatchEvent(new CustomEvent('onChange', {
detail: {
total: parseInt(newValue),
current: this._current,
pageSize: this._pageSize
}
}));
this.match()
}
}
}
if (!customElements.get('lit-pagination')) {
customElements.define('lit-pagination', LitPagination);
}
class LitSelect extends HTMLElement {
static get observedAttributes() {
return [
'value',//默认值
'default-value',//默认值
'placeholder',//placeholder
'disabled',
'loading',//是否处于加载状态
'allow-clear',//是否允许清除
'show-search',//是否允许搜索
'list-height',//设置弹窗滚动高度 默认256px
'border',//是否显示边框
'mode',// mode='multiple'多选
];
}
get value() {
return this.getAttribute('value') || this.defaultValue;
}
set value(value) {
this.setAttribute('value', value);
}
get border() {
return this.getAttribute('border') || 'true';
}
set border(value) {
if (value) {
this.setAttribute('border', 'true');
} else {
this.setAttribute('border', 'false');
}
}
get listHeight() {
return this.getAttribute('list-height') || '256px';
}
set listHeight(value) {
this.setAttribute('list-height', value);
}
get defaultPlaceholder() {
return this.getAttribute('placeholder') || '请选择';
}
get showSearch() {
return this.hasAttribute('show-search');
}
set defaultValue(value) {
this.setAttribute('default-value', value);
}
get defaultValue() {
return this.getAttribute('default-value') || '';
}
set placeholder(value) {
this.setAttribute('placeholder', value);
}
get placeholder() {
return this.getAttribute('placeholder') || this.defaultPlaceholder;
}
get loading() {
return this.hasAttribute('loading');
}
set loading(value) {
if (value) {
this.setAttribute('loading', '');
} else {
this.removeAttribute('loading')
}
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: inline-flex;
position: relative;
overflow: visible;
cursor: pointer;
transition: all .3s;
border-radius: 2px;
outline: none;
-webkit-user-select:none ;
-moz-user-select:none;
user-select:none;
/*width: 100%;*/
}
:host(:not([border])),
:host([border='true']){
border: 1px solid #dcdcdc;
}
input{
border: 0;
outline: none;
background-color: transparent;
cursor: pointer;
transition: all .3s;
-webkit-user-select:none ;
-moz-user-select:none;
user-select:none;
display: inline-flex;
}
:host(:not([mode])) input{
width: 100%;
}
:host([mode]) input{
padding: 6px 0px;
}
:host([mode]) .root{
padding: 1px 8px;
}
.root{
position: relative;
padding: 6px 8px;
display: flex;
align-items: center;
justify-content: space-between;
transition: all .3s;
border-radius: 2px;
background-color: #fff;
outline: none;
font-size: 1rem;
z-index: 2;
-webkit-user-select:none ;
-moz-user-select:none;
user-select:none;
width: 100%;
}
.body{
max-height: ${this.listHeight};
position: absolute;
top: 100%;
z-index: 99;
padding-top: 5px;
margin-top: 2px;
background-color: #fff;
width: 100%;
transition: all 0.2s;
transform: scaleY(.6);
visibility: hidden;
opacity: 0;
transform-origin: top center;
display: block;
flex-direction: column;
box-shadow: 0 5px 15px 0px #00000033;
border-radius: 2px;
overflow: auto;
}
.icon{
pointer-events: none;
}
.noSelect{
-webkit-touch-callout:none; /* iOS Safari */
-webkit-user-select:none; /* Chrome/Safari/Opera */
-khtml-user-select:none; /* Konqueror */
-moz-user-select:none; /* Firefox */
-ms-user-select:none; /* Internet Explorer/Edge */
user-select:none; /* Non-prefixed version */
}
:host(:not([border]):not([disabled]):focus),
:host([border='true']:not([disabled]):focus),
:host(:not([border]):not([disabled]):hover),
:host([border='true']:not([disabled]):hover){
border:1px solid #42b983
}
:host(:not([disabled]):focus) .body,
:host(:not([disabled]):focus-within) .body{
transform: scaleY(1);
opacity: 1;
z-index: 99;
visibility: visible;
}
:host(:not([disabled]):focus) input{
color: #bebebe;
}
:host(:not([border])[disabled]) *,
:host([border='true'][disabled]) *{
background-color: #f5f5f5;
color: #b7b7b7;
cursor: not-allowed;
}
:host([border='false'][disabled]) *{
color: #b7b7b7;
cursor: not-allowed;
}
:host([loading]) .loading{
display: flex;
}
:host([loading]) .icon{
display: none;
}
:host(:not([loading])) .loading{
display: none;
}
:host(:not([loading])) .icon{
display: flex;
}
:host(:not([allow-clear])) .clear{
display: none;
}
.clear{
display: none;
color: #bfbfbf;
}
.clear:hover{
color: #8c8c8c;
}
.search{
display: none;
color: #bfbfbf;
}
.multipleRoot{
display: flex;
flex-direction: column;
flex-wrap: wrap;
flex-flow: wrap;
align-items: center;
width: 100%;
}
.tag{
display: inline-flex;
align-items: center;
background-color: #f5f5f5;
padding: 1px 4px;
height: auto;
font-size: .75rem;
font-weight: bold;
color: #242424;
overflow: auto;
position: relative;
margin-right: 4px;
margin-top: 1px;
margin-bottom: 1px;
}
.tag-close{
font-size: .8rem;
padding: 2px;
margin-left: 0px;
color: #999999;
}
.tag-close:hover{
color: #333;
}
</style>
<div class="root noSelect" tabindex="0" hidefocus="true">
<div class="multipleRoot">
<input placeholder="${this.placeholder}" style="" autocomplete="off" ${this.showSearch ? '' : 'readonly'} tabindex="0">
</div><!--多选-->
<lit-loading class="loading" size="12"></lit-loading>
<!--<lit-icon class="icon" name='down' color="#c3c3c3"></lit-icon>-->
<svg class="icon" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;fill: #c3c3c3">
<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>
</svg>
<!-- <lit-icon class="clear" name='close-circle-fill'></lit-icon>-->
<svg class="clear" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">
<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>
</svg>
<!-- <lit-icon class="search" name='search'></lit-icon>-->
<svg class="search" viewBox="0 0 1024 1024" aria-hidden="true" style="width: 16px;width: 16px;">
<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>
</svg>
</div>
<div class="body">
<slot></slot>
<slot name="footer"></slot>
</div>
`
}
isMultiple() {
return this.hasAttribute('mode') && this.getAttribute('mode') === 'multiple'
}
newTag(value, text) {
let tag = document.createElement('div');
let icon = document.createElement('lit-icon');
icon.classList.add('tag-close')
icon.name = 'close'
let span = document.createElement('span');
tag.classList.add('tag');
span.dataset['value'] = value;
span.textContent = text;
tag.append(span);
tag.append(icon);
icon.onclick = ev => {
tag.parentElement.removeChild(tag);
this.querySelector(`lit-select-option[value=${value}]`).removeAttribute('selected')
if (this.shadowRoot.querySelectorAll('.tag').length == 0) {
this.inputElement.style.width = 'auto';
this.inputElement.placeholder = this.defaultPlaceholder;
}
ev.stopPropagation();
}
tag.value = value;
tag.dataset['value'] = value;
tag.text = text;
tag.dataset['text'] = text;
return tag;
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.tabIndex = 0;//设置当前组件为可以获取焦点
this.focused = false;
this.inputElement = this.shadowRoot.querySelector('input');
this.inputElement.style.width = '100%'
this.clearElement = this.shadowRoot.querySelector('.clear');
this.iconElement = this.shadowRoot.querySelector('.icon');
this.searchElement = this.shadowRoot.querySelector('.search');
this.multipleRootElement = this.shadowRoot.querySelector('.multipleRoot');
// console.log(this.multipleRootElement);
//点击清理 清空input值展示placeholder
this.clearElement.onclick = ev => {
if (this.isMultiple()) {
let delNodes = []
this.multipleRootElement.childNodes.forEach(a => {
if (a.tagName === 'DIV') {
delNodes.push(a);
}
})
for (let i = 0; i < delNodes.length; i++) {
delNodes[i].remove();
}
if (this.shadowRoot.querySelectorAll('.tag').length == 0) {
this.inputElement.style.width = 'auto';
this.inputElement.placeholder = this.defaultPlaceholder;
}
}
this.querySelectorAll('lit-select-option').forEach(a => a.removeAttribute('selected'));
this.inputElement.value = ''
this.clearElement.style.display = 'none';
this.iconElement.style.display = 'flex';
this.blur();
ev.stopPropagation();//这里不会因为点击清理而触发 选择栏目显示或者隐藏
this.dispatchEvent(new CustomEvent('onClear', { detail: ev }))//向外派发清理事件
}
//初始化时遍历所有的option节点
this.initOptions();
//当前控件点击时 如果时select本身 需要显示 或 隐藏选择栏目通过this.focused变量控制默认为false
this.onclick = ev => {
if (ev.target.tagName === 'LIT-SELECT') {
if (!this.focused) {
this.inputElement.focus();
this.focused = true;
} else {
this.blur();
this.focused = false;
}
}
}
this.onmouseover = this.onfocus = ev => {
// console.log('onmouseover',ev);
if (this.hasAttribute('allow-clear')) {
if (this.inputElement.value.length > 0 || this.inputElement.placeholder !== this.defaultPlaceholder) {
this.clearElement.style.display = 'flex'
this.iconElement.style.display = 'none';
} else {
this.clearElement.style.display = 'none'
this.iconElement.style.display = 'flex';
}
}
}
this.onmouseout = this.onblur = ev => {
// console.log('onmouseout',ev);
if (this.hasAttribute('allow-clear')) {
this.clearElement.style.display = 'none';
this.iconElement.style.display = 'flex';
}
this.focused = false;
}
//输入框获取焦点时value值 暂存于 placeholder 然后value值清空这样值会以placeholder形式灰色展示鼠标位于第一个字符
this.inputElement.onfocus = ev => {
if (this.hasAttribute('disabled')) return;//如果控件处于disabled状态 直接忽略
if (this.inputElement.value.length > 0) {
this.inputElement.placeholder = this.inputElement.value;
this.inputElement.value = ''
}
if (this.hasAttribute('show-search')) {//如果有show-search属性 需要显示放大镜,隐藏向下的箭头
this.searchElement.style.display = 'flex';
this.iconElement.style.display = 'none';
}
this.querySelectorAll('lit-select-option').forEach(a => {//input获取焦点时显示所有可选项相当于清理了搜索结果
a.style.display = 'flex';
})
}
//当输入框失去焦点的时候 placeholder 的值 保存到value上input显示值
this.inputElement.onblur = ev => {
if (this.hasAttribute('disabled')) return;//如果控件处于disabled状态 直接忽略
if (this.isMultiple()) {
if (this.hasAttribute('show-search')) {//如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标
this.searchElement.style.display = 'none';
this.iconElement.style.display = 'flex';
}
} else {
if (this.inputElement.placeholder !== this.defaultPlaceholder) {//如果placeholder为 请输入(默认值)不做处理
this.inputElement.value = this.inputElement.placeholder; //placeholder 保存的值放入 value中
this.inputElement.placeholder = this.defaultPlaceholder;//placeholder 值为 默认值(请输入)
}
if (this.hasAttribute('show-search')) {//如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标
this.searchElement.style.display = 'none';
this.iconElement.style.display = 'flex';
}
}
}
//输入框每次文本变化 会匹配搜索的option 显示或者隐藏,达到搜索的效果
this.inputElement.oninput = ev => {
// console.log(ev.target.value);
let els = [...this.querySelectorAll('lit-select-option')];
if (!ev.target.value) {
els.forEach(a => a.style.display = 'flex');
} else {
els.forEach(a => {
let value = a.getAttribute('value');
// console.log(value.toLowerCase(), ev.target.value.toLowerCase());
if (value.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 ||
a.textContent.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1) {
a.style.display = 'flex';
} else {
a.style.display = 'none';
}
})
}
}
//输入框按下回车键自动输入当前搜索出来的第一行及display!='none'的第一个,搜索会隐藏其他行
this.inputElement.onkeydown = ev => {
if (ev.key === 'Backspace') {
if (this.isMultiple()) {
let tag = this.multipleRootElement.lastElementChild.previousElementSibling;
if (tag) {
console.log(tag.value);
this.querySelector(`lit-select-option[value=${tag.value}]`).removeAttribute('selected');
tag.remove()
if (this.shadowRoot.querySelectorAll('.tag').length == 0) {
this.inputElement.style.width = 'auto';
this.inputElement.placeholder = this.defaultPlaceholder;
}
}
} else {
this.clear();
this.dispatchEvent(new CustomEvent('onClear', { detail: ev }))//向外派发清理事件
}
} else if (ev.key === 'Enter') {
let filter = [...this.querySelectorAll('lit-select-option')].filter(a => a.style.display !== 'none');
if (filter.length > 0) {
this.inputElement.value = filter[0].textContent;
this.inputElement.placeholder = filter[0].textContent;
this.blur();
this.dispatchEvent(new CustomEvent('change', {
detail: {
selected: true,
value: filter[0].getAttribute('value'),
text: filter[0].textContent
}
}));//向外层派发change事件返回当前选中项
}
}
}
}
initOptions() {
this.querySelectorAll('lit-select-option').forEach(a => {
//如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本
if (this.isMultiple()) {
a.setAttribute('check', '');
if (a.getAttribute('value') === this.defaultValue) {
let tag = this.newTag(a.getAttribute('value'), a.textContent);
this.multipleRootElement.insertBefore(tag, this.inputElement);
this.inputElement.placeholder = '';
this.inputElement.value = '';
this.inputElement.style.width = '1px';
a.setAttribute('selected', '');
}
// this.inputElement.focus();
} else {
if (a.getAttribute('value') === this.defaultValue) {
this.inputElement.value = a.textContent;
a.setAttribute('selected', '');
}
}
//每个option设置onSelected事件 接受当前点击的option
a.addEventListener('onSelected', (e) => {
// console.log(e.detail);
//所有option设置为未选中状态
if (this.isMultiple()) {//多选
if (a.hasAttribute('selected')) {
// console.log(e.detail.value);
let tag = this.shadowRoot.querySelector(`div[data-value=${e.detail.value}]`);
// console.log(tag);
tag.parentElement.removeChild(tag);
e.detail.selected = false;
} else {
let tag = this.newTag(e.detail.value, e.detail.text);
this.multipleRootElement.insertBefore(tag, this.inputElement);
this.inputElement.placeholder = '';
this.inputElement.value = '';
this.inputElement.style.width = '1px';
}
if (this.shadowRoot.querySelectorAll('.tag').length == 0) {
this.inputElement.style.width = 'auto';
this.inputElement.placeholder = this.defaultPlaceholder;
}
this.inputElement.focus();
} else {//单选
[...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected'))
this.blur();//失去焦点,隐藏选择栏目列表
this.inputElement.value = e.detail.text;
}
//设置当前option为选择状态
if (a.hasAttribute('selected')) {
a.removeAttribute('selected')
} else {
a.setAttribute('selected', '')
}
//设置input的值为当前选择的文本
this.dispatchEvent(new CustomEvent('change', { detail: e.detail }));//向外层派发change事件返回当前选中项
})
})
}
//js调用清理选项
clear() {
this.inputElement.value = '';
this.inputElement.placeholder = this.defaultPlaceholder;
}
//重置为默认值
reset() {
this.querySelectorAll('lit-select-option').forEach(a => {
//如果节点的值为 当前控件的默认值 defalut-value则 显示该值对应的option文本
[...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected'))
if (a.getAttribute('value') === this.defaultValue) {
this.inputElement.value = a.textContent;
a.setAttribute('selected', '');
}
})
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'value' && this.inputElement) {
if (newValue) {
[...this.querySelectorAll('lit-select-option')].forEach(a => {
if (a.getAttribute('value') === newValue) {
a.setAttribute('selected', '');
this.inputElement.value = a.textContent;
} else {
a.removeAttribute('selected')
}
})
} else {
this.clear();
}
}
}
set dataSource(value) {
value.forEach(a => {
let option = document.createElement('lit-select-option');
option.setAttribute('value', a.key);
option.textContent = a.val;
this.append(option)
})
this.initOptions();
}
}
if (!customElements.get('lit-select')) {
customElements.define('lit-select', LitSelect);
}
class LitSelectGroup extends HTMLElement {
static get observedAttributes() {
return ['label']
}
get label() {
return this.getAttribute('label') || '';
}
set label(value) {
this.setAttribute('label', value);
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: flex;
flex-direction: column;
/*padding-left: 10px;*/
}
.lab{
padding: 8px 10px 8px 10px;
font-size: .5rem;
color: #8c8c8c;
}
::slotted(lit-select-option){
padding-left: 20px;
}
</style>
<div class="lab">${this.label}</div>
<slot></slot>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('lit-select-group')) {
customElements.define('lit-select-group', LitSelectGroup);
}
class LitSelectOption extends HTMLElement {
static get observedAttributes() {
return ['selected', 'disabled', 'check']
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: flex;
padding: 8px 10px;
transition: all .3s;
color: #333;
tab-index: -1;
overflow: scroll;
align-items: center;
justify-content: space-between;
}
:host(:not([disabled])[selected]){
background-color: #e9f7fe;
font-weight: bold;
}
:host(:not([disabled]):not([selected]):hover){
background-color: #f5f5f5;
}
:host([disabled]){
cursor: not-allowed;
color: #bfbfbf;
}
:host([selected][check]) .check{
display: flex;
}
:host(:not([selected])) .check{
display: none;
}
:host(:not([check])) .check{
display: none;
}
</style>
<slot></slot>
<lit-icon class="check" name="check"></lit-icon>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
if (!this.hasAttribute('disabled')) {
this.onclick = ev => {
this.dispatchEvent(new CustomEvent('onSelected', {
detail: {
selected: true,
value: this.getAttribute('value'),
text: this.textContent
}
}))
}
}
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('lit-select-option')) {
customElements.define('lit-select-option', LitSelectOption);
}
class LitTable extends HTMLElement {
static get observedAttributes() {
return ['scroll-y', 'selectable', 'defaultOrderColumn']
}
get selectable() {
return this.hasAttribute('selectable');
}
set selectable(value) {
if (value) {
this.setAttribute('selectable', '');
} else {
this.removeAttribute('selectable');
}
}
get scrollY() {
return this.getAttribute('scroll-y') || 'auto';
}
set scrollY(value) {
this.setAttribute('scroll-y', value);
}
get dataSource() {
return this.ds || [];
}
set dataSource(value) {
this.ds = value;
if (this.hasAttribute('tree')) {
this.renderTreeTable();
} else {
this.renderTable();
}
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: grid;
grid-template-columns: repeat(1,1fr);
overflow: auto;
/*width: 500px;*/
width: 100%;
height: 100%;
}
.tr{
display: grid;
transition: all .3s;
}
.tr:nth-of-type(even){
background-color: #fcfcfc;
}
/*.tr:not(:last-of-type):not(:first-of-type){*/
/* border-top: 1px solid #f0f0f0;*/
/*}*/
/*.tr:last-of-type{*/
/* border-top: 1px solid #f0f0f0;*/
/* border-bottom: 1px solid #f0f0f0;*/
/*}*/
.tr{
background-color: #fff;
}
.tr:hover{
background-color: #f3f3f3; /*antd #fafafa 42b983*/
}
.td{
background-color: inherit;
box-sizing: border-box;
padding: 10px;
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
height: auto;
/*overflow: auto;*/
border-left: 1px solid #f0f0f0;
}
.td-order{
/*background: green;*/
}
.td-order:before{
}
.td:last-of-type{
border-right: 1px solid #f0f0f0;
}
.table{
color: #262626;
}
:host(:not([noheader])) .thead{
display: grid;
position: sticky;
top: 0;
font-weight: bold;
font-size: .9rem;
color: #fff;
/*width: 100%;*/
background-color: #42b983;
z-index: 1;
}
/*配置有 noheader 表示不限时表头tbody上添加 border-top*/
:host([noheader]) .thead{
display: none;
position: sticky;
top: 0;
font-weight: bold;
font-size: .9rem;
color: #fff;
/*width: 100%;*/
background-color: #42b983;
z-index: 1;
}
:host([noheader]) .tbody{
border-top: 1px solid #f0f0f0;
}
.tbody{
width: 100%;
height: ${this.scrollY};
display: grid;
grid-template-columns: 1fr;
row-gap: 1px;
column-gap: 1px;
background-color: #f0f0f0;
border-bottom: 1px solid #f0f0f0;
/*overflow: auto;*/
${this.scrollY === 'auto' ? '' : 'overflow-y: auto'};
}
.th{
display: grid;
background-color: #42b983;
/*position: sticky;*/
/*top: 0;*/
}
.tree-icon{
font-size: 1.2rem;
width: 20px;
height: 20px;
padding-right: 5px;
padding-left: 5px;
cursor: pointer;
}
.tree-icon:hover{
color: #42b983;
}
.row-checkbox,row-checkbox-all{
}
.up-svg{
position: absolute;
right: 5px;
top: 8px;
width: 15px;
height: 15px;
}
.down-svg{
position: absolute;
right: 5px;
bottom: 8px;
width: 15px;
height: 15px;
}
</style>
<slot id="slot" style="display: none"></slot>
<div class="table">
<div class="thead"></div>
<div class="tbody"></div>
</div>
`
}
/*根据column[]嵌套结构得到 grid css布局描述*/
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.st = this.shadowRoot.querySelector('#slot');
this.tableElement = this.shadowRoot.querySelector('.table');
this.theadElement = this.shadowRoot.querySelector('.thead');
this.tbodyElement = this.shadowRoot.querySelector('.tbody');
this.tableColumns = this.querySelectorAll('lit-table-column');
this.colCount = this.tableColumns.length;
this.st.addEventListener('slotchange', (event) => {
this.theadElement.innerHTML = '';
setTimeout(() => {
this.columns = this.st.assignedElements();
let rowElement = document.createElement('div');
rowElement.classList.add('th');
if (this.selectable) {
let box = document.createElement('div');
box.style.display = 'flex';
box.style.justifyContent = 'center';
box.style.alignItems = 'center';
box.style.gridArea = "_checkbox_";
box.classList.add('td');
box.style.backgroundColor = "#ffffff66";
let checkbox = document.createElement('lit-checkbox');
checkbox.classList.add('row-checkbox-all');
// checkbox.style.boxShadow = '0 0 1px #fff';
// console.log(checkbox.shadowRoot.querySelector('input'));
checkbox.onchange = e => {
this.shadowRoot.querySelectorAll('.row-checkbox').forEach(a => a.checked = e.detail.checked);
if (e.detail.checked) {
this.shadowRoot.querySelectorAll('.tr').forEach(a => a.setAttribute('checked', ''));
} else {
this.shadowRoot.querySelectorAll('.tr').forEach(a => a.removeAttribute('checked'));
}
}
box.appendChild(checkbox);
rowElement.appendChild(box);
}
// let getGridDesc = (columns)=>{
// columns.forEach(a=>{
// // console.log(a);
// if(a.tagName==='LIT-TABLE-GROUP'){
// let children = [...a.querySelectorAll('lit-table-column')];
// // console.log(children);
// getGridDesc(children)
// }else{
// // console.log([...a.querySelectorAll('lit-table-column')]);
// }
// })
// }
// getGridDesc(this.columns);
let area = [], gridTemplateColumns = [];
let resolvingArea = (columns, x, y) => {
columns.forEach((a, i) => {
// console.log(a.getAttribute('key'),i);
if (!area[y]) area[y] = []
let key = a.getAttribute('key') || a.getAttribute('title')
if (a.tagName === 'LIT-TABLE-GROUP') {
let len = a.querySelectorAll('lit-table-column').length;
let children = [...a.children].filter(a => a.tagName !== 'TEMPLATE');
if (children.length > 0) {
resolvingArea(children, x, y + 1);
}
for (let j = 0; j < len; j++) {
area[y][x] = { x, y, t: key };
x++;
}
let h = document.createElement('div');
h.classList.add('td');
h.style.justifyContent = a.getAttribute('align')
h.style.borderBottom = '1px solid #f0f0f0'
h.style.gridArea = key;
h.innerText = a.title;
if (a.hasAttribute('fixed')) {
this.fixed(h, a.getAttribute('fixed'), "#42b983")
}
rowElement.append(h);
} else if (a.tagName === 'LIT-TABLE-COLUMN') {
area[y][x] = { x, y, t: key };
x++;
let h = document.createElement('div');
h.classList.add('td');
if (a.hasAttribute('order')) {
h.sortType = 0;
h.classList.add('td-order');
h.style.position = "relative"
let NS = "http://www.w3.org/2000/svg";
let upSvg = document.createElementNS(NS, "svg");
let upPath = document.createElementNS(NS, "path");
upSvg.setAttribute('fill', '#efefef');
upSvg.setAttribute('viewBox', '0 0 1024 1024');
upSvg.setAttribute('stroke', '#000000');
upSvg.classList.add('up-svg');
// 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");
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");
// upPath.setAttribute("fill", "#ffffff");
// upPath.setAttribute("stroke-width", "2px");
// upPath.setAttribute("stroke", "rgba(207, 219, 230, 1)");
// upPath.setAttribute("marker-end", "url(#markerArrow)");
upSvg.appendChild(upPath);
let downSvg = document.createElementNS(NS, "svg");
let downPath = document.createElementNS(NS, "path");
downSvg.setAttribute('fill', '#efefef');
downSvg.setAttribute('viewBox', '0 0 1024 1024');
downSvg.setAttribute('stroke', '#efefef');
downSvg.classList.add('down-svg');
// 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");
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");
// downPath.setAttribute("fill", "#ffffff");
// downPath.setAttribute("stroke-width", "2px");
// downPath.setAttribute("stroke", "rgba(207, 219, 230, 1)");
// downPath.setAttribute("marker-end", "url(#markerArrow)");
downSvg.appendChild(downPath)
if (i == 0) {
h.sortType = 2; // 默认以第一列 降序排序 作为默认排序
upSvg.setAttribute('fill', '#efefef');
downSvg.setAttribute('fill', '#333');
}
h.appendChild(upSvg);
h.appendChild(downSvg);
h.onclick = ev => {
this.shadowRoot.querySelectorAll('.td-order svg').forEach(it => {
it.setAttribute('fill', '#efefef');
it.setAttribute('fill', '#efefef');
it.sortType = 0;
})
if (h.sortType == undefined || h.sortType == null) {
h.sortType = 0;
} else if (h.sortType === 2) {
h.sortType = 0;
} else {
h.sortType += 1;
}
// if(h.sortType == 2){
// h.sortType = 1
// }else{
// h.sortType = 2
// }
switch (h.sortType) {
case 1:
upSvg.setAttribute('fill', '#333');
downSvg.setAttribute('fill', '#efefef');
break;
case 2:
upSvg.setAttribute('fill', '#efefef');
downSvg.setAttribute('fill', '#333');
break;
default:
upSvg.setAttribute('fill', "#efefef");
downSvg.setAttribute('fill', "#efefef");
break;
}
this.dispatchEvent(new CustomEvent("ColumnClick", {
detail: {
sort: h.sortType, key: key
}, composed: true
}))
}
}
h.style.justifyContent = a.getAttribute('align')
gridTemplateColumns.push(a.getAttribute('width') || '1fr');
h.style.gridArea = key;
let titleLabel = document.createElement("label");
titleLabel.textContent = a.title;
// h.innerText = a.title;
h.appendChild(titleLabel);
if (a.hasAttribute('fixed')) {
this.fixed(h, a.getAttribute('fixed'), "#42b983")
}
rowElement.append(h);
}
// console.log(str)
// console.log(a.title,i,a.querySelectorAll('lit-table-column').length);
})
}
resolvingArea(this.columns, 0, 0);
area.forEach((rows, j, array) => {
for (let i = 0; i < this.colCount; i++) {
if (!rows[i]) rows[i] = array[j - 1][i];
}
})
// console.log(a.map(aa=>aa.t).join(' '));
// console.log(gridTemplateColumns.join(' '));
this.gridTemplateColumns = gridTemplateColumns.join(' ');
if (this.selectable) {
let s = area.map(a => '"_checkbox_ ' + (a.map(aa => aa.t).join(' ')) + '"').join(' ');
rowElement.style.gridTemplateColumns = "60px " + gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`
rowElement.style.gridTemplateAreas = s
} else {
let s = area.map(a => '"' + (a.map(aa => aa.t).join(' ')) + '"').join(' ');
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`
rowElement.style.gridTemplateAreas = s
}
this.theadElement.append(rowElement);
if (this.hasAttribute('tree')) {
this.renderTreeTable();
} else {
this.renderTable();
}
});
});
// this.shadowRoot.addEventListener("load", function (event) {
// console.log("DOM fully loaded and parsed");
// });
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
}
fixed(td, placement, bgColor, zIndex) {
td.style.position = 'sticky';
if (placement === "left") {
td.style.left = '0px';
// td.style.borderRight = '1px solid #f0f0f0';
td.style.boxShadow = '3px 0px 5px #33333333'
// td.style.backgroundColor=bgColor
} else if (placement === "right") {
td.style.right = '0px';
td.style.boxShadow = '-3px 0px 5px #33333333'
// td.style.borderLeft = '1px solid #f0f0f0';
// td.style.backgroundColor=bgColor
}
}
/*渲染成表格*/
renderTable() {
let that = this;
if (!this.columns) return;
if (!this.ds) return; // 如果没有设置数据源,直接返回
this.tbodyElement.innerHTML = '';//清空表格内容
// this.style.gridTemplateRows = `repeat(${this.ds.length}*2 ,1fr)`;
this.ds.forEach(rowData => {
let rowElement = document.createElement('div');
rowElement.classList.add('tr');
rowElement.data = rowData;
let gridTemplateColumns = []
//如果table配置了selectable选择行模式 单独在行头部添加一个 checkbox
if (this.selectable) {
let box = document.createElement('div');
box.style.display = 'flex';
box.style.justifyContent = 'center';
box.style.alignItems = 'center';
box.classList.add('td');
let checkbox = document.createElement('lit-checkbox');
checkbox.classList.add('row-checkbox');
checkbox.onchange = (e) => {//checkbox 的是否选中 会影响 行对应的 div上是否有 checked属性用于标记
if (e.detail.checked) {
rowElement.setAttribute('checked', "");
} else {
rowElement.removeAttribute('checked');
}
}
box.appendChild(checkbox);
rowElement.appendChild(box);
}
this.tableColumns.forEach(cl => {
let dataIndex = cl.getAttribute('data-index');
gridTemplateColumns.push(cl.getAttribute('width') || '1fr')
if (cl.template) {//如果自定义渲染,从模版中渲染得到节点
let cloneNode = cl.template.render(rowData).content.cloneNode(true);
// cloneNode.classList.add('td');
let d = document.createElement('div');
d.classList.add('td');
d.style.justifyContent = cl.getAttribute('align')
if (cl.hasAttribute('fixed')) {
this.fixed(d, cl.getAttribute('fixed'), "#ffffff")
}
d.append(cloneNode);
rowElement.append(d);
} else {
let td = document.createElement('div');
td.classList.add('td');
td.style.justifyContent = cl.getAttribute('align')
if (cl.hasAttribute('fixed')) {
this.fixed(td, cl.getAttribute('fixed'), "#ffffff")
}
// td.style.position='sticky';
// td.innerHTML = rowData[dataIndex];
td.innerHTML = `<code style="padding:0;margin:0">${rowData[dataIndex].toString().replace('\n', "")}</code>`;
// console.log(cl,cl.template);
rowElement.append(td);
}
})
if (this.selectable) { //如果 带选择的table 前面添加一个 60px的列
rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
} else {
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
}
rowElement.onclick = e => {
this.dispatchEvent(new CustomEvent('onRowClick', { detail: rowData, composed: true }));
}
this.tbodyElement.append(rowElement);
})
}
/*渲染树表结构*/
renderTreeTable() {
if (!this.columns) return;
if (!this.ds) return;
this.tbodyElement.innerHTML = '';
/*通过list 构建 tree 结构*/
let ids = JSON.parse(this.getAttribute('tree') || `["id","pid"]`);
let toTreeData = (data, id, pid) => {
let cloneData = JSON.parse(JSON.stringify(data));
return cloneData.filter(father => {
let branchArr = cloneData.filter(child => father[id] == child[pid]);
branchArr.length > 0 ? father['children'] = branchArr : '';
return !father[pid];
});
}
let treeData = toTreeData(this.ds, ids[0], ids[1]);//
// console.log(treeData);
let offset = 30;
let offsetVal = offset;
const drawRow = (arr, parentNode) => {
arr.forEach(rowData => {
let rowElement = document.createElement('div');
rowElement.classList.add('tr');
rowElement.data = rowData;
let gridTemplateColumns = [];
if (this.selectable) {
let box = document.createElement('div');
box.style.display = 'flex';
box.style.justifyContent = 'center';
box.style.alignItems = 'center';
box.classList.add('td');
let checkbox = document.createElement('lit-checkbox');
checkbox.classList.add('row-checkbox');
checkbox.onchange = (e) => {
if (e.detail.checked) {
rowElement.setAttribute('checked', "");
} else {
rowElement.removeAttribute('checked');
}
const changeChildNode = (rowElement, checked) => {
let id = rowElement.getAttribute('id');
let pid = rowElement.getAttribute('pid');
this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => {
a.querySelector('.row-checkbox').checked = checked;
if (checked) {
a.setAttribute('checked', '');
} else {
a.removeAttribute('checked');
}
changeChildNode(a, checked);
});
};
changeChildNode(rowElement, e.detail.checked);
}
box.appendChild(checkbox);
rowElement.appendChild(box);
}
this.tableColumns.forEach((cl, index) => {
let dataIndex = cl.getAttribute('data-index');
gridTemplateColumns.push(cl.getAttribute('width') || '1fr')
let td;
if (cl.template) {
let cloneNode = cl.template.render(rowData).content.cloneNode(true);
// cloneNode.classList.add('td');
td = document.createElement('div');
td.classList.add('td');
td.style.justifyContent = cl.getAttribute('align')
if (cl.hasAttribute('fixed')) {
this.fixed(td, cl.getAttribute('fixed'), "#ffffff")
}
td.append(cloneNode);
} else {
td = document.createElement('div');
td.classList.add('td');
td.style.justifyContent = cl.getAttribute('align')
if (cl.hasAttribute('fixed')) {
this.fixed(td, cl.getAttribute('fixed'), "#ffffff")
}
// td.style.position='sticky';
td.innerHTML = rowData[dataIndex];
// console.log(cl,cl.template);
}
if (index === 0) {
if (rowData.children && rowData.children.length > 0) {
let btn = document.createElement('lit-icon');
btn.classList.add('tree-icon');
btn.name = 'minus-square';
td.insertBefore(btn, td.firstChild);
td.style.paddingLeft = (offsetVal - 30) + 'px';
btn.onclick = (e) => {
const foldNode = (rowElement) => {
let id = rowElement.getAttribute('id');
let pid = rowElement.getAttribute('pid');
this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => {
let id = a.getAttribute('id');
let pid = a.getAttribute('pid');
a.style.display = 'none';
foldNode(a);
});
if (rowElement.querySelector('.tree-icon')) {
rowElement.querySelector('.tree-icon').name = 'plus-square';
}
rowElement.removeAttribute('expand');
};
const expendNode = (rowElement) => {
let id = rowElement.getAttribute('id');
let pid = rowElement.getAttribute('pid');
this.shadowRoot.querySelectorAll(`div[pid=${id}]`).forEach(a => {
let id = a.getAttribute('id');
let pid = a.getAttribute('pid');
a.style.display = '';
// expendNode(a);
});
if (rowElement.querySelector('.tree-icon')) {
rowElement.querySelector('.tree-icon').name = 'minus-square';
}
rowElement.setAttribute('expand', '');
}
if (rowElement.hasAttribute('expand')) {
foldNode(rowElement);
} else {
expendNode(rowElement);
}
};
} else {
td.style.paddingLeft = offsetVal + 'px';
}
}
rowElement.append(td);
})
if (this.selectable) {
rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
} else {
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');//`repeat(${this.colCount},1fr)`
}
rowElement.onclick = e => {
// console.log(rowElement.style.gridTemplateColumns);
// LitMessage.info(JSON.stringify(rowData));
}
parentNode.append(rowElement);
rowElement.setAttribute('id', rowData[ids[0]]);
rowElement.setAttribute('pid', rowData[ids[1]]);
rowElement.setAttribute('expand', '');
if (rowData.children && rowData.children.length > 0) {
//有子节点的 前面加上 + 图标 表示可以展开节点
offsetVal = offsetVal + offset;
drawRow(rowData.children, parentNode);
offsetVal = offsetVal - offset;
}
});
};
drawRow(treeData, this.tbodyElement);
}
//获取选中的行数据
getCheckRows() {
return [...this.shadowRoot.querySelectorAll('div[class=tr][checked]')].map(a => a.data).map(a => {
delete a['children'];
return a;
});
}
deleteRowsCondition(fn) {
this.shadowRoot.querySelectorAll("div[class=tr]").forEach(tr => {
if (fn(tr.data)) {
tr.remove();
}
})
}
}
if (!customElements.get('lit-table')) {
customElements.define('lit-table', LitTable);
}
class LitTableColumn extends HTMLElement {
static get observedAttributes() {
return ['name', 'order']
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{ }
</style>
<slot id="slot"></slot>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
this.template = null;
this.st = this.shadowRoot.querySelector('#slot')
this.st.addEventListener('slotchange', () => {
const elements = this.st.assignedElements({ flatten: false });
if (elements.length > 0) {
this.template = elements[0];
}
})
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('lit-table-column')) {
customElements.define('lit-table-column', LitTableColumn);
}
class LitTableGroup extends HTMLElement {
static get observedAttributes() {
return ['title']
}
get title() {
return this.getAttribute('title');
}
set title(value) {
this.setAttribute('title', value);
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{ }
</style>
<slot id="sl"></slot>
`
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('lit-table-group')) {
customElements.define('lit-table-group', LitTableGroup);
}
class LitTabpane extends HTMLElement {
static get observedAttributes() { return ['tab', 'key', 'disabled', 'icon', 'closeable', 'hide']; }
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host(){
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
overflow: auto;
width: 100%;
}
</style>
<slot></slot>
`
}
get tab() {
return this.getAttribute('tab');
}
set tab(value) {
this.setAttribute("tab", value);
}
get icon() {
return this.getAttribute("icon");
}
get disabled() {
return this.getAttribute('disabled') !== null;
}
set disabled(value) {
if (value === null || !value) {
this.removeAttribute("disabled");
} else {
this.setAttribute("disabled", value);
}
}
get closeable() {
return this.getAttribute('closeable') !== null;
}
set closeable(value) {
if (value === null || !value) {
this.removeAttribute("closeable");
} else {
this.setAttribute("closeable", value);
}
}
get key() {
return this.getAttribute("key");
}
set key(value) {
this.setAttribute("key", value);
}
get hide() {
return this.getAttribute('hide') !== null;
}
set hide(value) {
if (value === null || !value) {
this.removeAttribute("hide");
} else {
this.setAttribute("hide", value);
}
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() { }
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() { }
//当 custom element被移动到新的文档时被调用。
adoptedCallback() { }
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue && newValue !== undefined) {
if (name === 'tab' && this.parentNode) {
this.parentNode.updateLabel && this.parentNode.updateLabel(this.key, newValue);
}
if (name === 'disabled' && this.parentNode) {
this.parentNode.updateDisabled && this.parentNode.updateDisabled(this.key, newValue);
}
if (name === 'closeable' && this.parentNode) {
this.parentNode.updateCloseable && this.parentNode.updateCloseable(this.key, newValue);
}
if (name === 'hide' && this.parentNode) {
this.parentNode.updateCloseable && this.parentNode.updateHide(this.key, newValue);
}
}
}
}
if (!customElements.get('lit-tabpane')) {
customElements.define('lit-tabpane', LitTabpane);
}
class LitTabs extends HTMLElement {
static get observedAttributes() {
//mode = flat(default) | card
//position = top | top-left | top-center | top-right | left | left-top | left-center | left-bottom | right | bottom
return ['activekey', 'mode', 'position']
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
display: block;
text-align: unset;
color: #252525;
background-color: #fff;
box-shadow: #00000033 0 0 10px ;
/*padding: 10px;*/
/*margin-right: 10px;*/
}
::slotted(lit-tabpane){
box-sizing:border-box;
width:100%;
height:100%;
/*padding:10px;*/
flex-shrink:0;
overflow:auto;
}
.nav-item{
display: inline-flex;
justify-content: center;
align-items: center;
padding: 6px 0px 6px 12px;
font-size: .9rem;
font-weight: normal;
cursor: pointer;
transition: all 0.3s;
flex-shrink: 0;
}
.nav-item lit-icon{
margin-right: 2px;
font-size: inherit;
}
.nav-item:hover{
color: #42b983;
}
.nav-item[data-disabled]{
pointer-events: all;
cursor: not-allowed;
color: #bfbfbf;
}
.nav-item[data-selected]{
color: #42b983;;
}
.tab-content{
display: block;
background-color: #fff;
flex:1;
}
/*
* top top-left top-center top-right
*/
:host(:not([position])) .nav-root,
:host([position^='top']) .nav-root{
display: flex;
position: relative;
justify-content: center;
align-items: center;
}
:host(:not([mode]):not([position])) .tab-line,/*移动的线条*/
:host([mode='flat'][position^='top']) .tab-line{
position:absolute;
bottom: 2px;
background-color: #42b983;
height: 2px;
transform: translateY(100%);
transition: all 0.3s;
}
:host(:not([position])) .tab-nav-container,
:host([position^='top']) .tab-nav-container{
display: flex;
position: relative;
flex-direction: column;
overflow-y: hidden;
overflow-x: auto;
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
transition: all 0.3s;
flex: 1;
/*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/
/*background-repeat: no-repeat;*/
/*background-size: 50px 100%, 15px 100%;*/
/*background-attachment: local,scroll,local,scroll;*/
/*border-bottom: #f0f0f0 1px solid;*/
}
:host([position='top']) .tab-nav,
:host([position='top-left']) .tab-nav{
display: flex;
position: relative;
justify-content: flex-start;
}
:host([position='top-center']) .tab-nav{
display: flex;
justify-content: center;
}
:host([position='top-right']) .tab-nav{
display: flex;
justify-content: flex-end;
}
:host([position^='top'][mode='card']) .nav-item{
border-top: 1px solid #f0f0f0;
border-left: 1px solid #f0f0f0;
border-right: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
bottom: 0px;
margin-right: 2px;
position: relative;
}
:host([position^='top']) .tab-nav-bg-line{
position: absolute;bottom: 0;height: 1px;background-color: #f0f0f0;width: 100%
}
:host([position^='top'][mode='card']) .nav-item:not([data-selected]){
background-color: #f6f6f6;
border-bottom: 1px solid #f0f0f0;
}
:host([position^='top'][mode='card']) .nav-item[data-selected]{
background-color: #ffffff;
border-bottom: 1px solid #fff;
bottom: 0px;
}
/*
bottom bottom-left bottom-center bottom-right
*/
:host([position^='bottom']) .tab{
display: flex;
flex-direction: column-reverse;
}
:host([mode='flat'][position^='bottom']) .tab-line{
position:absolute;
top: -3px;
background-color: #42b983;
height: 2px;
transform: translateY(-100%);
transition: all 0.3s;
}
:host([position^='bottom']) .tab-nav-container{
display: flex;
position: relative;
flex-direction: column;
overflow-x: auto;
overflow-y: visible;
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
transition: all 0.3s;
flex: 1;
/*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/
/*background-repeat: no-repeat;*/
/*background-size: 50px 100%, 15px 100%;*/
/*background-attachment: local,scroll,local,scroll;*/
border-top: #f0f0f0 1px solid;
}
:host([position^='bottom']) .nav-root{
display: flex;
justify-content: center;
align-items: center;
}
:host([position='bottom']) .tab-nav,
:host([position='bottom-left']) .tab-nav{
display: flex;
position: relative;
justify-content: flex-start;
}
:host([position='bottom-center']) .tab-nav{
display: flex;
justify-content: center;
}
:host([position='bottom-right']) .tab-nav{
display: flex;
justify-content: flex-end;
}
:host([position^='bottom'][mode='card']) .nav-item{
border-top: 1px solid #ffffff;
border-left: 1px solid #f0f0f0;
border-right: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
top: -1px;
margin-right: 2px;
position: relative;
}
:host([position^='bottom']) .tab-nav-bg-line{
position: absolute;top: 0;height: 1px;background-color: #f0f0f0;width: 100%
}
:host([position^='bottom'][mode='card']) .nav-item:not([data-selected]){
background-color: #f5f5f5;
border-top: 1px solid #f0f0f0;
}
:host([position^='bottom'][mode='card']) .nav-item[data-selected]{
background-color: #ffffff;
border-top: 1px solid #fff;
top: -1px;
}
/*
left left-top left-center left-bottom
*/
:host([position^='left']) .tab{
display: flex;
flex-direction: row;
}
:host([mode='flat'][position^='left']) .tab-line{
position:absolute;
right: 1px;
background-color: #42b983;
width: 3px;
transform: translateX(100%);
transition: all 0.3s;
}
:host([position^='left']) .tab-nav-container{
display: flex;
position: relative;
flex-direction: row;
overflow-x: auto;
overflow-y: visible;
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
transition: all 0.3s;
flex: 1;
/*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/
/*background-repeat: no-repeat;*/
/*background-size: 50px 100%, 15px 100%;*/
/*background-attachment: local,scroll,local,scroll;*/
border-right: #f0f0f0 1px solid;
}
:host([position^='left']) .nav-root{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
:host([position='left']) .tab-nav,
:host([position='left-top']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: flex-start;
}
:host([position='left-center']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: center;
}
:host([position='left-bottom']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: flex-end;
}
:host([position^='left'][mode='card']) .nav-item{
border-top: 1px solid #f0f0f0;
border-left: 1px solid #f0f0f0;
border-right: 1px solid #ffffff;
border-bottom: 1px solid #f0f0f0;
right: -1px;
margin-bottom: 2px;
position: relative;
}
:host([position^='left']) .tab-nav-bg-line{
position: absolute;right: 0;width: 1px;background-color: #f0f0f0;width: 100%
}
:host([position^='left'][mode='card']) .nav-item:not([data-selected]){
background-color: #f5f5f5;
border-right: 1px solid #f0f0f0;
}
:host([position^='left'][mode='card']) .nav-item[data-selected]{
background-color: #ffffff;
border-bottom: 1px solid #fff;
right: -1px;
}
/*
right right-top right-center right-bottom
*/
:host([position^='right']) .tab{
display: flex;
flex-direction: row-reverse;
}
:host([mode='flat'][position^='right']) .tab-line{
position:absolute;
left: 1px;
background-color: #42b983;
width: 3px;
transform: translateX(-100%);
transition: all 0.3s;
}
:host([position^='right']) .tab-nav-container{
display: flex;
position: relative;
flex-direction: row-reverse;
overflow-x: auto;
overflow-y: visible;
overflow: -moz-scrollbars-none;
-ms-overflow-style: none;
transition: all 0.3s;
flex: 1;
/*background: linear-gradient(90deg,white 30%, transparent),radial-gradient(at 0 50%, rgba(0,0,0,.2),transparent 70%);*/
/*background-repeat: no-repeat;*/
/*background-size: 50px 100%, 15px 100%;*/
/*background-attachment: local,scroll,local,scroll;*/
border-left: #f0f0f0 1px solid;
}
:host([position^='right']) .nav-root{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
:host([position='right']) .tab-nav,
:host([position='right-top']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: flex-start;
}
:host([position='right-center']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: center;
}
:host([position='right-bottom']) .tab-nav{
display: flex;
position: relative;
flex-direction: column;
justify-content: flex-end;
}
:host([position^='right'][mode='card']) .nav-item{
border-top: 1px solid #f0f0f0;
border-left: 1px solid #ffffff;
border-right: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
left: -1px;
margin-top: 2px;
position: relative;
}
:host([position^='right']) .tab-nav-bg-line{
position: absolute;left: 0;width: 1px;background-color: #f0f0f0;width: 100%
}
:host([position^='right'][mode='card']) .nav-item:not([data-selected]){
background-color: #f5f5f5;
border-left: 1px solid #f0f0f0;
}
:host([position^='right'][mode='card']) .nav-item[data-selected]{
background-color: #ffffff;
left: -1px;
}
.tab-nav-container::-webkit-scrollbar {
display: none;
}
/*关闭的图标*/
.close-icon:hover{
color: #000;
}
.nav-item[data-closeable] .close-icon{
display: block;
padding: 5px 5px 5px 5px;
color: #999;
}
.nav-item[data-closeable] .no-close-icon{
display: none;
}
.nav-item:not([data-closeable]) .no-close-icon{
display: block;
}
.nav-item:not([data-closeable]) .close-icon{
display: none;
}
.nav-item:not([data-hide]){
display: block;
}
.nav-item[data-hide]{
display: none;
}
</style>
<style id="filter"></style>
<div class="tab">
<div class="nav-root">
<slot name="left" style="flex:1"></slot>
<div class="tab-nav-container" >
<div class="tab-nav-bg-line"></div>
<div class="tab-nav" id="nav"></div>
<div class="tab-line" id="tab-line"></div>
</div>
<slot name="right" style="flex:1"></slot>
</div>
<div class="tab-content">
<slot id="slot">NEED CONTENT</slot>
</div>
</div>
`
}
get position() {
return this.getAttribute('position') || 'top';
}
set position(value) {
this.setAttribute('position', value);
}
get mode() {
return this.getAttribute('mode') || 'flat';
}
set mode(value) {
this.setAttribute('mode', value);
}
get activekey() {
return this.getAttribute("activekey");
}
set activekey(value) {
this.setAttribute('activekey', value);
}
updateLabel(key, value) {
// console.log(key, value);
if (this.nav) {
let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
if (item) {
item.querySelector("span").innerHTML = value;
this.initTabPos()
}
}
}
updateDisabled(key, value) {
// console.log(key, value);
if (this.nav) {
let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
if (item) {
if (value) {
item.setAttribute('data-disabled', '')
} else {
item.removeAttribute('data-disabled');
}
this.initTabPos()
}
}
}
updateCloseable(key, value) {
if (this.nav) {
let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
if (item) {
if (value) {
item.setAttribute('data-closeable', '')
} else {
item.removeAttribute('data-closeable');
}
this.initTabPos()
}
}
}
updateHide(key, value) {
if (this.nav) {
let item = this.nav.querySelector(`.nav-item[data-key='${key}']`);
if (item) {
if (value) {
item.setAttribute('data-hide', '')
} else {
item.removeAttribute('data-hide');
}
this.initTabPos()
}
}
}
initTabPos() {
const items = this.nav.querySelectorAll(".nav-item");
Array.from(items).forEach((a, index) => {
this.tabPos[a.dataset.key] = {
index: index,
width: a.offsetWidth,
height: a.offsetHeight,
left: a.offsetLeft,
top: a.offsetTop,
label: a.textContent
}
})
if (this.activekey) {
if (this.position.startsWith('left')) {
this.line.style = `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${this.tabPos[this.activekey].top}px)`;
} else if (this.position.startsWith('top')) {
if (this.tabPos[this.activekey]) {
this.line.style = `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`;
}
} else if (this.position.startsWith('right')) {
this.line.style = `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${this.tabPos[this.activekey].top}px)`;
} else if (this.position.startsWith('bottom')) {
this.line.style = `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`;
}
}
}
//当 custom element首次被插入文档DOM时被调用。
connectedCallback() {
let that = this;
this.tabPos = {}
this.nav = this.shadowRoot.querySelector('#nav')
this.line = this.shadowRoot.querySelector('#tab-line')
this.slots = this.shadowRoot.querySelector('#slot');
this.slots.addEventListener('slotchange', () => {
const elements = this.slots.assignedElements();
let panes = this.querySelectorAll('lit-tabpane');
if (this.activekey) {
panes.forEach(a => {
if (a.key === this.activekey) {
a.style.display = 'block'
} else {
a.style.display = 'none';
}
})
} else {
panes.forEach((a, index) => {
if (index === 0) {
a.style.display = 'block'
this.activekey = a.key
} else {
a.style.display = 'none';
}
})
}
let navHtml = "";
elements.forEach(a => {
if (a.disabled) {
navHtml += `<div class="nav-item" data-key="${a.key}" data-disabled ${a.closeable ? 'data-closeable' : ''} ${a.hide ? 'data-hide' : ''}>
${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
<span>${a.tab}</span>
<lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
</div>`;
} else {
if (a.key === this.activekey) {
navHtml += `<div class="nav-item" data-key="${a.key}" data-selected ${a.closeable ? 'data-closeable' : ''} ${a.hide ? 'data-hide' : ''}>
${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
<span>${a.tab}</span>
<lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
</div>`;
} else {
navHtml += `<div class="nav-item" data-key="${a.key}" ${a.closeable ? 'data-closeable' : ''} ${a.hide ? 'data-hide' : ''}>
${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``}
<span>${a.tab}</span>
<lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div>
</div>`;
}
}
})
this.nav.innerHTML = navHtml;
this.initTabPos()
this.nav.querySelectorAll('.close-icon').forEach(a => {
a.onclick = (e) => {
e.stopPropagation();
const closeKey = e.target.parentElement.dataset.key;
console.log(closeKey);
console.log(e.target.parentElement.parentElement);
this.nav.removeChild(e.target.parentElement)
let elements = this.slots.assignedElements();
let closeElement = elements.filter(a => a.key === closeKey)[0];
closeElement.parentElement.removeChild(closeElement)
if (closeElement.style.display !== 'none') {
elements = this.slots.assignedElements();
let elArr = elements.filter(a => !a.hasAttribute('disabled'));
if (elArr.length > 0) {
elArr[0].style.display = 'block';
this.activekey = elArr[0].key
}
}
}
});
})
this.nav.onclick = (e) => {
if (e.target.closest('div').hasAttribute('data-disabled')) return;
let key = e.target.closest('div').dataset.key;
this.activeByKey(key)
let label = e.target.closest('div').querySelector('span').textContent;
this.dispatchEvent(new CustomEvent('onTabClick', { detail: { key: key, tab: label } }))
};
}
set onTabClick(fn) {
this.addEventListener('onTabClick', fn);
}
activeByKey(key) {
if (key === null || key === undefined) return; //如果没有key 不做相应
this.nav.querySelectorAll('.nav-item').forEach(a => {
if (a.getAttribute('data-key') === key) {
a.setAttribute('data-selected', 'true');
} else {
a.removeAttribute('data-selected');
}
})
let tbp = this.querySelector(`lit-tabpane[key='${key}']`);
let panes = this.querySelectorAll('lit-tabpane');
panes.forEach(a => {
if (a.key === key) {
a.style.display = 'block';
this.activekey = a.key;
this.initTabPos()
} else {
a.style.display = 'none';
}
})
}
/*激活选中 key 对应的 pane 成功返回true没有找到key对应的pane 返回false*/
activePane(key) {
if (key === null || key === undefined) return false;
let tbp = this.querySelector(`lit-tabpane[key='${key}']`);
if (tbp) {
this.activeByKey(key)
return true;
} else {
return false;
}
}
//当 custom element从文档DOM中删除时被调用。
disconnectedCallback() {
}
//当 custom element被移动到新的文档时被调用。
adoptedCallback() {
console.log('Custom square element moved to new page.');
}
//当 custom element增加、删除、修改自身属性时被调用。
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'activekey' && this.nav && oldValue !== newValue) {
this.activeByKey(newValue)
}
}
}
if (!customElements.get('lit-tabs')) {
customElements.define('lit-tabs', LitTabs);
}
class AppDiffFlame extends HTMLElement {
draw;
drawC;
rowHeight = 17;
static get observedAttributes() {
return []
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
font-size:inherit;
display:inline-flex;
align-items: center;
justify-content:center;
height: 100%;
width: 100%;
margin-bottom: 40px;
}
canvas { border: 1px solid #e9e9e9; }
#title{
font-weight: bold;
height: auto;
}
#title span{
color: gray;
}
#funcNameSpan{
display: inline-block;
border: 1px solid #e9e9e9;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
color: gray;
flex: 3;
margin-left: 10px;
}
#percentSpan{
display: inline-block;
border: 1px solid #e9e9e9;
margin-left: 10px;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
min-width: 160px;
max-width: 160px;
margin-left: 10px;
width: 100px;
height: 30px;
color: gray;
}
#diffSpan{
display: inline-block;
border: 1px solid #e9e9e9;
margin-left: 10px;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
min-width: 160px;
max-width: 160px;
margin-left: 10px;
width: 100px;
height: 30px;
color: gray;
}
#history{
display: none;
border: 1px solid #42b983;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
width: 100px;
margin-left: 10px;
height: 30px;
cursor: pointer;
user-select: none;
}
#history:hover{
background: #42b983;
color: #fff;
}
#searchInput{
height: 30px;
margin-left: 10px;
margin-right: 10px;
flex: 1;
}
</style>
<div style="position: relative;width: 100%">
<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>
<canvas id="panel" title=""></canvas>
<div id="controller" style="position: absolute;top: 32px;display: flex;width: 100%">
<span id="history">Zoom Out</span>
<span id="funcNameSpan"></span>
<span id="diffSpan"></span>
<span id="percentSpan"></span>
<lit-input id="searchInput" placeholder="search" allow-clear>Search</lit-input>
</div>
</div>
<slot></slot>
`
}
connectedCallback() {
this.history = [];
this.historyRefer = [];
this.panel = this.shadowRoot.getElementById('panel');
this.controller = this.shadowRoot.getElementById('controller');
this.funcNameSpan = this.shadowRoot.getElementById('funcNameSpan');
this.percentSpan = this.shadowRoot.getElementById('percentSpan');
this.diffSpan = this.shadowRoot.getElementById('diffSpan');
this.historySpan = this.shadowRoot.getElementById('history');
this.searchInput = this.shadowRoot.getElementById('searchInput');
this.pid = this.shadowRoot.getElementById('pid');
this.tid = this.shadowRoot.getElementById('tid');
this.processName = this.shadowRoot.getElementById('processName');
this.threadName = this.shadowRoot.getElementById('threadName');
this.sample = this.shadowRoot.getElementById('sample');
this.titleDiv = this.shadowRoot.getElementById('title');
this.context = this.panel.getContext('2d');
this.panel.width = this.shadowRoot.host.clientWidth;
this.panel.height = this.shadowRoot.host.clientHeight;
this.historySpan.onclick = e => {
if (this.history.length > 2) {
this.history.pop();
this.historyRefer.pop();
this.zoomOutRefer(this.historyRefer[this.historyRefer.length - 1])
this.zoomOut(this.history[this.history.length - 1])
} else if (this.history.length == 2) {
this.history.pop();
this.historyRefer.pop();
this.zoomOutRefer(this.historyRefer[this.historyRefer.length - 1])
this.zoomOut(this.history[this.history.length - 1])
this.historySpan.style.display = 'none';
} else {
this.historySpan.style.display = 'none';
}
}
this.searchInput.addEventListener("onPressEnter", (e) => {
this.keyword = e.currentTarget.value;
requestAnimationFrame(this.draw);
})
this.searchInput.addEventListener('onClear', e => {
this.keyword = null;
requestAnimationFrame(this.draw);
})
this.panel.onmouseover = (e) => {
this.mouseState = 'mouseOver';
}
this.panel.onmouseleave = e => {
this.mouseState = 'mouseLeave';
this.mouseX = 0;
this.mouseY = 0;
requestAnimationFrame(this.draw)
}
this.panel.onmousemove = e => {
const pos = e.currentTarget.getBoundingClientRect();
this.mouseX = e.clientX - pos.left;
this.mouseY = e.clientY - pos.top;
this.mouseState = 'mouseMove';
requestAnimationFrame(this.draw)
}
this.panel.onmousedown = e => {
this.mouseState = 'mouseDown';
// const pos = e.currentTarget.getBoundingClientRect();
// this.mouseX = e.clientX - pos.left;
// this.mouseY = e.clientY - pos.top;
// requestAnimationFrame(this.draw)
}
this.panel.onmouseup = e => {
this.mouseState = 'mouseUp';
const pos = e.currentTarget.getBoundingClientRect();
this.mouseX = e.clientX - pos.left;
this.mouseY = e.clientY - pos.top;
requestAnimationFrame(this.draw)
}
// this.panel.onclick = (e)=>{
// this.mouseState = 'mouseClick';
// }
}
set data(value) {
this._data = value;
this._dataRefer = value.jsonRefer;
/**
* type 值默认为1
* 1 Show percentage of event count relative to the current thread
* 2 Show percentage of event count relative to the current process
* 3 Show percentage of event count relative to all process
* 4 show event count
* 5 show event count in milliseconds
* 其他值
* pid, processName, tid, threadName, eventCount, sampleCount, CallOrder(g节点)
*/
this.type = value.type;
// reverse 表示火焰图需要是否需要倒序绘制value中没有传reverse默认表示false 既正序绘制
this.reverse = value.reverse || false;
// CallOrder.symbol 如果为-1 表示为根节点
if (value.CallOrder.symbol === -1) {
this._c = value.CallOrder.callStack;
this._cRef = this.threadRefer.CallOrder.callStack;
} else {
this._c = [value.CallOrder];
this._cRef = [this.threadRefer.CallOrder];
}
// console.log(this.data.json, this.dataRefer)
// console.log(this.c, this.cRef)
this.history.push(this._c)
this.historyRefer.push(this.cRef)
// 获取选项的 eventCount值 例如hw-cpu-cycles 和 processes 同层级节点 表示所有进程的eventCount值求和
this.eventCountAllProcess = this._data.json.recordSampleInfo[window.eventIndex].eventCount
console.log("diff eventCountAllProcess = " + this.eventCountAllProcess)
if (value.pid) {
// 当前进程的 eventCount值没有直接送json中去取是因为第一次传的json 和 windows.data 一样,后续点击了 自节点传入的是子集,可能取的不准确
this.eventCountCurrentProcess = this._data.json.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === value.pid)[0].eventCount
console.log("diff eventCountCurrentProcess = " + this.eventCountCurrentProcess)
// 将进程号显示在界面上
this.pid.textContent = value.pid;
} else {
// this.titleDiv.style.display = 'none';
}
if (value.tid) {
// 取当前线程的 eventCount 值
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;
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;
console.log("diff eventCountCurrentThread = " + this.eventCountCurrentThread)
// 显示线程号
this.tid.textContent = value.tid;
}
if (value.sampleCount) {
//显示sampleCount值 ,这个值在 thread节点里只有线程有
this.sample.textContent = ' (Samples: ' + value.sampleCount + ")";
}
if (value.processName) {
//显示进程名 null显示空字符串
this.processName.textContent = value.processName ? `(${value.processName})` : '';
}
if (value.threadName) {
//显示线程名 null显示空字符串
this.threadName.textContent = value.threadName ? `(${value.threadName})` : '';
}
if (value.funcName) {
//如果是单独绘制Funtion Tab界面下的火焰图只有 函数名称, 上面的 的进程线程信息不显示
this.titleDiv.innerHTML = `${value.funcName}`
this.controller.style.top = `${this.titleDiv.clientHeight + 10}px`
}
//获取最大层级,用于做火焰图高度计算,点击子节点后 maxDepth 也对应变化
this.maxDepth = this.getMaxDepth(this.data.CallOrder.callStack) + 5; //设置最大层级
this.maxDepthRefer = this.getMaxDepth(this.threadRefer.CallOrder.callStack) + 5; //设置最大层级
console.log("diff maxDepth = " + this.maxDepth + " maxDepthRefer = " + this.maxDepthRefer)
this.sumCount = this.data.CallOrder.subEvents; //设置根节点的 sumCount
this.sumCountRefer = this.threadRefer.CallOrder.subEvents; //设置根节点的 sumCountRefer 用来做对比
console.log("diff sumCount = " + this.sumCount + " sumCountRefer = " + this.sumCountRefer)
// console.log(this.threadRefer)//对比的线程数据
//计算canvas的宽度和高度
this.panel.height = this.maxDepth * this.rowHeight
this.panel.width = this.shadowRoot.host.clientWidth
//设置canvas 的大小
this.makeHighRes(this.panel);
//开始调用draw方法绘制一帧
requestAnimationFrame(this.draw);
}
get data() {
return this._data;
}
//需要对比的原始数据
get dataRefer() {
return this._dataRefer;
}
set dataRefer(val) {
this._dataRefer = val;
}
/*
获取要对比的 thread数据
*/
get threadRefer() {
return this._threadRefer;
}
/**
* 设置要对比的数据 这里传入的是 thread 节点;包含 {tideventCount,sampleCount,libs:[],CallOrder:[],CalledOrder:[]}
* @param val
*/
set threadRefer(val) {
this._threadRefer = val;
}
/**
* 点击图形中子节点时 传入 当前节点当作根节点绘制
* @param value
*/
set c(value) {
//显示回退button
this.historySpan.style.display = 'block';
//设置相对根节点
this._c = value;
//添加历史记录,用于回退到上一图形状态
this.history.push(this._c)
// 下面代码实现 canvas 随内容高度变化
// this.maxDepth = this._getMaxDepth(value) + 5; //设置最大层级
// this.panel.height = this.maxDepth * this.rowHeight
// this.panel.width = this.shadowRoot.host.clientWidth;
// this.makeHighRes(this.panel);
//开始绘制
requestAnimationFrame(this.draw);
}
get c() {
return this._c;
}
get cRef() {
return this._cRef
}
set cRef(val) {
this._cRef = val;
this.historyRefer.push(this._cRef)
}
zoomOutRefer(value) {
this._cRef = value;
}
zoomOut(value) {
this._c = value;
// 下面代码实现 canvas 随内容高度变化
// this.maxDepth = this._getMaxDepth(value) + 5; //设置最大层级
// // this.sumCount = value.reduce((acc, cur) => acc + cur.s, 0); //设置根节点的 sumCount
// this.panel.height = this.maxDepth * this.rowHeight
// this.panel.width = this.shadowRoot.host.clientWidth;
// this.makeHighRes(this.panel);
requestAnimationFrame(this.draw);
}
makeHighRes(canvas) {
let ctx = canvas.getContext('2d');
let dpr = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1;
let oldWidth = canvas.width;
let oldHeight = canvas.height;
canvas.width = Math.round(oldWidth * dpr);
canvas.height = Math.round(oldHeight * dpr);
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';
ctx.scale(dpr, dpr);
this.context = ctx;
return ctx;
}
//绘制火焰图
draw = () => {
// requestAnimationFrame(this.draw);
let ctx = this.context;
// ctx.clearRect(0, 0, this.panel.width, this.panel.height);
//绘制背景渐变色
let grad = ctx.createLinearGradient(0, 0, 0, this.panel.height / 2); //创建一个渐变色线性对象
grad.addColorStop(0, "#eeeeee"); //定义渐变色颜色
grad.addColorStop(1, "#efefb1");
ctx.fillStyle = grad; //设置fillStyle为当前的渐变对象
ctx.fillRect(0, 0, this.panel.width, this.panel.height);
// console.log(this.c,this.cRef);
if (this.data) {
//如果需要倒序绘制调用drawCReverse方法绘制
if (this.reverse) {
this.drawCReverse(
this.c,
1,
{
x: 0,
y: this.rowHeight * 4,
w: this.panel.clientWidth,
h: this.rowHeight
});
} else {
//绘制正序图需要3个参数 节点数据,当前绘制的层级,当前这行的Rect数据
this.drawC(
this.c,//根节点
this.cRef,
1,
{
x: 0,
y: this.panel.clientHeight - this.rowHeight,
w: this.panel.clientWidth,
h: this.rowHeight
});
}
}
}
//根据type类型计算 统计数据
getStatistics(c) {
let statistics;//鼠标hover展示的百分比
switch (this.type) {
case 1: //current thread
statistics = c.subEvents * 100 / this.eventCountCurrentThread;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 2: // current process
statistics = c.subEvents * 100 / this.eventCountCurrentProcess;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 3: // all process
statistics = c.subEvents * 100 / this.eventCountAllProcess;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 4: //event count
statistics = c.subEvents;
statistics = `${statistics}`
break;
case 5: //event count in milliseconds
statistics = c.subEvents / 1000000;
statistics = statistics.toFixed(3)
statistics = `${statistics} ms`
break;
default: //current thread
statistics = c.subEvents * 100 / this.eventCountCurrentThread;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
}
return statistics;
}
//HTML反转义
htmlDecode(text) {
let temp = document.createElement("div");
temp.innerHTML = text;
let output = temp.innerText || temp.textContent;
temp = null;
return output;
}
//获取方法名
getFunctionName(f) {
let funName = "";
if (this._data.json.SymbolMap[f]) {
funName = this._data.json.SymbolMap[f].symbol;
} else {
let f = c[i].symbol;
console.log(`processId:${this.pid.textContent} processName:${this.processName.textContent} threadId:${this.tid.textContent} threadName:${this.threadName.textContent}`, c[i], "SymbolMap中没有对应的值")
}
return this.htmlDecode(funName);
}
//根据比对对象的funId 在比对数组中找到对应的函数名
getFunctionNameRefer(f) {
let funName = "";
if (this.dataRefer.SymbolMap[f]) {
funName = this.dataRefer.SymbolMap[f].symbol;
} else {
// let f = c[i].symbol;
// console.log(`processId:${this.pid.textContent} processName:${this.processName.textContent} threadId:${this.tid.textContent} threadName:${this.threadName.textContent}`, c[i], "SymbolMap中没有对应的值")
}
return this.htmlDecode(funName);
}
//根据百分比获取显示的背景色,如果 搜索框中的关键字与当前funcName 匹配则单独显示色值
getColor(percent2, funName) {
let heatColor;
if (this.keyword && this.keyword.length > 0 && funName.indexOf(this.keyword) != -1) {
heatColor = { r: 0x66, g: 0xad, b: 0xff };
} else {
heatColor = this.getHeatColor(percent2);
}
return heatColor;
}
//倒序绘制
drawCReverse = (c, dept, rect) => {
let ctx = this.context;
let offset = 0
for (let i = 0; i < c.length; i++) {
let funName = this.getFunctionName(c[i].symbol);
let funcId = c[i].symbol;
let percent = c[i].subEvents * 100 / (c.reduce((acc, cur) => acc + cur.subEvents, 0));
let percent2 = c[i].subEvents * 100 / this.sumCount;
if (percent2 < 0.1) continue //过滤掉 百分比为0.1一下的节点
let heatColor = this.getColor(percent2, funName);
let w = rect.w * (percent / 100.0);
if (w < 1) {
w = 1
}
let _x = rect.x + offset;
//绘制填充矩形
ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`;
ctx.fillRect(_x, rect.y + 2, w, rect.h - 2);
// 绘制文本
ctx.fillStyle = "rgba(0,0,0,1)";
let txtWidth = ctx.measureText(funName).width;//文本长度
let chartWidth = txtWidth / funName.length;//每个字符长度
let number = (w - 6) / chartWidth;//可以显示多少字符
if (number >= 4 && number < funName.length - 3) {
ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6)
} else if (number >= 4) {
ctx.fillText(funName, _x + 3, rect.y + 13, w - 6)
}
let _rect = {
x: _x, y: rect.y, w: w, h: rect.h
}
c[i].rect = _rect;
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) {
if (this.mouseState === 'mouseMove') {
//绘制边框矩形
// ctx.font = '12px serif';
ctx.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
this.funcNameSpan.textContent = funName
this.panel.title = funName
this.percentSpan.textContent = this.getStatistics(c[i]);
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
}
}
}
} else {
ctx.lineWidth = 1;
// ctx.font = '11px serif';
}
offset += w;
// console.log(_rect,dept,percent,funName);
//递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y + _rect.h
this.drawCReverse(c[i].callStack, dept + 1, _rect);
}
}
}
//正序绘制
drawC = (c, cRef, dept, rect) => {
let ctx = this.context;
let offset = 0//用来保存x坐标的偏移量保证在rect的矩形范围内从左往右绘制 c集合中的节点
for (let i = 0; i < c.length; i++) {
let funName = this.getFunctionName(c[i].symbol);
// console.log(dept,this.data.SymbolMap[c[i].symbol].symbol);
let funcId = c[i].symbol;//获取funcId
//计算这个节点的subEvents值 在当前传入集合的subEvents总和中占的百分比因为rect默认是最大宽宽的递归后最越来越小根据这个百分比和 rect矩形的宽度计算节点绘制的宽度
let percent = c[i].subEvents * 100 / (c.reduce((acc, cur) => acc + cur.subEvents, 0));//sumCount;
//计算这个节点的subEvents 在总的sumCount对应的是最大宽度中占比这个百分比用来计算颜色占比越少颜色越浅
let percent2 = c[i].subEvents * 100 / this.sumCount;
if (percent2 < 0.1) continue //过滤掉 百分比为0.1一下的节点
let heatColor = this.getColor(percent2, funName);
let w = rect.w * (percent / 100.0);
if (w < 1) {
w = 1
}
let _x = rect.x + offset;
//绘制填充矩形
ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`;
let __rect = { x: _x, y: rect.y + 2, w: w, h: rect.h - 2 }
ctx.fillRect(__rect.x, __rect.y, __rect.w, __rect.h);
//覆盖要比较的数据 (显示的方法名,显示的矩形对象,宽度的百分比,需要比对节点集合[需要根据funName从中取得要比较的节点])
let nodeRefer = this.drawReferData(ctx, funName, __rect, percent, cRef);
// 绘制文本
ctx.fillStyle = "rgba(0,0,0,1)";
let txtWidth = ctx.measureText(funName).width;//文本长度
let chartWidth = txtWidth / funName.length;//每个字符长度
let number = (w - 6) / chartWidth;//可以显示多少字符
if (number >= 4 && number < funName.length - 3) {
ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6)
} else if (number >= 4) {
ctx.fillText(funName, _x + 3, rect.y + 13, w - 6)
}
let _rect = {
x: _x, y: rect.y, w: w, h: rect.h
}
c[i].rect = _rect;
if (this.mouseX > _x && this.mouseX < _x + w && this.mouseY > (this.maxDepth - dept) * rect.h && this.mouseY < (this.maxDepth - dept + 1) * rect.h) {
if (this.mouseState === 'mouseMove') {
//绘制边框矩形
// ctx.font = '12px serif';
ctx.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
this.funcNameSpan.textContent = funName
this.panel.title = funName
this.percentSpan.textContent = this.getStatistics(c[i]);
let node = this.fetchSameFuncNameData(funName, cRef);
var diff = 0.0;
if (node) {
let percentRefer = node.subEvents * 100 / (cRef.reduce((acc, cur) => acc + cur.subEvents, 0))
diff = (percent - percentRefer).toFixed(2)
}
this.diffSpan.textContent = `diff: ${diff}%`;
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
this.cRef = [nodeRefer];
}
}
}
} else {
ctx.lineWidth = 1;
// ctx.font = '11px serif';
}
offset += w;
// console.log(_rect,dept,percent,funName);
//递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y - _rect.h
this.drawC(c[i].callStack, nodeRefer ? nodeRefer.callStack : [], dept + 1, _rect);
}
}
}
//在对比数组中找到 方法名与 funName相同的节点
fetchSameFuncNameData(funName, cRef) {
let node = cRef.filter(it => {
let funNameRefer = this.getFunctionNameRefer(it.symbol);
return funName === funNameRefer
})
// console.log("cRef:",funName,cRef,node)
if (node && node.length > 0) {
return node[0];
} else {
return null;
}
}
//对比数据,如果要比对的数据宽度大于绘制节点宽度 显示红色,否则显示蓝色,颜色深度取决于大多少或者小多少
drawReferData(ctx, funName, rect, percent, cRef) {
//根据funName 在cRef集合中找到同函数名的节点
let node = this.fetchSameFuncNameData(funName, cRef);
if (node) {
let percentRefer = node.subEvents * 100 / (cRef.reduce((acc, cur) => acc + cur.subEvents, 0));//sumCount;
// console.log("找到相同节点", funName, rect.w, percent, percentRefer, node)
//如果绘制的百分比小于要比较的百分比
if (percent < percentRefer) {
let offset = Math.abs(percent - percentRefer);
if (offset > 0 && offset <= 10) {
ctx.fillStyle = `#66B3FF`;
} else if (offset > 10 && offset <= 20) {
ctx.fillStyle = `#2894FF`;
} else if (offset > 20 && offset <= 30) {
ctx.fillStyle = `#0072E3`;
} else if (offset > 30 && offset <= 50) {
ctx.fillStyle = `#0066CC`;
} else {
ctx.fillStyle = `#005AB5`;
}
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
} else if (percent > percentRefer) {
let offset = Math.abs(percent - percentRefer);
if (offset > 0 && offset <= 10) {
ctx.fillStyle = `#FF7575`;
} else if (offset > 10 && offset <= 20) {
ctx.fillStyle = `#FF2D2D`;
} else if (offset > 20 && offset <= 30) {
ctx.fillStyle = `#EA0000`;
} else if (offset > 30 && offset <= 50) {
ctx.fillStyle = `#CE0000`;
} else {
ctx.fillStyle = `#AE0000`;
}
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
}
}
return node;
}
compareNode(na, nb) {
let res = false;
if (na.selfEvents === nb.selfEvents && na.subEvents === nb.subEvents && na.symbol === nb.symbol) {
res = this.compareNodes(na.callStack || [], nb.callStack || []);
}
return res;
}
compareNodes(a, b) {
let res = false;
if (a.length === b.length) {
if (a.length === 0) {
return true;
}
for (let i = 0; i < a.length; i++) {
res = this.compareNode(a[i], b[i])
}
}
return res;
}
getMaxDepth(nodes) {
let isArray = Array.isArray(nodes);
let sumCount;
if (isArray) {
sumCount = nodes.reduce((acc, cur) => acc + cur.subEvents, 0);
} else {
sumCount = nodes.subEvents;
}
let width = sumCount * 100.0 / this.sumCount;
if (width < 0.1) {
return 0;
}
let children = isArray ? this.splitChildrenForNodes(nodes) : nodes.callStack;
let childDepth = 0;
if (children) {
for (let child of children) {
childDepth = Math.max(childDepth, this.getMaxDepth(child));
}
}
return childDepth + 1;
}
splitChildrenForNodes(nodes) {
let map = new Map();
for (let node of nodes) {
for (let child of node.callStack) {
let subNodes = map.get(child.symbol);
if (subNodes) {
subNodes.push(child);
} else {
map.set(child.symbol, [child]);
}
}
}
let res = [];
for (let subNodes of map.values()) {
res.push(subNodes.length == 1 ? subNodes[0] : subNodes);
}
return res;
}
//根据百分比获取节点的背景色
getHeatColor(widthPercentage) {
return {
r: Math.floor(245 + 10 * (1 - widthPercentage * 0.01)),
g: Math.floor(110 + 105 * (1 - widthPercentage * 0.01)),
b: 100,
};
}
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('app-diff-flame')) {
customElements.define('app-diff-flame', AppDiffFlame);
}
class AppNormalFlame extends HTMLElement {
draw;
drawC;
rowHeight = 17;
static get observedAttributes() {
return []
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
font-size:inherit;
display:inline-flex;
align-items: center;
justify-content:center;
height: 100%;
width: 100%;
margin-bottom: 40px;
}
canvas { border: 1px solid #e9e9e9; }
#title{
font-weight: bold;
height: auto;
}
#title span{
color: gray;
}
#funcNameSpan{
display: inline-block;
border: 1px solid #e9e9e9;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
color: gray;
flex: 3;
margin-left: 10px;
}
#percentSpan{
display: inline-block;
border: 1px solid #e9e9e9;
margin-left: 10px;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
min-width: 160px;
max-width: 160px;
margin-left: 10px;
width: 100px;
height: 30px;
color: gray;
}
#history{
display: none;
border: 1px solid #42b983;
box-sizing: border-box;
border-radius: 2px;
padding: 2px 8px;
background: #fff;
width: 100px;
margin-left: 10px;
height: 30px;
cursor: pointer;
user-select: none;
}
#history:hover{
background: #42b983;
color: #fff;
}
#searchInput{
height: 30px;
margin-left: 10px;
margin-right: 10px;
flex: 1;
}
</style>
<div style="position: relative;width: 100%">
<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>
<canvas id="panel" title=""></canvas>
<div id="controller" style="position: absolute;top: 32px;display: flex;width: 100%">
<span id="history">Zoom Out</span>
<span id="funcNameSpan"></span>
<span id="percentSpan"></span>
<lit-input id="searchInput" placeholder="search" allow-clear>Search</lit-input>
</div>
</div>
<slot></slot>
`
}
connectedCallback() {
this.history = [];
this.panel = this.shadowRoot.getElementById('panel');
this.controller = this.shadowRoot.getElementById('controller');
this.funcNameSpan = this.shadowRoot.getElementById('funcNameSpan');
this.percentSpan = this.shadowRoot.getElementById('percentSpan');
this.historySpan = this.shadowRoot.getElementById('history');
this.searchInput = this.shadowRoot.getElementById('searchInput');
this.pid = this.shadowRoot.getElementById('pid');
this.tid = this.shadowRoot.getElementById('tid');
this.processName = this.shadowRoot.getElementById('processName');
this.threadName = this.shadowRoot.getElementById('threadName');
this.sample = this.shadowRoot.getElementById('sample');
this.titleDiv = this.shadowRoot.getElementById('title');
this.context = this.panel.getContext('2d');
this.panel.width = this.shadowRoot.host.clientWidth;
this.panel.height = this.shadowRoot.host.clientHeight;
this.historySpan.onclick = e => {
if (this.history.length > 2) {
this.history.pop();
this.zoomOut(this.history[this.history.length - 1])
} else if (this.history.length == 2) {
this.history.pop();
this.zoomOut(this.history[this.history.length - 1])
this.historySpan.style.display = 'none';
} else {
this.historySpan.style.display = 'none';
}
}
this.searchInput.addEventListener("onPressEnter", (e) => {
this.keyword = e.currentTarget.value;
requestAnimationFrame(this.draw);
})
this.searchInput.addEventListener('onClear', e => {
this.keyword = null;
requestAnimationFrame(this.draw);
})
this.panel.onmouseover = (e) => {
this.mouseState = 'mouseOver';
}
this.panel.onmouseleave = e => {
this.mouseState = 'mouseLeave';
this.mouseX = 0;
this.mouseY = 0;
requestAnimationFrame(this.draw)
}
this.panel.onmousemove = e => {
const pos = e.currentTarget.getBoundingClientRect();
this.mouseX = e.clientX - pos.left;
this.mouseY = e.clientY - pos.top;
this.mouseState = 'mouseMove';
requestAnimationFrame(this.draw)
}
this.panel.onmousedown = e => {
this.mouseState = 'mouseDown';
// const pos = e.currentTarget.getBoundingClientRect();
// this.mouseX = e.clientX - pos.left;
// this.mouseY = e.clientY - pos.top;
// requestAnimationFrame(this.draw)
}
this.panel.onmouseup = e => {
this.mouseState = 'mouseUp';
const pos = e.currentTarget.getBoundingClientRect();
this.mouseX = e.clientX - pos.left;
this.mouseY = e.clientY - pos.top;
requestAnimationFrame(this.draw)
}
// this.panel.onclick = (e)=>{
// this.mouseState = 'mouseClick';
// }
}
set data(value) {
this._data = value;
this.type = value.type;
this.reverse = value.reverse || false;
if (value.CallOrder.symbol === -1) {
this._c = value.CallOrder.callStack;
} else {
this._c = [value.CallOrder];
}
this.history.push(this._c)
this.eventCountAllProcess = value.json.recordSampleInfo[window.eventIndex].eventCount
if (value.pid) {
this.eventCountCurrentProcess = value.json.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === value.pid)[0].eventCount
this.pid.textContent = value.pid;
} else {
// this.titleDiv.style.display = 'none';
}
if (value.tid) {
this.eventCountCurrentThread = value.json.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === value.pid)[0].threads.filter(it => it.tid === value.tid)[0].eventCount;
this.tid.textContent = value.tid;
}
if (value.sampleCount) {
this.sample.textContent = ' (Samples: ' + value.sampleCount + ")";
}
if (value.processName) {
this.processName.textContent = value.processName ? `(${value.processName})` : '';
}
if (value.threadName) {
this.threadName.textContent = value.threadName ? `(${value.threadName})` : '';
}
if (value.funcName) {
this.titleDiv.innerHTML = `${value.funcName}`
this.controller.style.top = `${this.titleDiv.clientHeight + 10}px`
}
this.maxDepth = this.getMaxDepth(this.data.CallOrder.callStack) + 5; //设置最大层级
this.sumCount = this.data.CallOrder.subEvents; //设置根节点的 sumCount
this.panel.height = this.maxDepth * this.rowHeight
this.panel.width = this.shadowRoot.host.clientWidth
this.makeHighRes(this.panel);
requestAnimationFrame(this.draw);
}
get data() {
return this._data;
}
set c(value) {
this.historySpan.style.display = 'block';
this._c = value;
this.history.push(this._c)
// 下面代码实现 canvas 随内容高度变化
// this.maxDepth = this._getMaxDepth(value) + 5; //设置最大层级
// this.panel.height = this.maxDepth * this.rowHeight
// this.panel.width = this.shadowRoot.host.clientWidth;
// this.makeHighRes(this.panel);
requestAnimationFrame(this.draw);
}
get c() {
return this._c;
}
zoomOut(value) {
this._c = value;
// 下面代码实现 canvas 随内容高度变化
// this.maxDepth = this._getMaxDepth(value) + 5; //设置最大层级
// // this.sumCount = value.reduce((acc, cur) => acc + cur.s, 0); //设置根节点的 sumCount
// this.panel.height = this.maxDepth * this.rowHeight
// this.panel.width = this.shadowRoot.host.clientWidth;
// this.makeHighRes(this.panel);
requestAnimationFrame(this.draw);
}
makeHighRes(canvas) {
let ctx = canvas.getContext('2d');
let dpr = window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1;
let oldWidth = canvas.width;
let oldHeight = canvas.height;
canvas.width = Math.round(oldWidth * dpr);
canvas.height = Math.round(oldHeight * dpr);
canvas.style.width = oldWidth + 'px';
canvas.style.height = oldHeight + 'px';
ctx.scale(dpr, dpr);
this.context = ctx;
return ctx;
}
draw = () => {
// requestAnimationFrame(this.draw);
let ctx = this.context;
// ctx.clearRect(0, 0, this.panel.width, this.panel.height);
let grad = ctx.createLinearGradient(0, 0, 0, this.panel.height / 2); //创建一个渐变色线性对象
grad.addColorStop(0, "#eeeeee"); //定义渐变色颜色
grad.addColorStop(1, "#efefb1");
ctx.fillStyle = grad; //设置fillStyle为当前的渐变对象
ctx.fillRect(0, 0, this.panel.width, this.panel.height);
if (this.data) {
if (this.reverse) {
this.drawCReverse(
this.c,
1,
{
x: 0,
y: this.rowHeight * 4,
w: this.panel.clientWidth,
h: this.rowHeight
});
} else {
this.drawC(
this.c,
1,
{
x: 0,
y: this.panel.clientHeight - this.rowHeight,
w: this.panel.clientWidth,
h: this.rowHeight
});
}
}
}
getStatistics(c) {
let statistics;//鼠标hover展示的百分比
switch (this.type) {
case 1: //current thread
statistics = c.subEvents * 100 / this.eventCountCurrentThread;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 2: // current process
statistics = c.subEvents * 100 / this.eventCountCurrentProcess;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 3: // all process
statistics = c.subEvents * 100 / this.eventCountAllProcess;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
case 4: //event count
statistics = c.subEvents;
statistics = `${statistics}`
break;
case 5: //event count in milliseconds
statistics = c.subEvents / 1000000;
statistics = statistics.toFixed(3)
statistics = `${statistics} ms`
break;
default: //current thread
statistics = c.subEvents * 100 / this.eventCountCurrentThread;
statistics = statistics.toFixed(2)
statistics = `${statistics}%`
break;
}
return statistics;
}
//HTML反转义
htmlDecode(text) {
let temp = document.createElement("div");
temp.innerHTML = text;
let output = temp.innerText || temp.textContent;
temp = null;
return output;
}
getFunctionName(f) {
let funName = "";
if (this.data.json.SymbolMap[f]) {
funName = this.data.json.SymbolMap[f].symbol;
} else {
let f = c[i].symbol;
console.log(`processId:${this.pid.textContent} processName:${this.processName.textContent} threadId:${this.tid.textContent} threadName:${this.threadName.textContent}`, c[i], "SymbolMap中没有对应的值")
}
return this.htmlDecode(funName);
}
getColor(percent2, funName) {
let heatColor;
if (this.keyword && this.keyword.length > 0 && funName.indexOf(this.keyword) != -1) {
heatColor = { r: 0x66, g: 0xad, b: 0xff };
} else {
heatColor = this.getHeatColor(percent2);
}
return heatColor;
}
drawCReverse = (c, dept, rect) => {
let ctx = this.context;
let offset = 0
for (let i = 0; i < c.length; i++) {
let funName = this.getFunctionName(c[i].symbol);
let funcId = c[i].symbol;
let percent = c[i].subEvents * 100 / (c.reduce((acc, cur) => acc + cur.subEvents, 0));
let percent2 = c[i].subEvents * 100 / this.sumCount;
if (percent2 < 0.1) continue //过滤掉 百分比为0.1一下的节点
let heatColor = this.getColor(percent2, funName);
let w = rect.w * (percent / 100.0);
if (w < 1) {
w = 1
}
let _x = rect.x + offset;
//绘制填充矩形
ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`;
ctx.fillRect(_x, rect.y + 2, w, rect.h - 2);
// 绘制文本
ctx.fillStyle = "rgba(0,0,0,1)";
let txtWidth = ctx.measureText(funName).width;//文本长度
let chartWidth = txtWidth / funName.length;//每个字符长度
let number = (w - 6) / chartWidth;//可以显示多少字符
if (number >= 4 && number < funName.length - 3) {
ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6)
} else if (number >= 4) {
ctx.fillText(funName, _x + 3, rect.y + 13, w - 6)
}
let _rect = {
x: _x, y: rect.y, w: w, h: rect.h
}
c[i].rect = _rect;
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) {
if (this.mouseState === 'mouseMove') {
//绘制边框矩形
// ctx.font = '12px serif';
ctx.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
this.funcNameSpan.textContent = funName
this.panel.title = funName
this.percentSpan.textContent = this.getStatistics(c[i]);
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
}
}
}
} else {
ctx.lineWidth = 1;
// ctx.font = '11px serif';
}
offset += w;
// console.log(_rect,dept,percent,funName);
//递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y + _rect.h
this.drawCReverse(c[i].callStack, dept + 1, _rect);
}
}
}
drawC = (c, dept, rect) => {
let ctx = this.context;
let offset = 0
for (let i = 0; i < c.length; i++) {
let funName = this.getFunctionName(c[i].symbol);
// console.log(dept,this.data.SymbolMap[c[i].symbol].symbol);
let funcId = c[i].symbol;
let percent = c[i].subEvents * 100 / (c.reduce((acc, cur) => acc + cur.subEvents, 0));//sumCount;
let percent2 = c[i].subEvents * 100 / this.sumCount;
if (percent2 < 0.1) continue //过滤掉 百分比为0.1一下的节点
let heatColor = this.getColor(percent2, funName);
let w = rect.w * (percent / 100.0);
if (w < 1) {
w = 1
}
let _x = rect.x + offset;
//绘制填充矩形
ctx.fillStyle = `rgba(${heatColor.r}, ${heatColor.g}, ${heatColor.b}, 1)`;
ctx.fillRect(_x, rect.y + 2, w, rect.h - 2);
// 绘制文本
ctx.fillStyle = "rgba(0,0,0,1)";
let txtWidth = ctx.measureText(funName).width;//文本长度
let chartWidth = txtWidth / funName.length;//每个字符长度
let number = (w - 6) / chartWidth;//可以显示多少字符
if (number >= 4 && number < funName.length - 3) {
ctx.fillText(funName.slice(0, number - 3) + '...', _x + 3, rect.y + 13, w - 6)
} else if (number >= 4) {
ctx.fillText(funName, _x + 3, rect.y + 13, w - 6)
}
let _rect = {
x: _x, y: rect.y, w: w, h: rect.h
}
c[i].rect = _rect;
if (this.mouseX > _x && this.mouseX < _x + w && this.mouseY > (this.maxDepth - dept) * rect.h && this.mouseY < (this.maxDepth - dept + 1) * rect.h) {
if (this.mouseState === 'mouseMove') {
//绘制边框矩形
// ctx.font = '12px serif';
ctx.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
this.funcNameSpan.textContent = funName
this.panel.title = funName
this.percentSpan.textContent = this.getStatistics(c[i]);
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
}
}
}
} else {
ctx.lineWidth = 1;
// ctx.font = '11px serif';
}
offset += w;
// console.log(_rect,dept,percent,funName);
//递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y - _rect.h
this.drawC(c[i].callStack, dept + 1, _rect);
}
}
}
compareNode(na, nb) {
let res = false;
if (na.selfEvents === nb.selfEvents && na.subEvents === nb.subEvents && na.symbol === nb.symbol) {
res = this.compareNodes(na.callStack || [], nb.callStack || []);
}
return res;
}
compareNodes(a, b) {
let res = false;
if (a.length === b.length) {
if (a.length === 0) {
return true;
}
for (let i = 0; i < a.length; i++) {
res = this.compareNode(a[i], b[i])
}
}
return res;
}
getMaxDepth(nodes) {
let isArray = Array.isArray(nodes);
let sumCount;
if (isArray) {
sumCount = nodes.reduce((acc, cur) => acc + cur.subEvents, 0);
} else {
sumCount = nodes.subEvents;
}
let width = sumCount * 100.0 / this.sumCount;
if (width < 0.1) {
return 0;
}
let children = isArray ? this.splitChildrenForNodes(nodes) : nodes.callStack;
let childDepth = 0;
if (children) {
for (let child of children) {
childDepth = Math.max(childDepth, this.getMaxDepth(child));
}
}
return childDepth + 1;
}
splitChildrenForNodes(nodes) {
let map = new Map();
for (let node of nodes) {
for (let child of node.callStack) {
let subNodes = map.get(child.symbol);
if (subNodes) {
subNodes.push(child);
} else {
map.set(child.symbol, [child]);
}
}
}
let res = [];
for (let subNodes of map.values()) {
res.push(subNodes.length == 1 ? subNodes[0] : subNodes);
}
return res;
}
getHeatColor(widthPercentage) {
return {
r: Math.floor(245 + 10 * (1 - widthPercentage * 0.01)),
g: Math.floor(110 + 105 * (1 - widthPercentage * 0.01)),
b: 100,
};
}
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('app-normal-flame')) {
customElements.define('app-normal-flame', AppNormalFlame);
}
class AppFlameGraph extends HTMLElement {
static get observedAttributes() {
return ['color', 'size']
}
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
:host{
font-size:inherit;
display:inline-flex;
align-items: center;
justify-content:center;
width: 100%;
}
</style>
<div style="width: 100%;display: flex;flex-direction: column">
<lit-select id="typeSelect" default-value="1" mode="single" style="width:40vw;margin-bottom: 10px;align-self: flex-end">
<lit-select-option value="1">Show percentage of event count relative to the current thread</lit-select-option>
<lit-select-option value="2">Show percentage of event count relative to the current process</lit-select-option>
<lit-select-option value="3">Show percentage of event count relative to all process</lit-select-option>
<lit-select-option value="4">show event count</lit-select-option>
<lit-select-option value="5">show event count in milliseconds</lit-select-option>
</lit-select>
<div id="panel" style="width: 100%"></div>
</div>
<slot></slot>
`
}
get dataRefer() {
return this._dataRefer;
}
//设置比对的数据【这里是json文件中加载的数据 需要从 recordSampleInfo[index] 取进程集合 线程集合】
set dataRefer(val) {
this._dataRefer = val;
}
get data() {
return this._json || null;
}
set data(json) {
//如果已经给过值,不重新刷新
if (this.isFinished) {
return;
}
this._json = json;
this.panel = this.shadowRoot.getElementById('panel');
this.panel.innerHTML = '';
let processes = json.recordSampleInfo[window.eventIndex].processes;
let processesRefer;
if (this.dataRefer) {
processesRefer = this.dataRefer.recordSampleInfo[window.eventIndex].processes;
}
processes.slice(0).forEach(it => {
let itRefer
if (processesRefer) {
itRefer = processesRefer.find(element => element.pid == it.pid);//找到要比较的进程
}
//slice(0,1) 控制只绘制一个线程数据,用作测试
it.threads.slice(0).forEach(th => {
let pid = it.pid;
let processName = json.processNameMap[it.pid];
let tid = th.tid;
let threadName = json.threadNameMap[th.tid];
let eventCount = th.eventCount;
let sampleCount = th.sampleCount;
let g = th.CallOrder;
if (this.dataRefer) {
console.log("create diff")
let thRefer;
if (itRefer) {
thRefer = itRefer.threads.find(e => e.tid == th.tid);//找到要比较的线程
}
let chart = document.createElement('app-diff-flame');
chart.style.width = '100%'
chart.style.height = 'auto';
chart.style.display = 'flex'
this.panel.appendChild(chart);
chart.threadRefer = thRefer;//注意 app-flame-graph中的dataRefer中保存的是加载的json完整数据集app-chart-flame中的dataRefer缓存的是对比的线程数据
chart.data = {
json: json,//显示的json数据集【直接从json文件读取的完整json结构】
jsonRefer: this.dataRefer,//需要把完整的对比数据也传入过去因为只传入thread对比数据需要 完整数据中的eventCount去计算百分比计算宽度然后对比原始宽度多还是少
type: this.type || 1,
pid, processName, tid, threadName, eventCount, sampleCount, CallOrder: g
}
} else {
console.log("create normal")
let chart = document.createElement('app-normal-flame');
chart.style.width = '100%'
chart.style.height = 'auto';
chart.style.display = 'flex'
this.panel.appendChild(chart);
chart.data = {
json: json,
type: this.type || 1,
pid, processName, tid, threadName, eventCount, sampleCount, CallOrder: g
}
}
// console.log(pid,processName,tid,threadName,sampleCount,g);
})
})
this.isFinished = true;
}
connectedCallback() {
this.isFinished = false;
this.panel = this.shadowRoot.getElementById('panel');
this.typeSelect = this.shadowRoot.getElementById('typeSelect');
this.typeSelect.onchange = ev => {
this.type = parseInt(ev.detail.value);
this.isFinished = false;
this.data = this.data; //切换分类后刷新
}
}
attributeChangedCallback(name, oldValue, newValue) {
if (name == 'color' && this.loading) {
this.loading.style.color = newValue;
}
if (name == 'size' && this.loading) {
this.loading.style.fontSize = newValue + 'px';
}
}
}
if (!customElements.get('app-flame-graph')) {
customElements.define('app-flame-graph', AppFlameGraph);
}
(function (values) {
function createPromise(callback) {
if (callback) {
return new Promise((resolve, _) => callback(resolve));
}
return new Promise((resolve, _) => resolve());
}
function initGlobalObjects1() {
let recordData = document.querySelector('#record_data_diff_1').textContent;
if (recordData.trim().length > 0) {
return new Promise((resolve, reject) => {
resolve(JSON.parse(recordData));
})
} else {
return fetch('data-diff-1.json').then(response => response.json())
}
}
function initGlobalObjects2() {
let recordData = document.querySelector('#record_data_diff_2').textContent;
if (recordData.trim().length > 0) {
return new Promise((resolve, reject) => {
resolve(JSON.parse(recordData));
})
} else {
return fetch('data-diff-2.json').then(response => response.json())
}
}
function waitDocumentReady() {
return createPromise((resolve) => document.addEventListener("DOMContentLoaded", resolve));
}
createPromise()
.then(waitDocumentReady)
.then(() => Promise.all([initGlobalObjects1(), initGlobalObjects2()]))
.then((array) => {
let j1 = array[0]
let j2 = array[1]
// window.data = j1; // 没有用到function 模块 不用保存
let eventSelector1 = document.querySelector('#events1')
let changeBaseBt = document.querySelector('#changeBaseBt')
if (j1.recordSampleInfo && j1.recordSampleInfo.length > 0) {
let events = [];
j1.recordSampleInfo.forEach((e, index) => {
events.push({ key: index + '', val: e.eventConfigName })
})
eventSelector1.dataSource = events;
window.eventIndex = 0;
}
let loading = document.querySelector('#loading');
let tabs = document.querySelector('#tabs')
let diffFlame = document.querySelector('#diff-flame');
let flame1 = document.querySelector('#flame-1');
let flame2 = document.querySelector('#flame-2');
let legend1 = document.querySelector('#legend-1');
let legend2 = document.querySelector('#legend-2');
tabs.onTabClick = (e) => {
if (e.detail.key == 1) {
diffFlame.isFinished = false;
diffFlame.dataRefer = window.isReserved ? j1 : j2; //对换显示数据和对比数据
diffFlame.data = window.isReserved ? j2 : j1;//将 对比的数据当作显示数据
} else if (e.detail.key == 2) {
flame1.isFinished = false;
flame1.data = j1;
} else {
flame2.isFinished = false;
flame2.data = j2;
}
}
//火焰图
window.isReserved = false; //定义变量。表示是否反转基本画图数据默认为false ,表示以 data-diff-1.json 数据图形数据data-diff-2.json 为比较数据
diffFlame.isFinished = false;
diffFlame.dataRefer = j2; //设置需要对比的数据集flame.data赋值会引起绘制所以这行要先设置对比数据
diffFlame.data = j1;//要绘制火焰图需要显示的数据
eventSelector1.addEventListener('change', (e) => {
loading.style.display = 'flex'
window.eventIndex = parseInt(e.detail.value);//改变界面最上面的select 缓存的index刷新 绘制的数据需要这个index去总数据中取
diffFlame.isFinished = false;
diffFlame.dataRefer = j2;
diffFlame.data = j1;//这里触发了界面更新
flame1.isFinished = false;
flame1.data = j1;
flame2.isFinished = false;
flame2.data = j2;
loading.style.display = 'none'
})
changeBaseBt.addEventListener('click', (e) => {
window.isReserved = !window.isReserved
changeBaseBt.setAttribute('enable', 'false');
loading.style.display = 'flex'
legend1.textContent = window.isReserved ? "diff-data-2" : "diff-data-1";
legend2.textContent = window.isReserved ? "diff-data-1" : "diff-data-2";
diffFlame.isFinished = false;
diffFlame.dataRefer = window.isReserved ? j1 : j2; //对换显示数据和对比数据
diffFlame.data = window.isReserved ? j2 : j1;//将 对比的数据当作显示数据
loading.style.display = 'none'
changeBaseBt.setAttribute('enable', 'true');
})
})
}())
</script>
<div style="width: 100%;height: 100%">
<div style="width: 100%;display: flex;flex-direction: column;align-items: center">
<lit-loading id="loading" size="32" style="display: none"></lit-loading>
</div>
<div style="display: flex;flex-direction: row;align-items: center;padding: 15px;justify-content: space-between">
<div>
<span style="font-weight: bold;margin-right: 10px">Event Type :</span>
<lit-select id="events1" default-value="0" style="width: 400px"></lit-select>
</div>
</div>
<lit-tabs id='tabs' position="top-left" activekey="1" mode="flat">
<lit-tabpane id="pane1" tab="diff-flame" key="1">
<div style="display: flex;flex-direction: row;align-items: center;justify-content: space-between">
<div style="display: flex;flex-direction: row;align-items: center">
<div style="display: flex;flex-direction: row;align-items: center">
<span>图形数据: </span>
<span id="legend-1">data-diff-1</span>
</div>
<div style="display: flex;flex-direction: row;margin-left: 20px;align-items: center">
<span>比较数据: </span>
<span id="legend-2">data-diff-2</span>
</div>
<div id="changeBaseBt" style="background-color: coral;color: white;margin-left: 40px;
padding: 7px 15px 7px 15px;border-radius: 5px;cursor: pointer">change</div>
</div>
<div style="display: flex;flex-direction: row;align-items: center">
<div style="display: flex;flex-direction: row;align-items: center">
<span style="width: 10px;height: 10px;background-color:#2894FF;margin-right: 10px"></span>
<span>函数执行时间相比小于(颜色越深则差值越大)</span>
</div>
<div style="display: flex;flex-direction: row;align-items: center;margin-left: 30px">
<span style="width: 10px;height: 10px;background-color:#FF2D2D;margin-right: 10px"></span>
<span>函数执行时间相比大于(颜色越深则差值越大)</span>
</div>
</div>
</div>
<div style="padding: 20px">
<app-flame-graph id="diff-flame"></app-flame-graph>
</div>
</lit-tabpane>
<lit-tabpane id="pane2" tab="data-1-flame" key="2">
<app-flame-graph id="flame-1"></app-flame-graph>
</lit-tabpane>
<lit-tabpane id="pane3" tab="data-2-flame" key="3">
<app-flame-graph id="flame-2"></app-flame-graph>
</lit-tabpane>
</lit-tabs>
</div>