You've already forked pure-component
1628 lines
53 KiB
JavaScript
1628 lines
53 KiB
JavaScript
// 请求器组件
|
||
class API {
|
||
constructor(options = {}) {
|
||
// 接口基础地址
|
||
this.baseUrl = ''
|
||
// 合并配置
|
||
Object.assign(this, options)
|
||
}
|
||
|
||
// 创建新的 XMLHttpRequest 实例
|
||
createXHR() {
|
||
return new XMLHttpRequest()
|
||
}
|
||
|
||
// 设置请求头
|
||
setHeaders(xhr, headers = {}) {
|
||
for (let key in headers) {
|
||
xhr.setRequestHeader(key, headers[key])
|
||
}
|
||
}
|
||
|
||
// 格式化数据
|
||
formatData(data) {
|
||
if (typeof data !== 'string') return data
|
||
|
||
try {
|
||
return JSON.parse(data)
|
||
} catch (e) {
|
||
return data
|
||
}
|
||
}
|
||
|
||
// 中断请求
|
||
abort(xhr, callback = null) {
|
||
if (xhr) {
|
||
xhr.abort()
|
||
callback && callback()
|
||
}
|
||
}
|
||
|
||
// 处理响应
|
||
handleResponse(xhr, callback, success, fail, error) {
|
||
try {
|
||
const response = this.formatData(xhr.responseText)
|
||
if (xhr.status >= 200 && xhr.status < 300) {
|
||
callback && callback(response)
|
||
success && success(response)
|
||
} else {
|
||
const err = new Error(`HTTP Error ${xhr.status}`)
|
||
error && error(err)
|
||
fail && fail(err)
|
||
}
|
||
} catch (e) {
|
||
error && error(e)
|
||
fail && fail(e)
|
||
}
|
||
}
|
||
|
||
// get请求
|
||
get(options = {}) {
|
||
const defaultOption = {
|
||
url: '',
|
||
headers: null,
|
||
callback: null,
|
||
success: null,
|
||
fail: null,
|
||
error: null,
|
||
async: true,
|
||
}
|
||
|
||
const config = Object.assign({}, defaultOption, options)
|
||
const { url, callback, success, fail, error, async, headers } = config
|
||
const xhr = this.createXHR()
|
||
|
||
xhr.open('GET', `${this.baseUrl}${url}`, async)
|
||
|
||
if (headers) {
|
||
this.setHeaders(xhr, headers)
|
||
}
|
||
|
||
xhr.addEventListener('error', err => {
|
||
error && error(err)
|
||
fail && fail(err)
|
||
})
|
||
|
||
xhr.onreadystatechange = () => {
|
||
if (xhr.readyState === 4) {
|
||
this.handleResponse(xhr, callback, success, fail, error)
|
||
}
|
||
}
|
||
|
||
xhr.send()
|
||
return xhr
|
||
}
|
||
|
||
// post请求
|
||
post(options = {}) {
|
||
const defaultOption = {
|
||
url: '',
|
||
data: null,
|
||
headers: null,
|
||
progress: null,
|
||
callback: null,
|
||
success: null,
|
||
fail: null,
|
||
error: null,
|
||
async: true,
|
||
}
|
||
|
||
const config = Object.assign({}, defaultOption, options)
|
||
const { url, data, progress, callback, success, error, fail, async, headers } = config
|
||
const xhr = this.createXHR()
|
||
|
||
xhr.open('POST', `${this.baseUrl}${url}`, async)
|
||
|
||
if (headers) {
|
||
this.setHeaders(xhr, headers)
|
||
}
|
||
|
||
if (progress) {
|
||
xhr.upload.addEventListener('progress', evt => {
|
||
progress(evt)
|
||
})
|
||
}
|
||
|
||
xhr.addEventListener('error', err => {
|
||
error && error(err)
|
||
fail && fail(err)
|
||
})
|
||
|
||
xhr.onreadystatechange = () => {
|
||
if (xhr.readyState === 4) {
|
||
this.handleResponse(xhr, callback, success, fail, error)
|
||
}
|
||
}
|
||
|
||
xhr.send(data)
|
||
return xhr
|
||
}
|
||
}
|
||
|
||
// UI组件
|
||
class UI {
|
||
constructor(registry = null) {
|
||
const that = this
|
||
// 属性转换方法
|
||
const parseBoolean = value => {
|
||
return value !== null && value !== 'false'
|
||
}
|
||
// 搜索框
|
||
this.Search = class Input extends HTMLElement {
|
||
// 可用属性
|
||
static observedAttributes = ['value', 'placeholder', 'disabled', 'max', 'wrap']
|
||
static get observedAttributes() {
|
||
return ['value', 'placeholder', 'disabled', 'max', 'wrap']
|
||
}
|
||
// 构造函数
|
||
constructor() {
|
||
super()
|
||
this.attachShadow({ mode: 'open' })
|
||
// 输入框
|
||
this.input = null
|
||
// 输入框值
|
||
this.value = ''
|
||
// 输入框占位符
|
||
this.placeholder = '搜索...'
|
||
// 输入框禁用
|
||
this.disabled = false
|
||
// 输入框最大长度
|
||
this.max = Infinity
|
||
// 是否换行
|
||
this.wrap = false
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
this.bindEvent()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
if (this[name] === newValue) return
|
||
|
||
this[name] = name === 'disabled' || name === 'wrap' ? parseBoolean(newValue) : newValue
|
||
|
||
if (!this.input) return
|
||
|
||
switch (name) {
|
||
case 'disabled':
|
||
const inputEl = this.shadowRoot.querySelector('.search-input')
|
||
if (inputEl) {
|
||
inputEl.toggleAttribute('disabled', this.disabled)
|
||
inputEl.contentEditable = !this.disabled
|
||
const clearBtn = this.shadowRoot.querySelector('.search-clear')
|
||
if (clearBtn) {
|
||
clearBtn.classList.toggle('visible')
|
||
}
|
||
}
|
||
break
|
||
case 'value':
|
||
const searchInput = this.shadowRoot.querySelector('.search-input')
|
||
if (searchInput) {
|
||
searchInput.textContent = newValue
|
||
}
|
||
break
|
||
}
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const shadow = this.shadowRoot
|
||
shadow.innerHTML = `
|
||
<style>
|
||
.search {
|
||
--background-color: #fff;
|
||
--border-color: #ccc;
|
||
--placeholder-color: var(--border-color);
|
||
--clear-color: var(--border-color);
|
||
--clear-hover-color: #333;
|
||
--border-radius: 5px;
|
||
--padding: .5em 1em;
|
||
--min-width: 150px;
|
||
--gap: .5em;
|
||
--scrollbar-color: var(--border-color);
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 1em;
|
||
position: relative;
|
||
background: var(--background-color);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: var(--border-radius);
|
||
padding: var(--padding);
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
min-width: var(--min-width);
|
||
gap: var(--gap);
|
||
cursor: text;
|
||
}
|
||
.search-input {
|
||
width: 100%;
|
||
font-size: 1em;
|
||
outline: none;
|
||
transition: all .2s;
|
||
resize: none;
|
||
min-height: 1em;
|
||
overflow: hidden;
|
||
}
|
||
.search-input::-webkit-scrollbar {
|
||
width: 0;
|
||
height: .1em;
|
||
}
|
||
.search-input::-webkit-scrollbar-thumb {
|
||
background: var(--scrollbar-color);
|
||
}
|
||
.search-input:empty::before {
|
||
content: attr(placeholder);
|
||
color: var(--placeholder-color);
|
||
}
|
||
.search-input:focus::before {
|
||
display: none;
|
||
}
|
||
.search:focus-within {
|
||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||
}
|
||
.search-clear {
|
||
cursor: pointer;
|
||
color: var(--clear-color);
|
||
width: 20px;
|
||
height: 20px;
|
||
background: none;
|
||
border: none;
|
||
opacity: 0;
|
||
transition: all .3s;
|
||
position: relative;
|
||
flex-shrink: 0;
|
||
pointer-events: none;
|
||
}
|
||
.search-clear.visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
.search-clear svg {
|
||
fill: var(--clear-color);
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
pointer-events: none;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
</style>
|
||
<div class="search" part="search">
|
||
<slot name="prefix"></slot>
|
||
<div class="search-input" part="search-input"
|
||
placeholder="${this.placeholder}"
|
||
${this.disabled ? 'disabled' : ''}
|
||
contenteditable="${!this.disabled}">
|
||
${this.value}
|
||
</div>
|
||
<button class="search-clear" part="search-clear">
|
||
${that.Icon.clear}
|
||
</button>
|
||
<slot name="append"></slot>
|
||
</div>
|
||
`
|
||
|
||
this.input = shadow.querySelector('.search-input')
|
||
this.input.textContent = ''
|
||
this.value = ''
|
||
// 初始清除按钮状态
|
||
this.updateClearButton()
|
||
|
||
// 换行处理
|
||
if (this.wrap) {
|
||
this.input.style.whiteSpace = 'pre-wrap'
|
||
this.input.style.overflowY = 'auto'
|
||
} else {
|
||
this.input.style.whiteSpace = 'nowrap'
|
||
this.input.style.overflowX = 'auto'
|
||
}
|
||
}
|
||
// 清空输入框
|
||
updateClearButton() {
|
||
const clearBtn = this.shadowRoot.querySelector('.search-clear')
|
||
if (clearBtn) {
|
||
clearBtn.classList.toggle('visible', this.value.length > 0)
|
||
}
|
||
}
|
||
clear() {
|
||
this.input.textContent = ''
|
||
this.value = ''
|
||
this.updateClearButton()
|
||
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
|
||
}
|
||
// 绑定事件
|
||
bindEvent() {
|
||
const clearBtn = this.shadowRoot.querySelector('.search-clear')
|
||
const inputEl = this.input
|
||
|
||
clearBtn.addEventListener('click', e => {
|
||
e.preventDefault()
|
||
this.clear()
|
||
})
|
||
|
||
inputEl.addEventListener('input', e => {
|
||
let newValue = inputEl.textContent.replace(/[\n]/g, '')
|
||
|
||
if (newValue.length > this.max) {
|
||
newValue = newValue.substring(0, this.max)
|
||
inputEl.textContent = newValue
|
||
}
|
||
|
||
this.value = newValue
|
||
this.updateClearButton()
|
||
|
||
// 移动光标到末尾
|
||
const range = document.createRange()
|
||
const sel = window.getSelection()
|
||
range.selectNodeContents(inputEl)
|
||
range.collapse(false)
|
||
sel.removeAllRanges()
|
||
sel.addRange(range)
|
||
|
||
this.dispatchEvent(new CustomEvent('change', { detail: this.value }))
|
||
})
|
||
|
||
inputEl.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault()
|
||
this.dispatchEvent(new CustomEvent('enter', { detail: this.value }))
|
||
}
|
||
})
|
||
|
||
inputEl.addEventListener('blur', () => {
|
||
this.dispatchEvent(new CustomEvent('blur'))
|
||
})
|
||
|
||
inputEl.addEventListener('focus', () => {
|
||
this.dispatchEvent(new CustomEvent('focus'))
|
||
})
|
||
}
|
||
}
|
||
// 下拉框
|
||
this.Select = class Select extends HTMLElement {
|
||
// 可用属性
|
||
static get observedAttributes() {
|
||
return ['placeholder', 'list', 'value', 'current', 'disabled', 'focus']
|
||
}
|
||
// 构造函数
|
||
constructor() {
|
||
super()
|
||
// 下拉框
|
||
this.select = null
|
||
this.ele = null
|
||
// 下拉框列表
|
||
this.list = '默认选项'
|
||
this.dl = null
|
||
this.options = []
|
||
// 下拉框当前下标
|
||
this.current = 0
|
||
// 下拉框值
|
||
this.value = ''
|
||
// 下拉框占位符
|
||
this.placeholder = null
|
||
// 下拉框禁用
|
||
this.disabled = false
|
||
// 下拉框按钮
|
||
this.button = null
|
||
// 下拉框焦点
|
||
this.focus = false
|
||
this.selected = false
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
this[name] = newValue
|
||
|
||
if (name == 'disabled') {
|
||
if (eval(this.disabled)) {
|
||
this.ele.querySelector('.select-button').setAttribute('disabled', eval(this.disabled))
|
||
} else {
|
||
this.ele.querySelector('.select-button').removeAttribute('disabled')
|
||
}
|
||
} else if (name == 'value') {
|
||
this.dl.innerHTML = ''
|
||
this.addOption()
|
||
this.button.innerText = this.placeholder || this.options[this.current].text
|
||
}
|
||
}
|
||
// 重置
|
||
reset() {
|
||
const { current, focus, options, placeholder, list } = this
|
||
|
||
if (focus) {
|
||
this.selected = false
|
||
this.select.classList.remove('selected')
|
||
}
|
||
this.value = options[current].value
|
||
this.button.innerText = placeholder || list.split(',')[current]
|
||
}
|
||
// 添加选项
|
||
addOption() {
|
||
const { list, value } = this
|
||
const options = list.split(',')
|
||
const optionValues = value.split(',')
|
||
|
||
this.dl.innerHTML = ''
|
||
|
||
// 添加选项
|
||
options.forEach((item, index) => {
|
||
const dd = document.createElement('dd')
|
||
dd.innerText = item
|
||
dd.value = optionValues[index]
|
||
dd.className = dd.part = 'select-option'
|
||
if (index === Number(this.current)) {
|
||
dd.classList.add('selected')
|
||
}
|
||
this.options.push({ text: item, value: optionValues[index] })
|
||
this.dl.appendChild(dd)
|
||
})
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const { focus, disabled, placeholder, options, current } = this
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
this.select = document.createElement('div')
|
||
this.select.className = this.select.part = 'select'
|
||
this.dl = document.createElement('dl')
|
||
const dl = this.dl
|
||
dl.className = dl.part = 'select-list'
|
||
dl.setAttribute('popover', '')
|
||
this.button = document.createElement('button')
|
||
const button = this.button
|
||
button.disabled = disabled
|
||
button.className = button.part = 'select-button'
|
||
button.popoverTargetElement = dl
|
||
|
||
const style = document.createElement('style')
|
||
style.innerHTML = `
|
||
.select {
|
||
position: relative;
|
||
font-size: 1em;
|
||
--background-color: #fff;
|
||
--border-color: #ccc;
|
||
--list-background-color: #fff;
|
||
--hover-background-color: #dee6ff;
|
||
--hover-text-color: #001eff;
|
||
--selected-background-color: #dee6ff;
|
||
--selected-text-color: #001eff;
|
||
--border-radius: 5px;
|
||
--min-width: 150px;
|
||
min-width: var(--min-width);
|
||
}
|
||
.select.selected button{
|
||
background: var(--selected-background-color);
|
||
color: var(--selected-text-color);
|
||
border: 1px solid transparent;
|
||
}
|
||
.select-button {
|
||
min-width: var(--min-width);
|
||
width: 100%;
|
||
padding: .5em 1em;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: var(--border-radius);
|
||
background: var(--background-color);
|
||
cursor: pointer;
|
||
font-size: 1em;
|
||
}
|
||
.select-button:disabled {
|
||
background: #f5f5f5;
|
||
cursor: not-allowed;
|
||
opacity: .9;
|
||
}
|
||
.select-button::after {
|
||
content: '▼';
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
}
|
||
.select-list {
|
||
position: absolute;
|
||
min-width: var(--min-width);
|
||
width: 100%;
|
||
background: var(--list-background-color);
|
||
border: none;
|
||
border-radius: var(--border-radius);
|
||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||
list-style: none;
|
||
padding: 0;
|
||
top: 0;
|
||
left: 0;
|
||
margin: 0;
|
||
}
|
||
.select-list dt,.select-list dd {
|
||
padding: .5em 1em;
|
||
margin: .2em;
|
||
border-radius: calc(var(--border-radius) / 2);
|
||
}
|
||
.select-list dt {
|
||
background: #f5f5f5;
|
||
cursor: not-allowed;
|
||
}
|
||
.select-list dd:hover,.select-list dd.selected {
|
||
background: var(--hover-background-color);
|
||
cursor: pointer;
|
||
color: var(--hover-text-color);
|
||
}
|
||
`
|
||
|
||
this.addOption()
|
||
button.innerText = placeholder || options[current].text
|
||
this.value = options[current].value
|
||
this.select.appendChild(button)
|
||
this.select.appendChild(dl)
|
||
shadow.appendChild(style)
|
||
shadow.appendChild(this.select)
|
||
this.ele = shadow
|
||
|
||
// 选项点击事件
|
||
dl.addEventListener('click', e => {
|
||
if (e.target.tagName === 'DD') {
|
||
const { innerText, value } = e.target
|
||
button.innerText = innerText
|
||
this.value = value
|
||
if (focus) {
|
||
this.selected = true
|
||
this.select.classList.add('selected')
|
||
}
|
||
// 移除选中状态
|
||
dl.querySelectorAll('.select-option').forEach(item => {
|
||
item.classList.remove('selected')
|
||
})
|
||
e.target.classList.add('selected')
|
||
dl.hidePopover()
|
||
this.dispatchEvent(new CustomEvent('change', { detail: e.target }))
|
||
}
|
||
})
|
||
|
||
// 按钮点击事件
|
||
button.addEventListener('click', () => {
|
||
const { left, top, height, width } = this.select.getBoundingClientRect()
|
||
dl.style.top = `${top + height}px`
|
||
dl.style.left = `${left}px`
|
||
dl.style.width = `${width}px`
|
||
})
|
||
}
|
||
}
|
||
// 加载中
|
||
this.Loading = class Loading extends HTMLElement {
|
||
static observedAttributes = ['content', 'inline', 'hidden']
|
||
constructor() {
|
||
super()
|
||
this.loading = null
|
||
// 加载中内容
|
||
this.content = ''
|
||
// 是否内联
|
||
this.inline = false
|
||
// 是否默认隐藏
|
||
this.hidden = true
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
if (name == 'content') {
|
||
this[name] = newValue
|
||
} else {
|
||
this[name] = eval(newValue)
|
||
}
|
||
}
|
||
//生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
!this.hidden && this.show()
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
const style = document.createElement('style')
|
||
const loading = document.createElement('div')
|
||
const mask = document.createElement('div')
|
||
const content = document.createElement('div')
|
||
mask.className = mask.part = 'loading'
|
||
loading.className = loading.part = 'loader'
|
||
content.className = content.part = 'loading-content'
|
||
content.innerText = this.content
|
||
|
||
style.innerHTML = `
|
||
.loading{
|
||
--background-color: rgba(0, 0, 0, 0.2);
|
||
--text-color: #333;
|
||
--shadow-color: rgba(0, 0, 0, 0.1);
|
||
--icon-color: white;
|
||
${this.inline ? 'position: absolute;' : 'position: fixed;'}
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
backdrop-filter: blur(5px);
|
||
display: none;
|
||
z-index: calc(Infinity + 1);
|
||
font-size: 1rem;
|
||
background: var(--background-color);
|
||
}
|
||
.loader {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 6em;
|
||
height: 6em;
|
||
filter: drop-shadow(0 0 10px var(--shadow-color));
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
}
|
||
.loading svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
fill: var(--icon-color);
|
||
}
|
||
.loading-content {
|
||
font-size: .9em;
|
||
color: var(--text-color);
|
||
white-space: nowrap;
|
||
margin-top: 1em;
|
||
font-weight: bold;
|
||
}
|
||
`
|
||
|
||
loading.innerHTML = `
|
||
<slot name="icon">
|
||
<svg width="135" height="140" viewBox="0 0 135 140" xmlns="http://www.w3.org/2000/svg">
|
||
<rect y="10" width="15" height="120" rx="6">
|
||
<animate attributeName="height"
|
||
begin="0.5s" dur="1s"
|
||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
<animate attributeName="y"
|
||
begin="0.5s" dur="1s"
|
||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
</rect>
|
||
<rect x="30" y="10" width="15" height="120" rx="6">
|
||
<animate attributeName="height"
|
||
begin="0.25s" dur="1s"
|
||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
<animate attributeName="y"
|
||
begin="0.25s" dur="1s"
|
||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
</rect>
|
||
<rect x="60" width="15" height="140" rx="6">
|
||
<animate attributeName="height"
|
||
begin="0s" dur="1s"
|
||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
<animate attributeName="y"
|
||
begin="0s" dur="1s"
|
||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
</rect>
|
||
<rect x="90" y="10" width="15" height="120" rx="6">
|
||
<animate attributeName="height"
|
||
begin="0.25s" dur="1s"
|
||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
<animate attributeName="y"
|
||
begin="0.25s" dur="1s"
|
||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
</rect>
|
||
<rect x="120" y="10" width="15" height="120" rx="6">
|
||
<animate attributeName="height"
|
||
begin="0.5s" dur="1s"
|
||
values="120;110;100;90;80;70;60;50;40;140;120" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
<animate attributeName="y"
|
||
begin="0.5s" dur="1s"
|
||
values="10;15;20;25;30;35;40;45;50;0;10" calcMode="linear"
|
||
repeatCount="indefinite" />
|
||
</rect>
|
||
</svg>
|
||
</slot>
|
||
`
|
||
loading.appendChild(content)
|
||
shadow.appendChild(style)
|
||
mask.appendChild(loading)
|
||
shadow.appendChild(mask)
|
||
this.loading = mask
|
||
}
|
||
// 显示loading
|
||
show(fn = null) {
|
||
this.hidden = false
|
||
this.loading.style.display = 'block'
|
||
this.loading.querySelector('.loading-content').innerText = this.content
|
||
fn && fn()
|
||
}
|
||
// 隐藏loading
|
||
hide(fn = null) {
|
||
this.hidden = true
|
||
this.loading.style.display = 'none'
|
||
fn && fn()
|
||
}
|
||
}
|
||
// TODO:泡泡列表
|
||
this.BubbleList = class BubbleList extends HTMLElement {
|
||
// 可用属性
|
||
static observedAttributes = ['list', 'value', 'current', 'disabled', 'rows', 'size', 'selected']
|
||
static get observedAttributes() {
|
||
return ['selected', 'current', 'disabled']
|
||
}
|
||
constructor() {
|
||
super()
|
||
// 泡泡列表
|
||
this.bubbleList = null
|
||
this.bubbles = []
|
||
// 泡泡列表属性
|
||
this.list = []
|
||
// 泡泡列表值
|
||
this.val = null
|
||
// 泡泡列表当前下标
|
||
this.current = undefined
|
||
// 泡泡列表当前值
|
||
this.selected = ''
|
||
// 泡泡列表禁用
|
||
this.disabled = false
|
||
// 泡泡列表行数
|
||
this.rows = null
|
||
// 泡泡列表大小
|
||
this.size = 'auto'
|
||
// 初始化
|
||
this.init()
|
||
this.select()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
this.clear()
|
||
switch (name) {
|
||
// 列表
|
||
case 'list':
|
||
if (newValue) {
|
||
this.list = newValue.split(',')
|
||
}
|
||
break
|
||
// 值
|
||
case 'value':
|
||
if (newValue) {
|
||
this.val = newValue.split(',')
|
||
}
|
||
break
|
||
// 下标
|
||
case 'current':
|
||
this.current = newValue
|
||
this.bubbles.forEach((item, index) => {
|
||
if (index === newValue) {
|
||
item.classList.add('selected')
|
||
item.part = 'selected'
|
||
} else {
|
||
item.classList.remove('selected')
|
||
item.part = 'bubble'
|
||
}
|
||
})
|
||
break
|
||
// 行数
|
||
case 'rows':
|
||
this.rows = newValue
|
||
if (this.rows) {
|
||
const { rows } = this
|
||
this.bubbleList.style.height = `calc(${Number(rows) * 2.5}em - var(--gap))`
|
||
this.bubbleList.style.overflow = 'auto'
|
||
}
|
||
break
|
||
default:
|
||
this[name] = newValue
|
||
break
|
||
}
|
||
|
||
if (this.val) {
|
||
this.list.forEach((item, index) => {
|
||
this.add(item, this.val[index])
|
||
})
|
||
} else {
|
||
this.list.forEach(item => {
|
||
this.add(item)
|
||
})
|
||
}
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
this.bubbleList = document.createElement('div')
|
||
this.bubbleList.className = this.bubbleList.part = 'bubble-list'
|
||
const style = document.createElement('style')
|
||
|
||
style.innerHTML = `
|
||
.bubble-list {
|
||
--background-color: #3297f3;
|
||
--text-color: #fff;
|
||
--shadow-color: rgba(0, 0, 0, 0.1);
|
||
--hover-background-color: #7983ff;
|
||
--hover-text-color: #fff;
|
||
--border: 1px solid transparent;
|
||
--hover-border: 1px solid transparent;
|
||
--scrollbar-width: 1px;
|
||
--scrollbar-color: transparent;
|
||
--gap:.5em;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
font-size: 1.2em;
|
||
gap: var(--gap);
|
||
user-select: none;
|
||
}
|
||
.bubble-list::-webkit-scrollbar {
|
||
width: var(--scrollbar-width);
|
||
height: var(--scrollbar-width);
|
||
}
|
||
.bubble-list::-webkit-scrollbar-thumb {
|
||
background: var(--scrollbar-color);
|
||
border-radius: 5px;
|
||
}
|
||
.bubble {
|
||
padding: .2em 1.5em;
|
||
border-radius: calc(infinity * 1px);
|
||
background: var(--background-color);
|
||
color: var(--text-color);
|
||
font-size: 1em;
|
||
box-shadow: 0 0 5px var(--shadow-color);
|
||
cursor: pointer;
|
||
transition: all .2s;
|
||
border: var(--border);
|
||
}
|
||
.bubble.selected,.bubble:hover:not([disabled]) {
|
||
background: var(--hover-background-color);
|
||
box-shadow: 0 0 10px var(--shadow-color);
|
||
color: var(--hover-text-color);
|
||
border: var(--hover-border);
|
||
}
|
||
`
|
||
shadow.appendChild(style)
|
||
shadow.appendChild(this.bubbleList)
|
||
}
|
||
// 添加泡泡
|
||
add(text = '', value = '') {
|
||
const { current, disabled, size } = this
|
||
const bubble = document.createElement('button')
|
||
bubble.className = bubble.part = 'bubble'
|
||
disabled && bubble.setAttribute('disabled', 'disabled')
|
||
bubble.innerText = text
|
||
bubble.value = value || text
|
||
bubble.style.width = size
|
||
|
||
if (size && size !== 'auto') {
|
||
bubble.style.whiteSpace = 'nowrap'
|
||
bubble.style.overflow = 'hidden'
|
||
bubble.style.textOverflow = 'ellipsis'
|
||
}
|
||
|
||
this.bubbles.push(bubble)
|
||
current && this.bubbles[current].classList.add('selected')
|
||
this.bubbleList.appendChild(bubble)
|
||
}
|
||
// 清空泡泡
|
||
clear() {
|
||
this.bubbleList.innerHTML = ''
|
||
this.bubbles = []
|
||
}
|
||
// 选中泡泡
|
||
select(fn = null) {
|
||
const { bubbleList } = this
|
||
bubbleList.addEventListener('click', e => {
|
||
bubbleList.querySelectorAll('.bubble').forEach(item => {
|
||
item.classList.remove('selected')
|
||
item.part = 'bubble'
|
||
})
|
||
if (e.target.className === 'bubble') {
|
||
e.target.classList.add('selected')
|
||
e.target.part = 'selected'
|
||
const { innerText, value } = e.target
|
||
this.selected = innerText
|
||
this.value = value || innerText
|
||
fn && fn(innerText, value)
|
||
}
|
||
this.dispatchEvent(new CustomEvent('change', { detail: this }))
|
||
})
|
||
}
|
||
}
|
||
// TODO:对话框
|
||
this.Dialog = class Dialog extends HTMLElement {
|
||
// 可用属性
|
||
static observedAttributes = ['subtitle', 'content', 'hidden']
|
||
constructor() {
|
||
super()
|
||
this.dialog = null
|
||
this.mask = null
|
||
this.subtitle = '提示'
|
||
this.content = ''
|
||
this.confirmText = '确定'
|
||
this.cancelText = '取消'
|
||
this.confirm = null
|
||
this.cancel = null
|
||
this.hidden = true
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
if (name == 'hidden') {
|
||
this[name] = eval(newValue)
|
||
} else {
|
||
this[name] = newValue
|
||
}
|
||
|
||
if (this.dialog) {
|
||
switch (name) {
|
||
case 'subtitle':
|
||
this.dialog.querySelector('.dialog-title').innerText = newValue
|
||
break
|
||
case 'content':
|
||
this.dialog.querySelector('.dialog-text').innerText = newValue
|
||
break
|
||
}
|
||
}
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const { subtitle, content, confirmText, cancelText, confirm, cancel, hidden } = this
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
const template = document.createElement('template')
|
||
const style = document.createElement('style')
|
||
const mask = document.createElement('div')
|
||
const ctx = document.createElement('div')
|
||
mask.className = mask.part = 'dialog-mask'
|
||
ctx.className = ctx.part = 'dialog-content'
|
||
this.dialog = document.createElement('div')
|
||
this.dialog.className = this.dialog.part = 'dialog'
|
||
template.content.appendChild(mask)
|
||
|
||
ctx.innerHTML += `
|
||
<span class="dialog-title" part="dialog-title"><slot name="title">${subtitle}</slot></span>
|
||
<div class="dialog-icon" part="dialog-icon"><slot name="icon">${that.Icon.task}</slot></div>
|
||
<div class="dialog-text" part="dialog-text"><slot name="content">${content}</slot></div>
|
||
<div class="dialog-btns" part="dialog-btns">
|
||
<slot name="button">
|
||
<button class="dialog-btn dialog-confirm">${confirmText}</button>
|
||
<button class="dialog-btn dialog-cancel">${cancelText}</button>
|
||
</slot>
|
||
</div>
|
||
<slot name="close"><i></i></slot>
|
||
`
|
||
mask.appendChild(ctx)
|
||
|
||
style.innerHTML = `
|
||
.dialog {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: calc(Infinity + 1);
|
||
display: block;
|
||
font-size: 1em;
|
||
--background-color: #fff;
|
||
--border-radius: 10px;
|
||
--shadow-color: rgba(0, 0, 0, 0.1);
|
||
--mask-color: rgba(0, 0, 0, 0.5);
|
||
--show-icon: block;
|
||
--min-width: 580px;
|
||
--min-height: 320px;
|
||
--btn-color: #fff;
|
||
--btn-background-color: linear-gradient(to bottom, #7983ff, #3297f3);
|
||
}
|
||
.dialog-mask {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: var(--mask-color);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
.dialog-content {
|
||
padding: 20px;
|
||
background:url(/static/v3/tipbg01.png) no-repeat,var(--background-color);
|
||
background-size: cover;
|
||
border-radius: var(--border-radius);
|
||
overflow: hidden;
|
||
box-shadow: 0 0 10px var(--shadow-color);
|
||
text-align: center;
|
||
min-width: var(--min-width);
|
||
min-height: var(--min-height);
|
||
color:black
|
||
}
|
||
.dialog-icon{
|
||
margin: 20px auto;
|
||
display: var(--show-icon);
|
||
}
|
||
.dialog-title {
|
||
font-size: 1.5em;
|
||
margin-bottom: 10px;
|
||
text-align: left;
|
||
display: block;
|
||
}
|
||
.dialog-text {
|
||
font-size: 1.2em;
|
||
margin-bottom: 20px;
|
||
}
|
||
.dialog-btns {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
width: 80%;
|
||
margin: 40px auto 0 auto;
|
||
}
|
||
.dialog-btn {
|
||
padding: 5px 20px;
|
||
margin: 0 10px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
background:var(--btn-background-color);
|
||
cursor: pointer;
|
||
color: var(--btn-color);
|
||
font-size: 1.2em;
|
||
}
|
||
`
|
||
|
||
template.content.appendChild(style)
|
||
this.dialog.style.display = hidden ? 'none' : 'block'
|
||
this.dialog.appendChild(template.content.cloneNode(true))
|
||
shadow.appendChild(this.dialog)
|
||
this.mask = this.dialog.querySelector('.dialog-mask')
|
||
const confirmBtn = this.dialog.querySelector('.dialog-confirm')
|
||
const cancelBtn = this.dialog.querySelector('.dialog-cancel')
|
||
|
||
// 绑定事件
|
||
confirmBtn.addEventListener('click', () => {
|
||
this.hide()
|
||
confirm && confirm(this)
|
||
})
|
||
cancelBtn.addEventListener('click', () => {
|
||
this.hide()
|
||
cancel && cancel(this)
|
||
})
|
||
}
|
||
// 显示对话框
|
||
show(detail = null) {
|
||
this.dialog.style.display = 'block'
|
||
this.hidden = false
|
||
this.dispatchEvent(new CustomEvent('show', { detail }))
|
||
}
|
||
// 隐藏对话框
|
||
hide(detail = null) {
|
||
this.dialog.style.display = 'none'
|
||
this.hidden = true
|
||
this.dispatchEvent(new CustomEvent('hide', { detail }))
|
||
}
|
||
// 设置对话框
|
||
set(options = {}) {
|
||
Object.assign(this, options)
|
||
}
|
||
}
|
||
// TODO:模态框
|
||
this.Modal = class Modal extends HTMLElement {
|
||
// 可用属性
|
||
static observedAttributes = ['subtitle', 'content', 'hidden']
|
||
constructor() {
|
||
super()
|
||
// 模态框
|
||
this.modal = null
|
||
// 遮罩层
|
||
this.mask = null
|
||
// 标题
|
||
this.subtitle = '标题'
|
||
// 内容
|
||
this.content = '第一行<br/><br/>第二行<br/><br/>第三行<br/><br/>第四行'
|
||
// 动画定时器
|
||
this.timer = null
|
||
// 是否隐藏
|
||
this.hidden = true
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
if (name == 'hidden') {
|
||
this[name] = eval(newValue)
|
||
} else {
|
||
this[name] = newValue
|
||
}
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const { subtitle, content, hidden } = this
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
const template = document.createElement('template')
|
||
const style = document.createElement('style')
|
||
this.modal = document.createElement('div')
|
||
this.modal.className = this.modal.part = 'modal'
|
||
this.mask = document.createElement('div')
|
||
this.mask.className = this.mask.part = 'modal-mask'
|
||
this.mask.popoverTargetElement = this.modal
|
||
|
||
template.innerHTML = `
|
||
<div class="modal-content" part="modal-content">
|
||
<slot name="title"><span part="modal-title" class="modal-title">${subtitle}</span></slot>
|
||
<slot name="content"><div part="modal-text" class="modal-text">${content}</div></slot>
|
||
</div>
|
||
`
|
||
style.innerHTML = `
|
||
.modal {
|
||
display: block;
|
||
font-size: 1rem;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 2;
|
||
--background-color: #fff;
|
||
--border-radius: 10px;
|
||
--shadow-color: rgba(0, 0, 0, 0.1);
|
||
--min-width: 200px;
|
||
--min-height: 120px;
|
||
--mask-color: rgba(0, 0, 0, 0.3);
|
||
--mask-blur: 8px;
|
||
transition: all .3s;
|
||
}
|
||
.modal-mask {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: var(--mask-color);
|
||
backdrop-filter: blur(var(--mask-blur));
|
||
-moz-backdrop-filter: blur(var(--mask-blur));
|
||
-webkit-backdrop-filter: blur(var(--mask-blur));
|
||
}
|
||
.modal-content {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
padding: 1em;
|
||
background: var(--background-color);
|
||
border-radius: var(--border-radius);
|
||
box-shadow: 0 0 10px var(--shadow-color);
|
||
text-align: center;
|
||
min-width: var(--min-width);
|
||
width: max-content;
|
||
min-height: var(--min-height);
|
||
box-sizing: border-box;
|
||
}
|
||
.modal-title {
|
||
font-size: 1.5em;
|
||
margin-block: 1vh;
|
||
display: block;
|
||
color: black;
|
||
}
|
||
.modal-text {
|
||
font-size: 1.2em;
|
||
}
|
||
`
|
||
|
||
template.content.appendChild(style)
|
||
this.modal.appendChild(this.mask)
|
||
this.modal.style.display = hidden ? 'none' : 'block'
|
||
this.modal.appendChild(template.content.cloneNode(true))
|
||
shadow.appendChild(this.modal)
|
||
}
|
||
// 动画
|
||
animate(action = 'open', params) {
|
||
const { modal } = this
|
||
|
||
this.timer && clearTimeout(this.timer)
|
||
|
||
switch (action) {
|
||
case 'open':
|
||
modal.style.display = 'block'
|
||
this.hidden = false
|
||
modal.style.opacity = 0
|
||
this.timer = setTimeout(() => {
|
||
modal.style.opacity = 1
|
||
modal.style.display = 'block'
|
||
this.dispatchEvent(new CustomEvent('open', { detail: modal, params }))
|
||
}, 100)
|
||
break
|
||
case 'close':
|
||
modal.style.opacity = 0
|
||
this.timer = setTimeout(() => {
|
||
modal.style.display = 'none'
|
||
this.hidden = true
|
||
this.dispatchEvent(new CustomEvent('close', { detail: modal, params }))
|
||
}, 300)
|
||
break
|
||
}
|
||
}
|
||
// 显示模态框
|
||
open(params = null) {
|
||
this.animate('open', params)
|
||
}
|
||
// 隐藏模态框
|
||
close(params = null) {
|
||
this.animate('close', params)
|
||
}
|
||
}
|
||
// TODO:选项卡
|
||
this.Tab = class Tab extends HTMLElement {
|
||
// 可用属性
|
||
static observedAttributes = ['list', 'current']
|
||
constructor() {
|
||
super()
|
||
// 选项卡
|
||
this.tabs = null
|
||
// 选项卡列表
|
||
this.list = null
|
||
// 选项卡当前下标
|
||
this.current = 0
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
this.setTab()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
this['list'] = newValue.split('|')
|
||
this['list'].map((item, index) => {
|
||
this['list'][index] = item.split('>')
|
||
})
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const { list } = this
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
const template = document.createElement('template')
|
||
const style = document.createElement('style')
|
||
this.tabs = document.createElement('div')
|
||
const tabList = document.createElement('dl')
|
||
tabList.className = tabList.part = 'tab-list'
|
||
this.tabs.className = this.tabs.part = 'tab-box'
|
||
|
||
template.innerHTML = `
|
||
<slot name='content'></slot>
|
||
`
|
||
style.innerHTML = `
|
||
.tab-box {
|
||
font-size: 1em;
|
||
width: 100%;
|
||
user-select: none;
|
||
--highlight-color: #001EFF;
|
||
--text-color: black;
|
||
--background-color: transparent;
|
||
--description-color: #424242;
|
||
--gap: 5em;
|
||
}
|
||
.tab-list {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: var(--gap);
|
||
margin: 0;
|
||
}
|
||
.tab-item {
|
||
padding: .6em 2em;
|
||
background: var(--background-color);
|
||
cursor: pointer;
|
||
transition: all .3s;
|
||
border-bottom: 3px solid transparent;
|
||
text-align: center;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
color: var(--text-color);
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
}
|
||
.tab-item:hover,.tab-item.active {
|
||
border-bottom: 3px solid var(--highlight-color);
|
||
}
|
||
.tab-item cite {
|
||
font-size: .7em;
|
||
color: var(--description-color);
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
margin-top: .4em;
|
||
pointer-events: none;
|
||
}
|
||
`
|
||
list.forEach((item, index) => {
|
||
const dd = document.createElement('dd')
|
||
dd.className = dd.part = 'tab-item'
|
||
dd.index = index
|
||
|
||
if (index === this.current) {
|
||
dd.classList.add('active')
|
||
}
|
||
|
||
// 判断是否有描述
|
||
if (item.length > 1) {
|
||
const cite = document.createElement('cite')
|
||
cite.className = cite.part = 'tab-description'
|
||
cite.innerText = item[1]
|
||
dd.innerText = item[0]
|
||
dd.appendChild(cite)
|
||
} else {
|
||
dd.innerText = item
|
||
}
|
||
|
||
tabList.appendChild(dd)
|
||
})
|
||
|
||
this.tabs.appendChild(tabList)
|
||
shadow.appendChild(style)
|
||
shadow.appendChild(this.tabs)
|
||
shadow.appendChild(template.content.cloneNode(true))
|
||
}
|
||
// 选项卡切换
|
||
setTab() {
|
||
this.tabs.addEventListener('click', e => {
|
||
if (e.target.tagName === 'DD') {
|
||
this.tabs.querySelectorAll('.tab-item').forEach(item => {
|
||
item.classList.remove('active')
|
||
})
|
||
e.target.classList.add('active')
|
||
this.dispatchEvent(new CustomEvent('change', { detail: e.target }))
|
||
}
|
||
})
|
||
}
|
||
}
|
||
// 提示条
|
||
this.Tip = class Tip extends HTMLElement {
|
||
// 可用属性
|
||
static get observedAttributes() {
|
||
return ['content', 'type', 'hidden', 'timeout']
|
||
}
|
||
constructor() {
|
||
super()
|
||
// 提示条
|
||
this.Tip = null
|
||
// 提示内容
|
||
this.content = '提示内容'
|
||
// 提示类型
|
||
this.type = 'info'
|
||
// 提示条隐藏
|
||
this.hidden = false
|
||
// 提示条定时器
|
||
this.timer = null
|
||
// 提示条显示时间
|
||
this.timeout = 3000
|
||
}
|
||
// 生命周期
|
||
connectedCallback() {
|
||
this.init()
|
||
!this.hidden && this.show()
|
||
}
|
||
// 属性变化
|
||
attributeChangedCallback(name, oldValue, newValue) {
|
||
switch (name) {
|
||
case 'hidden':
|
||
this[name] = newValue
|
||
if (this[name]) {
|
||
this.hide()
|
||
}
|
||
break
|
||
case 'timeout':
|
||
this[name] = Number(newValue)
|
||
break
|
||
case 'type':
|
||
this.Tip.className = this.Tip.part = `tip ${newValue}`
|
||
break
|
||
case 'content':
|
||
this[name] = newValue
|
||
break
|
||
}
|
||
}
|
||
// 初始化
|
||
init() {
|
||
const { content, type, hidden } = this
|
||
const shadow = this.attachShadow({ mode: 'open' })
|
||
const template = document.createElement('template')
|
||
const style = document.createElement('style')
|
||
this.Tip = document.createElement('div')
|
||
this.Tip.className = this.Tip.part = `tip ${type}`
|
||
const contentSlot = document.createElement('slot')
|
||
const prefixSlot = document.createElement('slot')
|
||
contentSlot.name = 'content'
|
||
prefixSlot.name = 'prefix'
|
||
const closeBtn = document.createElement('button')
|
||
closeBtn.className = closeBtn.part = 'tip-close'
|
||
closeBtn.innerHTML = that.Icon.clear
|
||
|
||
contentSlot.innerHTML = `<span class="tip-content" part="tip-content">${content}</span>`
|
||
prefixSlot.innerHTML = `<i class="tip-icon" part="tip-icon">${that.Icon.task}</i>`
|
||
style.innerHTML = `
|
||
.tip {
|
||
--icon-color: #333;
|
||
--close-color: #333;
|
||
--color: #78A2F5;
|
||
--background-color: #E7F3FF;
|
||
position: absolute;
|
||
top: 1vh;
|
||
left: 50%;
|
||
transform: translate(-50%,-150%);
|
||
padding: .5em .5em .5em 1em;
|
||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
|
||
border: 1px solid;
|
||
border-radius: 5px;
|
||
font-size: 16px;
|
||
z-index: calc(Infinity + 1);
|
||
transition: all .3s ease-out;
|
||
display: none;
|
||
align-items: center;
|
||
width: auto;
|
||
opacity: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
.tip-icon {
|
||
width: 1.3em;
|
||
height: 1.3em;
|
||
flex-shrink: 0;
|
||
margin-right: .5em;
|
||
--icon-color:var(--color);
|
||
}
|
||
.tip-icon svg{
|
||
width: 100%;
|
||
height: 100%;
|
||
fill: var(--icon-color);
|
||
}
|
||
.tip-content {
|
||
font-size: 1em;
|
||
font-weight: bold;
|
||
text-align: left;
|
||
min-width: 100px;
|
||
max-width: 400px;
|
||
}
|
||
.tip-close{
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
width: 2.2em;
|
||
height: 2.2em;
|
||
margin-left: .5em;
|
||
padding: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
.tip-close svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
fill: var(--close-color);
|
||
}
|
||
.tip {
|
||
background: var(--background-color);
|
||
color: var(--color);
|
||
}
|
||
.tip.success {
|
||
--color: #4CAF50;
|
||
--background-color: #E8F5E9;
|
||
}
|
||
.tip.warn {
|
||
--color: #ff9800;
|
||
--background-color: #FFF3E0;
|
||
}
|
||
.tip.error {
|
||
--color: #f44336;
|
||
--background-color: #FFEBEE;
|
||
}
|
||
`
|
||
|
||
// 关闭提示条
|
||
closeBtn.onclick = () => {
|
||
this.hide()
|
||
}
|
||
|
||
this.Tip.style.display = hidden ? 'none' : 'flex'
|
||
template.content.appendChild(prefixSlot)
|
||
template.content.appendChild(contentSlot)
|
||
this.Tip.appendChild(template.content.cloneNode(true))
|
||
this.Tip.appendChild(closeBtn)
|
||
shadow.appendChild(style)
|
||
shadow.appendChild(this.Tip)
|
||
}
|
||
// 动画
|
||
animate(action = 'show') {
|
||
const { Tip } = this
|
||
switch (action) {
|
||
case 'show':
|
||
Tip.style.display = 'flex'
|
||
setTimeout(() => {
|
||
Tip.style.transform = 'translate(-50%, 0)'
|
||
Tip.style.opacity = 1
|
||
}, 50)
|
||
break
|
||
case 'hide':
|
||
Tip.style.transform = 'translate(-50%, -150%)'
|
||
Tip.style.opacity = 0
|
||
Tip.addEventListener(
|
||
'transitionend',
|
||
() => {
|
||
Tip.style.display = 'none'
|
||
},
|
||
{ once: true }
|
||
)
|
||
break
|
||
}
|
||
}
|
||
// 显示提示条
|
||
show(msg = null) {
|
||
const { timeout, Tip, content } = this
|
||
Tip.querySelector('.tip-content').innerText = msg || content
|
||
|
||
if (Tip.querySelector('.tip-content').innerText !== '') {
|
||
this.animate('show')
|
||
|
||
if (timeout && timeout > 0) {
|
||
clearTimeout(this.timer)
|
||
this.timer = setTimeout(() => {
|
||
this.animate('hide')
|
||
}, timeout)
|
||
}
|
||
} else {
|
||
return false
|
||
}
|
||
}
|
||
// 隐藏提示条
|
||
hide() {
|
||
this.animate('hide')
|
||
}
|
||
}
|
||
|
||
// 注册组件
|
||
if (registry) {
|
||
registry.map(name => {
|
||
const componentName = name.toLowerCase()
|
||
switch (name.toLowerCase()) {
|
||
case 'ui-search':
|
||
this.registerComponent(componentName, this.Search)
|
||
break
|
||
case 'ui-select':
|
||
this.registerComponent(componentName, this.Select)
|
||
break
|
||
case 'ui-loading':
|
||
this.registerComponent(componentName, this.Loading)
|
||
break
|
||
case 'ui-bubble-list':
|
||
this.registerComponent(componentName, this.BubbleList)
|
||
break
|
||
case 'ui-dialog':
|
||
this.registerComponent(componentName, this.Dialog)
|
||
break
|
||
case 'ui-modal':
|
||
this.registerComponent(componentName, this.Modal)
|
||
break
|
||
case 'ui-tab':
|
||
this.registerComponent(componentName, this.Tab)
|
||
break
|
||
case 'ui-tip':
|
||
this.registerComponent(componentName, this.Tip)
|
||
break
|
||
}
|
||
})
|
||
} else {
|
||
throw new Error('请传入注册组件名称')
|
||
}
|
||
}
|
||
// 注册组件
|
||
registerComponent(name, component, extend = false) {
|
||
// 如果是扩展元素
|
||
if (extend) {
|
||
customElements.define(name, component, { extends: extend })
|
||
} else {
|
||
customElements.define(name, component)
|
||
}
|
||
}
|
||
// 组件图标
|
||
Icon = {
|
||
// 清除&关闭
|
||
clear: `<svg t="1713249570364" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2336"><path d="M673.28 331.968a32 32 0 0 0-45.312 0L512 448 395.904 331.968a32 32 0 0 0-45.248 0l-18.752 18.752a32 32 0 0 0 0 45.248L448 512 331.904 628.032a32 32 0 0 0 0 45.248l18.752 18.752a32 32 0 0 0 45.248 0L512 576l116.032 116.032a32 32 0 0 0 45.312 0l18.688-18.752a32 32 0 0 0 0-45.248L576 512l116.032-116.032a32 32 0 0 0 0-45.248l-18.688-18.752z" p-id="2337"></path></svg>`,
|
||
// 任务
|
||
task: `<svg t="1714280326335" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2382" width="200" height="200"><path d="M831.825474 63.940169H191.939717C121.2479 63.940169 63.940169 121.2479 63.940169 191.939717v639.885757C63.940169 902.517291 121.2479 959.825022 191.939717 959.825022h639.885757c70.691817 0 127.999548-57.307731 127.999548-127.999548V191.939717C959.825022 121.2479 902.517291 63.940169 831.825474 63.940169zM895.884854 831.998871A63.835408 63.835408 0 0 1 831.912173 895.884854H192.087827c-17.112123 0-33.270563-6.574639-45.372232-18.67631S127.880338 849.110994 127.880338 831.998871V192.001129A64.236389 64.236389 0 0 1 192.087827 127.880338h639.824346A64.037705 64.037705 0 0 1 895.884854 192.001129v639.997742z" p-id="2383"></path><path d="M791.998335 351.851551h-255.999097a31.970084 31.970084 0 0 0 0 63.940169h255.999097a31.970084 31.970084 0 0 0 0-63.940169zM791.998335 607.973471h-255.999097a31.970084 31.970084 0 0 0 0 63.940169h255.999097a31.970084 31.970084 0 0 0 0-63.940169zM344.001722 527.997686c-61.855792 0-111.985607 50.144265-111.985607 111.985606s50.144265 111.985607 111.985607 111.985607 111.985607-50.144265 111.985606-111.985607-50.129815-111.985607-111.985606-111.985606z m33.982213 145.982269a48.045438 48.045438 0 1 1 14.088511-33.982213 47.745605 47.745605 0 0 1-14.088511 33.985826zM417.395643 297.394035L311.999125 402.78694 270.6078 361.392003a31.970084 31.970084 0 1 0-45.213286 45.213285l63.997968 64.001581a31.970084 31.970084 0 0 0 45.213286 0l127.999548-127.999549a31.970084 31.970084 0 0 0-45.209673-45.213285z" p-id="2384"></path></svg>`,
|
||
}
|
||
}
|