mirror of
https://gitee.com/openharmony/developtools_hiperf
synced 2024-11-26 17:21:15 +00:00
2492e992d6
Signed-off-by:wenlong12 <wenlong12@huawei.com> Signed-off-by: wenlong12 <wenlong12@huawei.com>
5392 lines
209 KiB
HTML
5392 lines
209 KiB
HTML
<!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">
|