developtools_hiperf/script/report.html
wenlong12 2492e992d6 修复火焰图Function页called项无数据问题
Signed-off-by:wenlong12 <wenlong12@huawei.com>

Signed-off-by: wenlong12 <wenlong12@huawei.com>
2024-11-12 15:55:09 +08:00

5392 lines
209 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</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') {
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 => {
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 LitPieChart extends HTMLElement {
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;
color:#42b983;
}
</style>
<div style="display: flex;flex-direction: row;width: 100%;padding: 15px">
<div style="display: flex;flex-direction: column;align-items: center;width: 40%">
<div id="chartTitle" style="width:auto;font-size: 20px;margin-bottom: 15px;text-overflow: ellipsis;white-space: nowrap"></div>
<canvas id="chartView" width="200" height="200" ></canvas>
</div>
<div id="labelDiv" style="margin-top: 45px">
<div id="legendDiv"></div>
<div id="pageOptionDiv" style="display: none;flex-direction: row;margin-top: 15px">
<div id="previous" style="cursor: pointer">previous</div>
<div id="page" style="margin-left: 10px;margin-right: 10px">1/9</div>
<div id="next" style="cursor: pointer">next</div>
</div>
</div>
<div id="tip" style="display: none;position: absolute;z-index: 999;width: auto;height: auto;background-color: #fff;
border-radius: 3px;border:1px solid #eee;box-shadow: 0px 0px 2px 2px #aaa;padding: 10px">
<div id="tipName" style="width: auto;font-weight: bold;margin-bottom: 10px"></div>
<div id="tipValue" style="width: auto;font-weight: bold"></div>
</div>
</div>
<slot></slot>
`;
}
get dataSource() {
return this.ds || [];
}
set dataSource(value) {
this.ds = value;
if (this.ds) {
this.page = 0;
let p = parseInt((this.ds.length / this.pageSize).toString());
this.totalPage = this.ds.length % this.pageSize > 0 ? (p + 1) : p;
if (this.totalPage > 9) {
this.totalPage = 9;
}
let tempAngel = -1 / 4 * 2 * Math.PI; // 饼图的起始角度
this.ds.forEach(item=>{
item.startAngel = tempAngel; // 起始弧度
item.angel = item.value * 2 * Math.PI;
item.endAngel = (tempAngel + item.angel); //结束弧度
if (item.endAngel > Math.PI * 3 / 2) {
item.endAngel = Math.PI * 3 / 2;
}
tempAngel += item.angel;
});
this.updateOptionStatus();
this.addChartLegend();
}
this.renderChart();
}
get title() {
return this.ct || '';
}
set title(value) {
this.ct = value;
if (value) {
this.chartTitle.innerText = value.length > 50 ? value.slice(0,49)+'...' : value;
}
}
connectedCallback() {
this.pageSize = 14;
this.page = 0;
this.chartView = this.shadowRoot.getElementById('chartView');
this.chartView.width = window.devicePixelRatio * this.chartView.width;
this.chartView.height = window.devicePixelRatio * this.chartView.height;
this.chartTitle = this.shadowRoot.getElementById('chartTitle');
this.labelDiv = this.shadowRoot.getElementById('legendDiv');
this.tip = this.shadowRoot.getElementById('tip');
this.tipName = this.shadowRoot.getElementById('tipName');
this.tipValue = this.shadowRoot.getElementById('tipValue');
this.pageOptionDiv = this.shadowRoot.getElementById('pageOptionDiv');
this.previousBt = this.shadowRoot.getElementById('previous');
this.pageDiv = this.shadowRoot.getElementById('page');
this.nextBt = this.shadowRoot.getElementById('next');
this.previousBt.addEventListener('click',this.previousPage.bind(this));
this.nextBt.addEventListener('click',this.nextPage.bind(this));
this.chartView.addEventListener('mousemove',this.mouseMove.bind(this));
this.chartView.addEventListener('click',this.mouseClick.bind(this));
this.chartView.addEventListener('mouseout',this.mouseOut.bind(this));
}
mouseMove(event) {
const mousePos = this.getMousePost(event);
const result = this.containPoint(mousePos);
if (result) {
this.tip.style.left = (event.clientX + 40) + 'px';
this.tip.style.top = (event.clientY - 40) + 'px';
this.tip.style.display = 'flex';
this.tip.style.flexDirection = 'column';
this.tipName.innerText = result.name;
this.tipValue.innerText = result.time + ' (' + (result.value * 100).toFixed(1) + '%)';
} else {
this.tip.style.display = 'none';
if (this.selectItem !== null) {
this.selectItem = undefined;
this.renderChart();
}
}
}
mouseOut(event) {
this.tip.style.display = 'none';
if (this.selectItem !== null) {
this.selectItem = undefined;
this.renderChart();
}
}
mouseClick(event) {
if (this.selectItem && this.hasOwnProperty('chartClickListener')) {
this.chartClickListener(this.selectItem);
}
}
attributeChangedCallback (name, oldValue, newValue) {
}
// clientX,clientY
// 判断鼠标在 canvas 的位置
getMousePost(event) {
// 获取鼠标的位置
const { clientX, clientY } = event;
// 获取 canvas 的边界位置
const { top, left } = this.chartView.getBoundingClientRect();
// 计算鼠标在 canvas 在位置
const x = (clientX - left);
const y = (clientY - top);
return { x, y };
}
// 判断是不是在 canvas上
containPoint(mousePos) {
let selector = undefined;
let centerX = this.centerX;
let centerY = this.centerY;
const [subX,subY] = [centerX - mousePos.x,centerY - mousePos.y];
// 圆心到鼠标 的位置
const len = Math.sqrt(subX * subX + subY * subY);
// 判断是否在圆内
const inChart = len < this.chartRadius;
// 判断是不是在 startAngle 和 endAngle
if (inChart) {
if (this.ds) {
let angle = Math.atan2(centerY - mousePos.y, centerX - mousePos.x) * (180 / Math.PI);
let pointAngle = 0;
if (angle >= 90) {
pointAngle = -(180 - angle);
} else if (angle >= 0 && angle < 90) {
pointAngle = 270 + angle;
} else if (angle < 0 && angle >= -90) {
pointAngle = 270 + angle;
} else {
pointAngle = 90 + 180 + angle;
}
let pa = pointAngle / 180 * Math.PI;
if (pa > 0) {
pa = pa - Math.PI / 2;
}
for (let item of this.ds) {
if (pa >= item.startAngel && pa < item.endAngel) {
selector = item;
if (item !== this.selectItem) {
this.selectItem = item;
this.renderChart();
}
break;
}
}
}
}
return selector;
}
renderChart() {
if (this.ds) {
let context = this.chartView.getContext('2d');
// 清除画布
context.clearRect(0,0,this.chartView.width,this.chartView.height);
this.centerX = this.chartView.width / 2;
this.centerY = this.chartView.height / 2; // 圆心坐标
let labelX,labelY; // label 文字绘制位置
let radius = this.chartView.height / 2 - 10; // 原型半径
this.chartRadius = radius;
let offset = radius / 6;
let size = this.ds.length;
this.ds.forEach(item => {
if (item === this.selectItem) {
context.beginPath();
context.moveTo(this.centerX,this.centerY);
context.arc(this.centerX,this.centerY,radius + 5,item.startAngel,item.endAngel);
context.fillStyle = this.colorRgbWithAlpha(item.color);
context.fill();
context.closePath();
}
context.beginPath();
context.moveTo(this.centerX,this.centerY);
context.arc(this.centerX,this.centerY,radius,item.startAngel,item.endAngel);
context.fillStyle = item.color;
context.fill();
if (size > 1) {
context.strokeStyle = '#ffffff';
context.stroke();
}
if (item.value > 0.045) {
let text = (item.value * 100).toFixed(1) + '%';
context.font = '13px Microsoft Yahei';
context.moveTo(this.centerX,this.centerY);
context.fillStyle = '#ffffff';
let textWidth = context.measureText(text).width;
if (size === 1) {
context.fillText(text,this.centerX - textWidth / 2,this.centerY);
} else {
let textAngel = item.startAngel + item.angel * 0.5; // 文字角度
labelX = this.centerX + Math.cos(textAngel) * (radius - offset);
labelY = this.centerY + Math.sin(textAngel) * (radius - offset);
context.fillText(text,labelX - textWidth / 2,labelY);
}
}
context.closePath();
})
}
}
addChartLegend() {
this.labelDiv.innerText = '';
if (this.ds.length <= 15) {
this.pageOptionDiv.style.display = 'none';
this.ds.forEach(item=>{
this.labelDiv.appendChild(this.createChartLegend(item));
});
} else {
this.pageOptionDiv.style.display = 'flex';
for (let i = this.page * this.pageSize; i < (this.page + 1) * this.pageSize; i++) {
if (i < this.ds.length) {
this.labelDiv.appendChild(this.createChartLegend(this.ds[i]));
} else {
break;
}
}
if (this.page === 9 && this.ds.length > 126) {
this.labelDiv.appendChild(this.createChartLegend(this.ds[this.ds.length - 1]));
}
}
this.pageDiv.innerText = `${this.page + 1}/${this.totalPage}`;
}
previousPage() {
if (this.page > 0) {
this.page = this.page - 1;
}
this.addChartLegend();
this.updateOptionStatus();
}
nextPage() {
if (this.page < this.totalPage - 1) {
this.page = this.page + 1;
}
this.addChartLegend();
this.updateOptionStatus();
}
updateOptionStatus() {
if (this.page === 0) {
this.previousBt.setAttribute('disabled','true');
this.previousBt.style.color = '#999';
} else {
this.previousBt.style.color = '#42b983';
this.previousBt.removeAttribute('disabled');
}
if (this.page + 1 === this.totalPage) {
this.nextBt.style.color = '#999';
this.nextBt.setAttribute('disabled','true');
} else {
this.nextBt.style.color = '#42b983';
this.nextBt.removeAttribute('disabled');
}
}
createChartLegend(item) {
let legend = document.createElement('div');
legend.style.display = 'flex';
legend.style.alignItems = 'center';
legend.style.marginBottom = '5px';
legend.style.cursor = 'pointer';
let icon = document.createElement('div');
icon.style.backgroundColor = item.color;
icon.style.borderRadius = '5px';
icon.style.width = '10px';
icon.style.height = '10px';
icon.style.marginRight = '10px';
let span = document.createElement('span');
span.style.fontSize = '14px';
span.style.width = '800px';
span.style.whiteSpace = 'nowrap';
span.style.textOverflow = 'ellipsis';
span.style.overflow = 'hidden';
span.innerText = item.name;
legend.appendChild(icon);
legend.appendChild(span);
legend.addEventListener('mouseover',e => {
this.selectItem = item;
this.renderChart();
});
legend.addEventListener('click',e => {
this.selectItem = item;
this.mouseClick(e);
});
legend.addEventListener('mouseout',e => {
this.selectItem = undefined;
this.renderChart();
});
return legend;
}
colorRgbWithAlpha(sColor) {
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
sColor = sColor.toLowerCase();
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#';
for (let i=1; i<4; i+=1) {
sColorNew += sColor.slice(i,i+1).concat(sColor.slice(i,i+1));
}
sColor = sColorNew;
}
// 处理六位的颜色值
let sColorChange = [];
for (let i=1; i<7; i+=2) {
sColorChange.push(parseInt('0x'+sColor.slice(i,i+2)));
}
sColorChange.push(0.6);
return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]},${sColorChange[3]})`;
} else {
return sColor;
}
};
}
if (!customElements.get('lit-pie-chart')) {
customElements.define('lit-pie-chart', LitPieChart);
}
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;
-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 => {
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 => {
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';
}
// input获取焦点时显示所有可选项相当于清理了搜索结果
this.querySelectorAll('lit-select-option').forEach(a => {
a.style.display = 'flex';
})
}
// 当输入框失去焦点的时候 placeholder 的值 保存到value上input显示值
this.inputElement.onblur = ev => {
if (this.hasAttribute('disabled')) return;// 如果控件处于disabled状态 直接忽略
if (this.isMultiple()) {
// 如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标
if (this.hasAttribute('show-search')) {
this.searchElement.style.display = 'none';
this.iconElement.style.display = 'flex';
}
} else {
// 如果placeholder为 请输入(默认值)不做处理
if (this.inputElement.placeholder !== this.defaultPlaceholder) {
this.inputElement.value = this.inputElement.placeholder; // placeholder 保存的值放入 value中
this.inputElement.placeholder = this.defaultPlaceholder;// placeholder 值为 默认值(请输入)
}
// 如果有show-search属性 失去焦点需要 隐藏放大镜图标,显示默认的向下箭头图标
if (this.hasAttribute('show-search')) {
this.searchElement.style.display = 'none';
this.iconElement.style.display = 'flex';
}
}
}
// 输入框每次文本变化 会匹配搜索的option 显示或者隐藏,达到搜索的效果
this.inputElement.oninput = ev => {
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');
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', '');
}
} else {
if (a.getAttribute('value') === this.defaultValue) {
this.inputElement.value = a.textContent;
a.setAttribute('selected', '');
}
}
// 每个option设置onSelected事件 接受当前点击的option
a.addEventListener('onSelected', (e) => {
// 所有option设置为未选中状态
if (this.isMultiple()) {//多选
if (a.hasAttribute('selected')) {
let tag = this.shadowRoot.querySelector(`div[data-value=${e.detail.value}]`);
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的值为当前选择的文本
// 向外层派发change事件返回当前选中项
this.dispatchEvent(new CustomEvent('change', {detail: e.detail}));
});
});
}
// 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.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 area = [], gridTemplateColumns = [];
let resolvingArea = (columns, x, y) => {
columns.forEach((a, 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', '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');
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', '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');
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;
}
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.appendChild(titleLabel);
if (a.hasAttribute('fixed')) {
this.fixed(h, a.getAttribute('fixed'), '#42b983');
}
rowElement.append(h);
}
});
};
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];
}
});
this.gridTemplateColumns = gridTemplateColumns.join(' ');
if (this.selectable) {
let s = area.map(a => '"_checkbox_ ' + (a.map(aa => aa.t).join(' ')) + '"').join(' ');
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');
rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`;
rowElement.style.gridTemplateAreas = s;
} else {
let s = area.map(a => '"' + (a.map(aa => aa.t).join(' ')) + '"').join(' ');
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');
rowElement.style.gridTemplateRows = `repeat(${area.length},1fr)`;
rowElement.style.gridTemplateAreas = s;
}
this.theadElement.append(rowElement);
if (this.hasAttribute('tree')) {
this.renderTreeTable();
} else {
this.renderTable();
}
});
});
}
// 当 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.boxShadow = '3px 0px 5px #33333333';
} else if (placement === 'right') {
td.style.right = '0px';
td.style.boxShadow = '-3px 0px 5px #33333333';
}
}
/*渲染成表格*/
renderTable() {
let that = this;
if (!this.columns) return;
if (!this.ds) return; // 如果没有设置数据源,直接返回
this.tbodyElement.innerHTML = '';// 清空表格内容
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);
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.innerHTML =
`<code style='padding:0;margin:0'>${rowData[dataIndex].toString().replace('\n','')}</code>`;
rowElement.append(td);
}
});
if (this.selectable) { // 如果 带选择的table 前面添加一个 60px的列
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');
} else {
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');
}
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]);
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);
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.innerHTML = rowData[dataIndex];
}
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 = '';
});
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) {
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = '60px ' + gridTemplateColumns.join(' ');
} else {
// `repeat(${this.colCount},1fr)`
rowElement.style.gridTemplateColumns = gridTemplateColumns.join(' ');
}
rowElement.onclick = e => {
};
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===false) {
this.removeAttribute('disabled');
} else {
this.setAttribute('disabled',value);
}
}
get closeable() {
return this.getAttribute('closeable')!==null;
}
set closeable(value) {
if (value===null||value===false) {
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===false) {
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) {
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) {
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 AppChartFlame 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';
};
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);
};
}
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 = data.recordSampleInfo[window.eventIndex].eventCount;
if (value.pid) {
this.eventCountCurrentProcess =
data.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === value.pid)[0].eventCount;
this.pid.textContent = value.pid;
}
if (value.tid) {
this.eventCountCurrentThread =
data.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 随内容高度变化
requestAnimationFrame(this.draw);
}
get c() {
return this._c;
}
zoomOut(value) {
this._c = value;
// 下面代码实现 canvas 随内容高度变化
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 = () => {
let ctx = this.context;
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(
0,
this.c,
1,
{
x: 0,
y: this.panel.clientHeight - this.rowHeight,
w: this.panel.clientWidth,
h: this.rowHeight
});
}
}
};
getCount(c) {
let count;// 鼠标hover展示的百分比
count = c.subEvents;
count = `${count}`;
return count;
}
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 (data.SymbolMap[f]) {
funName = data.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 = funName.includes("url:") ? this.getJsHeatColor(percent2) : 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;
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.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
let statisticNum = this.getStatistics(c[i]);
this.funcNameSpan.textContent = funName;
this.panel.title = funName + ': [' + statisticNum + ']';
this.percentSpan.textContent = statisticNum;
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
}
}
}
} else {
ctx.lineWidth = 1;
}
offset += w;
// 递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y + _rect.h;
this.drawCReverse(c[i].callStack, dept + 1, _rect);
}
}
}
drawC = (parentEvents, c, dept, rect) => {
let ctx = this.context;
let offset = 0;
if (parentEvents === 0) {
parentEvents = c.reduce((acc, cur) => acc + cur.subEvents, 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 / parentEvents;// sumCount;
let percent2 = c[i].subEvents * 100 / this.sumCount;
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.lineWidth = 2;
ctx.strokeStyle = `#000000`;
ctx.strokeRect(_x, rect.y + 1, w - 1, rect.h);
let statisticNum = this.getStatistics(c[i]);
let count = this.getCount(c[i]);
this.funcNameSpan.textContent = funName;
if (this.type === 1 || this.type === 2 || this.type === 3) {
this.panel.title = funName + ': [' + count + ' ' + statisticNum + ']';
} else {
this.panel.title = funName + ': [' + statisticNum + ']';
}
this.percentSpan.textContent = statisticNum;
} else {
if (this.mouseState === 'mouseUp') {
this.mouseState = null;
if (!this.compareNodes(this.c, [c[i]])) {
this.c = [c[i]];
}
}
}
} else {
ctx.lineWidth = 1;
}
offset += w;
// 递归绘制子节点
if (c[i].callStack && c[i].callStack.length > 0) {
_rect.y = _rect.y - _rect.h;
this.drawC(c[i].subEvents, 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,
};
}
getJsHeatColor(widthPercentage) {
return {
r: Math.floor(20 + 120 * (1 - widthPercentage * 0.01)),
g: 200,
b: Math.floor(0 + 120 * (1 - widthPercentage * 0.01)),
};
}
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('app-chart-flame')) {
customElements.define('app-chart-flame', AppChartFlame);
}
class AppChartStatistics extends HTMLElement {
static get observedAttributes() {
return ['data'];
}
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
this.color = [
'#3391ff', // red
'#ff9201', // green
'#008078', // indigo
'#0094c6', // orange
'#ff7500', // light green
'#2db3aa', // deep purple
'#0076ff', // pink
'#66adff', // purple
'#73e6de', // blue
'#535da6', // light blue
'#ffab40', // lime
'#38428c', // cyan
'#7cdeff', // deep orange
'#fbbf00', // blue gray
'#2db4e2', // amber #ffc105
'#ffd44a', // brown
'#7a84cc', // teal
'#ffe593' // yellow 0xffec3d
];
shadowRoot.innerHTML = `
<style>
:host{
font-size:inherit;
display:inline-flex;
align-items: center;
justify-content:center;
padding: 0;
margin: 0;
width: 100%;
}
</style>
<div style="display: flex;flex-direction: column;width: 100%;height: auto">
<lit-table id="tbl1" noheader style="height: auto;width: 100%;">
<lit-table-column title="key" data-index="key" width="200px" key="key" ></lit-table-column>
<lit-table-column title="value" data-index="value" key="value"></lit-table-column>
<lit-table-column title="time" data-index="time" key="time"></lit-table-column>
</lit-table>
<div id="back" style="display: none;cursor: pointer;
border-radius: 5px;width: 35px;justify-content: center;text-align: center;
padding: 7px 15px 7px 15px;border: 1px solid #aaa;color: #555">back</div>
<lit-pie-chart id="chart" style="height: auto;width: 100%"></lit-pie-chart>
</div>
<slot></slot>
`;
}
set data(json) {
if (json.recordSampleInfo && json.recordSampleInfo.length > 0) {
this.eventInfo = json.recordSampleInfo[window.eventIndex];
}
this.processNameMap = json.processNameMap;
this.threadNameMap = json.threadNameMap;
this.symbolsFileList = json.symbolsFileList;
this.SymbolMap = json.SymbolMap;
let rows = [];
if (json.deviceTime) {
rows.push({key: 'Device Time', value: json.deviceTime,time:''});
}
if (json.deviceType) {
rows.push({key: 'Device Type', value: json.deviceType,time:''});
}
if (json.osVersion) {
rows.push({key: 'OS Version', value: json.osVersion,time:''});
}
rows.push({key: 'Script Version', value: '1.0.0.241031',time:''});
if (json.deviceCommandLine) {
rows.push({key:'Record cmdline',value: json.deviceCommandLine,time:''});
}
rows.push({key:'Total Samples',value: '' + json.totalRecordSamples,time:''});
if (this.eventInfo) {
rows.push({key:'Event Type',
value:this.eventInfo.eventConfigName,time:this.getSampleWeight(this.eventInfo.eventCount)});
this.initChartData();
}
this.table.dataSource = rows;
}
getSampleWeight(count) {
if (this.eventInfo.eventConfigName.includes('task-clock') ||
this.eventInfo.eventConfigName.includes('cpu-clock')) {
return (count / 1000000.0).toFixed(3) + ' ms';
} else {
return ''+count;
}
}
getProcessName(pid) {
let name = this.processNameMap[pid];
return name ? `Process: ${pid} (${name})` : 'Process: '+pid.toString();
}
getThreadName(tid) {
let name = this.threadNameMap[tid];
return name ? `Thread: ${tid} (${name})` : 'Thread: '+tid.toString();
}
getLibName(fileId) {
return 'Library: '+this.symbolsFileList[fileId];
}
getFuncName(funcId) {
return 'Function: '+this.SymbolMap[funcId].symbol;
}
connectedCallback() {
this.table = this.shadowRoot.getElementById('tbl1');
this.chart = this.shadowRoot.getElementById('chart');
this.backBt = this.shadowRoot.getElementById('back');
this.backBt.addEventListener('mouseover',e=>{
this.backBt.style.borderColor = '#42b983';
this.backBt.style.color = '#42b983';
});
this.backBt.addEventListener('mouseout',e=>{
this.backBt.style.borderColor = '#aaa';
this.backBt.style.color = '#555';
});
this.chartMaxCount = 126;
this.backBt.addEventListener('click',this.back.bind(this));
this.chart.chartClickListener = (item) => {
if (item && item.id !== -1) {
let ds = this.table.dataSource;
if (item.name.startsWith('Process')) {
let pName = item.name.slice(8);
if (!ds.find(item => item.key === 'Process')) {
ds.push({key:'Process',value:pName,time:item.time});
}
this.chart.title = 'Threads in process ' + pName;
let find = this.eventInfo.processes.find(process => item.id === process.pid);
this.clickProcess(find);
} else if (item.name.startsWith('Thread')) {
let tName = item.name.slice(7);
if (!ds.find(item => item.key === 'Thread')) {
ds.push({key:'Thread',value:tName,time:item.time});
}
this.chart.title = 'Libraries in thread ' + tName;
let find = this.clickProcessData.threads.find(thread => item.id === thread.tid);
this.clickThread(find);
} else if (item.name.startsWith('Library')) {
let libName = item.name.slice(8);
if (!ds.find(item => item.key === 'Library')) {
ds.push({key:'Library',value:libName,time:item.time});
}
this.chart.title = 'Function in library ' + libName;
let find = this.clickThreadData.libs.find(lib => item.id === lib.fileId);
this.clickLib(find);
}
this.table.dataSource = ds;
}
};
}
initChartData() {
if (Array.isArray(this.eventInfo.processes)) {
function compare(property) {
return function (a,b) {
return b[property] - a[property];
}
}
this.eventInfo.processes.sort(compare('eventCount'));
let chartSource = [];
let otherValue = 0;
this.eventInfo.processes.forEach((process,index) => {
if (index < 14) {
chartSource.push({
id:process.pid,
name: this.getProcessName(process.pid),
value: (process.eventCount / this.eventInfo.eventCount).toFixed(3),
color: this.color[index],
time: this.getSampleWeight(process.eventCount)
});
} else {
otherValue += process.eventCount;
}
});
if (otherValue > 0) {
chartSource.push({
id : -1,
name: 'Other: Represents a collection of items which proporiton of count ranked very low',
value: (otherValue / this.eventInfo.eventCount).toFixed(3),
color: '#888888',
time: this.getSampleWeight(otherValue)
});
}
chartSource.sort((a, b) => { return b.time - a.time });
this.processDs = chartSource;
this.chart.dataSource = chartSource;
this.chart.title = 'Processes in event type ' + this.eventInfo.eventConfigName;
}
}
back() {
if (this.currentLevel === 'Function in Library') {
this.chart.title = 'Libraries in thread ' + this.getThreadName(this.clickThreadData.tid).slice(7);
this.chart.dataSource = this.libDs;
this.currentLevel = 'Library in Thread';
} else if (this.currentLevel === 'Library in Thread') {
this.chart.title = 'Threads in process ' + this.getProcessName(this.clickProcessData.pid).slice(8);
this.chart.dataSource = this.threadDs;
this.currentLevel = 'Thread in Process';
} else {
this.chart.title = 'Processes in event type ' + this.eventInfo.eventConfigName;
this.chart.dataSource = this.processDs;
this.backBt.style.display = 'none';
}
let row = this.table.dataSource;
if (Array.isArray(row)) {
row.pop();
this.table.dataSource = row;
}
}
/**
* chart click process
*
* @param process
*/
clickProcess(process)
{
if (process && process.threads) {
this.backBt.style.display = 'flex';
this.currentLevel = 'Thread in Process';
this.clickProcessData = process;
let chartSource = [];
let chartTotal = 0;
let count = 0;
let filter = process.threads.filter(item => item.eventCount / process.eventCount > 0.001);
let total = 0;
filter.forEach(item=>{ total += item.eventCount });
total = process.eventCount;
for (let item of filter) {
if (count >= this.chartMaxCount) {
break;
}
chartSource.push({
id:item.tid,
name: this.getThreadName(item.tid),
value: (item.eventCount / total).toFixed(6),
color: this.color[count % this.color.length],
time: this.getSampleWeight(item.eventCount)
});
chartTotal += item.eventCount;
count++;
}
if (count < this.chartMaxCount && chartTotal < process.eventCount) {
chartSource.push({
id : -1,
name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low',
value: ((process.eventCount - chartTotal) / process.eventCount).toFixed(6),
color: '#888888',
time: this.getSampleWeight(process.eventCount - chartTotal)
});
} else {
this.addOtherItem(count, total, chartTotal, chartSource);
}
chartSource.sort((a, b) => { return b.time - a.time });
this.threadDs = chartSource;
this.chart.dataSource = chartSource;
}
}
/**
* chart click thread
* @param thread
*/
clickThread(thread)
{
if (thread && thread.libs) {
this.currentLevel = 'Library in Thread';
let chartSource = [];
let chartTotal = 0;
let count = 0;
this.clickThreadData = thread;
let filter = thread.libs.filter(item => item.eventCount / thread.eventCount > 0.001);
let total = 0;
filter.forEach(item=>{ total += item.eventCount });
total = thread.eventCount;
for (let item of filter) {
if (count < this.chartMaxCount) {
chartSource.push({
id:item.fileId,
name: this.getLibName(item.fileId),
value: (item.eventCount / total).toFixed(6),
color: this.color[count % this.color.length],
time: this.getSampleWeight(item.eventCount)
});
chartTotal += item.eventCount;
count ++;
}
if (count >= this.chartMaxCount) {
break;
}
}
if (count < this.chartMaxCount && chartTotal < thread.eventCount) {
chartSource.push({
id : -1,
name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low',
value: ((thread.eventCount - chartTotal) / thread.eventCount).toFixed(6),
color: '#888888',
time: this.getSampleWeight(thread.eventCount - chartTotal)
});
} else {
this.addOtherItem(count, total, chartTotal, chartSource);
}
chartSource.sort((a, b) => { return b.time - a.time })
this.libDs = chartSource;
this.chart.dataSource = chartSource;
}
}
/**
* chart click lib
* @param lib
*/
clickLib(lib) {
if (lib && lib.functions) {
this.currentLevel = 'Function in Library'
let chartSource = [];
let chartTotal = 0;
let count = 0;
let filter = lib.functions.filter(item => item.counts[1] / lib.eventCount > 0.001);
let total = lib.eventCount;
let countTotal = 0;
filter.forEach(item=>{ countTotal += item.counts[1]});
for (let item of filter) {
if (count < this.chartMaxCount) {
chartSource.push({
id:item.symbol,
name: this.getFuncName(item.symbol),
value: (item.counts[1] / total).toFixed(6),
color: this.color[count % this.color.length],
time: this.getSampleWeight(item.counts[1])
});
chartTotal += item.counts[1];
count++;
}
if (count >= this.chartMaxCount) {
break;
}
}
if (count < this.chartMaxCount && countTotal < lib.eventCount) {
chartSource.push({
id : -1,
name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low',
value: ((lib.eventCount - countTotal) / lib.eventCount).toFixed(6),
color: '#888888',
time: this.getSampleWeight(lib.eventCount - countTotal)
});
} else {
this.addOtherItem(count,total,chartTotal,chartSource);
}
chartSource.sort((a, b) => { return b.time - a.time });
this.chart.dataSource = chartSource;
}
}
addOtherItem(count,total,chartTotal,chartSource) {
if (count >= this.chartMaxCount && total > chartTotal) {
chartSource.push({
id : -1,
name: 'Other: Represents a collection of items which proporiton of count less than 1% or ranked very low',
value: ((total - chartTotal) / total).toFixed(6),
color: '#888888',
time: this.getSampleWeight(total - chartTotal)
});
}
}
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-chart-statistics')) {
customElements.define('app-chart-statistics', AppChartStatistics);
}
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 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;
processes.slice(0).forEach(it => {
it.threads.sort((a, b) => { return b.eventCount - a.eventCount });
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;
let flame = document.createElement('app-chart-flame');
flame.style.width = '100%';
flame.style.height = 'auto';
flame.style.display = 'flex';
this.panel.appendChild(flame);
flame.data = {
type:this.type||1,
pid, processName, tid, threadName, eventCount, sampleCount, CallOrder: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 = window.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);
}
class AppFunction 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;
padding: 0;
margin: 0;
width: 100%;
}
</style>
<div style="width: 100%;display: flex;flex-direction: column">
<lit-table id="table" noheader style="width: 100%;">
<lit-table-column title="key" data-index="key" width="200px" key="key" ></lit-table-column>
<lit-table-column title="value" data-index="value" width="1fr" key="value"></lit-table-column>
</lit-table>
<lit-select id="typeSelect" default-value="1" mode="single" style="width:500px;margin-bottom: 10px;margin-top: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>
<app-chart-flame id="flame1"></app-chart-flame>
<app-chart-flame id="flame2"></app-chart-flame>
</div>
<slot></slot>
`;
}
getNodesMatchingFuncId(root, funcId) {
let nodes = [];
function recursiveFn(node) {
if (node.symbol === funcId) {
nodes.push(node);
} else {
for (let child of node.callStack) {
recursiveFn(child);
}
}
}
recursiveFn(root);
return nodes;
}
get dataSource() {
return this._dataSource;
}
getReverseData(rg, funId) {
}
set dataSource(val) {
this._dataSource = val;
this.table.dataSource = [
{key: 'Event Type', value: data.recordSampleInfo[window.eventIndex].eventConfigName},
{key: 'Process', value: val.process},
{key: 'Thread', value: val.thread},
{key: 'Library', value: val.library},
{key: 'Function', value: val.fun}
];
let filterProcess =
data.recordSampleInfo[window.eventIndex].processes.filter(it => it.pid === val.processId);
let filterThread = filterProcess[0].threads.filter(it => it.tid === val.threadId);
let filterG = filterThread[0].CallOrder;
let filterRG = filterThread[0].CalledOrder;
let c;
let rc;
let findF = (obj) => {
if (Array.isArray(obj)) {
obj.forEach(it => {
if (it.symbol === val.funId) {
c = it;
return;
} else {
if (it.callStack && it.callStack.length > 0) {
findF(it.callStack);
}
}
});
} else {
findF(obj.callStack);
}
};
// 合并倒树结构
let mergeRc = (obj) => {
let rc = {};
let _rc = this.getNodesMatchingFuncId(obj, val.funId);// 将rg树中 为funId值的节点 形成一个数组
let _sumCount = _rc.reduce((acc, cur) => acc + cur.subEvents, 0);// 计算eventCount值
let splitChildrenForNodes = (nodes) => {
let map = new Map();
for (let node of nodes) {
if (node.callStack) {
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 key of map.keys()) {
let subNodes = map.get(key);
res.push({
selfEvents: 0,
subEvents: subNodes.reduce((acc, cur) => acc + cur.subEvents, 0),
symbol: key,
callStack: splitChildrenForNodes(subNodes)
});
}
return res;
};
let children = splitChildrenForNodes(_rc);
return {
selfEvents: 0,
subEvents: _sumCount,
symbol: val.funId,
callStack: children
};
}
findF(filterG);
rc = mergeRc(filterRG);
c = mergeRc(filterG);
this.flame1.data = {
pid: val.processId,
processName: val.processName,
tid: val.threadId,
threadName: val.threadName,
eventCount: null,
sampleCount: null,
type: this.type || 1,
funcName: `Functions called by ${val.fun}`,
CallOrder: c
};
this.flame2.data = {
pid: val.processId,
processName: val.processName,
tid: val.threadId,
threadName: val.threadName,
eventCount: null,
sampleCount: null,
reverse: true,
type: this.type || 1,
funcName: `Functions calling ${val.fun}`,
CallOrder: rc
};
}
connectedCallback() {
this.table = this.shadowRoot.getElementById('table');
this.flame1 = this.shadowRoot.getElementById('flame1');
this.flame2 = this.shadowRoot.getElementById('flame2');
this.typeSelect = this.shadowRoot.getElementById('typeSelect');
this.typeSelect.onchange = (e) => {
this.type = parseInt(e.detail.value);
this.dataSource = this.dataSource;
};
}
attributeChangedCallback(name, oldValue, newValue) {
}
}
if (!customElements.get('app-function')) {
customElements.define('app-function', AppFunction);
}
class AppSimpleTable 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;
}
.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>
<div style="width: 100%;height: auto;">
<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>
<div style="display: flex;flex-direction: column;align-items: flex-end;width: 100%;">
<div style="display: flex;flex-direction: row;align-items: center;justify-content: space-between;width: 100%;margin-bottom: 10px">
<lit-input id="keyword" icon="search" placeholder="Please enter a keyword" style="width: 300px" allow-clear></lit-input>
<lit-select id="typeSelect" default-value="1" mode="single" style="width: 400px;margin-bottom: 10px">
<lit-select-option value="1">show percentage of event count</lit-select-option>
<lit-select-option value="2">show event count</lit-select-option>
<lit-select-option value="3">show event count in milliseconds</lit-select-option>
</lit-select>
</div>
<lit-table id="table" style="width: calc(100vw - 40px);">
<lit-table-column title="Total" data-index="total" width="100px" key="total" order></lit-table-column>
<lit-table-column title="Self" data-index="self" width="100px" key="self" order></lit-table-column>
<lit-table-column title="Samples" data-index="samples" width="100px" key="samples" order></lit-table-column>
<lit-table-column title="Process" data-index="process" width="250px" key="process" order></lit-table-column>
<lit-table-column title="Thread" data-index="thread" width="250px" key="thread" order></lit-table-column>
<lit-table-column title="Library" data-index="library" width="250px" key="library" order></lit-table-column>
<lit-table-column title="Function" data-index="fun" key="fun" order></lit-table-column>
</lit-table>
<div style="height: 140px">
<lit-pagination id="pagination" show-size-changer page-size="10" page-size-options="[20,50,200]" style="margin-top: 10px;">
<!-- <template slot="showTotal"><label>{{range[0]}}-{{range[1]}} of {{total}} items</label></template>-->
</lit-pagination>
</div>
</div>
</div>
<slot></slot>
`;
}
set data(json) {
if (json.recordSampleInfo && json.recordSampleInfo.length > 0) {
this.processNameMap = json.processNameMap;
this.threadNameMap = json.threadNameMap;
this.symbolsFileList = json.symbolsFileList;
this.SymbolMap = json.SymbolMap;
this.eventInfo = json.recordSampleInfo[window.eventIndex];
this.initTableData().then(() => {
this.loading.style.display = 'none';
this.pagination.current = 1;
this.pagination.total = this.source.length;
this.table.dataSource = this.paginationHandler(1,this.pagination.pageSize);
});
}
}
paginationHandler(page,pageSize,data) {
let offset = (page - 1) * pageSize;
let arr = [];
if (this.searchKey && this.searchKey.length > 0 && data) {
arr = (offset + pageSize >= data.length) ?
data.slice(offset, data.length) : data.slice(offset, offset + pageSize);
} else {
arr = (offset + pageSize >= this.source.length) ?
this.source.slice(offset, this.source.length) : this.source.slice(offset, offset + pageSize);
}
arr.forEach(item=>{
item.total = this.getSampleWeight(item.totalCount);
item.self = this.getSampleWeight(item.selfCount);
})
return arr;
}
async initTableData() {
this.source = [];
this.eventInfo.processes.forEach(process => {
process.threads.forEach(thread => {
thread.libs.forEach(lib => {
lib.functions.forEach(fun => {
this.source.push({
process: this.getProcessName(process.pid),
processId:process.pid,
thread: this.getThreadName(thread.tid),
threadId:thread.tid,
library: this.getLibName(lib.fileId),
libraryId:lib.fileId,
fun: this.getFuncName(fun.symbol),
funId: fun.symbol,
totalCount: fun.counts[2],
selfCount: fun.counts[1],
samples: fun.counts[0],
total:this.getSampleWeight(fun.counts[2]),
self:this.getSampleWeight(fun.counts[1]),
});
});
});
});
});
function compare(property) {
return function (a, b) {
return b[property] - a[property];
};
}
this.source.sort(compare('totalCount'));
}
getSampleWeight(count) {
if (this.eventType.value === '1') {
return (count * 100.0 / this.eventInfo.eventCount).toFixed(2) + '%';
} else if (this.eventType.value === '2') {
return count + '';
} else {
return (count / 1000000.0).toFixed(3);
}
}
getProcessName(pid) {
let name = this.processNameMap[pid];
return name ? `${pid} (${name})` : pid.toString();
}
getThreadName(tid) {
let name = this.threadNameMap[tid];
return name ? `${tid} (${name})` : tid.toString();
}
getLibName(fileId) {
return this.symbolsFileList[fileId];
}
getFuncName(funcId) {
return this.SymbolMap[funcId].symbol;
}
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.suffix = '';
this.loading = this.shadowRoot.getElementById('loading');
this.keyword = this.shadowRoot.getElementById('keyword');
this.eventType = this.shadowRoot.getElementById('typeSelect');
this.pagination = this.shadowRoot.getElementById('pagination');
this.table = this.shadowRoot.getElementById('table');
this.eventType.addEventListener('change',this.updateTableSource.bind(this));
this.pagination.addEventListener('onChange',this.updateTableSource.bind(this));
this.pagination.addEventListener('onShowSizeChange',this.updateTableSource.bind(this));
this.size && (this.size = this.size);
this.color && (this.color = this.color);
this.ds = [];
this.keyword.addEventListener('input',e=>{
if (this.searchKey !== this.keyword.value) {
this.searchKey = this.keyword.value;
this.pagination.current = 1;
this.ds = this.source.filter(item => item.process.indexOf(this.searchKey) !== -1
|| item.thread.indexOf(this.searchKey) !== -1
|| item.library.indexOf(this.searchKey) !== -1 || item.fun.indexOf(this.searchKey) !== -1);
this.pagination.total = this.ds.length;
this.table.dataSource =
this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize),this.ds);
}
});
this.keyword.addEventListener('onClear',e=>{
this.searchKey = undefined;
this.pagination.current = 1;
this.pagination.total = this.source.length;
this.table.dataSource =
this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize));
});
this.table.addEventListener('ColumnClick',evt => {
this.sortByColumn(evt.detail);
});
}
sortByColumn(detail) {
function compare(property,sort,type) {
return function (a, b) {
if (type === 'number') {
return sort === 2 ? b[property] - a[property] : a[property] - b[property];
} else {
if (b[property] > a[property]) {
return sort === 2 ? 1 : -1;
} else if (b[property] === a[property]) {
return 0;
} else {
return sort === 2 ? -1 : 1;
}
}
};
}
console.log(detail.key);
if (detail.key === 'total') {
this.source.sort(compare('totalCount',detail.sort,'number'));
} else if (detail.key === 'self') {
this.source.sort(compare('selfCount',detail.sort,'number'));
} else if (detail.key === 'samples') {
this.source.sort(compare('samples',detail.sort,'number'));
} else {
this.source.sort(compare(detail.key,detail.sort,'string'));
}
this.pagination.current = 1;
this.pagination.total = this.source.length;
this.table.dataSource = this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize));
}
updateTableSource(e) {
if (e.type === 'change') {
this.eventType.value = e.detail.value;
this.suffix = e.detail.value === '3' ? '(in ms)' : '';
}
if (this.ds.length !== 0) {
this.pagination.total = this.ds.length;
this.table.dataSource =
this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize),this.ds);
} else {
this.table.dataSource =
this.paginationHandler(this.pagination.current,parseInt(this.pagination.pageSize));
}
}
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-simple-table')) {
customElements.define('app-simple-table', AppSimpleTable);
}
(function () {
function createPromise(callback) {
if (callback) {
return new Promise((resolve, _) => callback(resolve));
}
return new Promise((resolve, _) => resolve());
}
function initGlobalObjects() {
let recordData = document.querySelector('#record_data').textContent;
if (recordData.trim().length>0) {
return new Promise((resolve, reject) => {
resolve(JSON.parse(recordData));
});
} else {
return fetch('data.json').then(response => response.json());
}
}
function waitDocumentReady() {
return createPromise((resolve) => document.addEventListener('DOMContentLoaded', resolve));
}
createPromise()
.then(waitDocumentReady)
.then(initGlobalObjects)
.then((json) => {
window.data = json;
window.eventIndex = 0;
let eventSelector = document.querySelector('#events');
if (json.recordSampleInfo && json.recordSampleInfo.length > 0) {
let events = [];
json.recordSampleInfo.forEach((e, index) => {
events.push({key:index+'',val:e.eventConfigName})
});
eventSelector.dataSource = events;
}
let chart = document.querySelector('#chart-statistics');
let loading = document.querySelector('#loading');
let table = document.querySelector('#sample-table');
let appFunc = document.querySelector('#function');
let flame = document.querySelector('#flame-graph');
let tabs = document.querySelector('#tabs')
let pane4 = document.querySelector('#pane4')
chart.data = json;
table.data = json;
table.addEventListener('onRowClick', e => {
pane4.hide = false;
tabs.activePane('4');
appFunc.dataSource = e.detail;
});
tabs.onTabClick = (e) => {
if (e.detail.key === '3') {
flame.isFinished = false;
flame.data = json;
}
};
eventSelector.addEventListener('change',(e)=>{
loading.style.display = 'flex';
pane4.hide = true;
window.eventIndex = parseInt( e.detail.value);
chart.data = json;
table.data = json;
flame.isFinished = false;
flame.data = json;
if (tabs.activekey === '4') {
tabs.activePane('1');
}
loading.style.display = 'none';
});
});
}())
</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">
<span style="font-weight: bold;margin-right: 10px">Event Type :</span>
<lit-select id="events" default-value="0" style="width: 400px"></lit-select>
</div>
<lit-tabs id='tabs' position="top-left" activekey="1" mode="flat">
<lit-tabpane id="pane1" tab="Chart Statistics" key="1">
<app-chart-statistics id="chart-statistics"></app-chart-statistics>
</lit-tabpane>
<lit-tabpane id="pane2" tab="Sample Table" key="2">
<app-simple-table id="sample-table"></app-simple-table>
</lit-tabpane>
<lit-tabpane id="pane3" tab="Flame Graph" key="3">
<app-flame-graph id="flame-graph"></app-flame-graph>
</lit-tabpane>
<lit-tabpane id="pane4" tab="Function" key="4" hide>
<app-function id="function" style="width: 100%"></app-function>
</lit-tabpane>
</lit-tabs>
</div>
<script id="record_data" type="application/json">