update add login modules
This commit is contained in:
351
packages/kv-login/src/pages/kv-message.ts
Normal file
351
packages/kv-login/src/pages/kv-message.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
import { html, render, TemplateResult } from 'lit-html'
|
||||
|
||||
export interface KvMessageOptions {
|
||||
type?: 'success' | 'error' | 'loading'
|
||||
message: string
|
||||
duration?: number
|
||||
closable?: boolean
|
||||
position?: 'center' | 'right'
|
||||
}
|
||||
|
||||
class KvMessage extends HTMLElement {
|
||||
private options: KvMessageOptions
|
||||
private timer: number | null = null
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.options = {
|
||||
type: 'success',
|
||||
message: '',
|
||||
duration: 2000,
|
||||
closable: true
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render()
|
||||
}
|
||||
|
||||
setOptions(options: KvMessageOptions) {
|
||||
this.options = { ...this.options, ...options }
|
||||
this.render()
|
||||
}
|
||||
|
||||
private render() {
|
||||
const { type, message, closable } = this.options
|
||||
|
||||
const getTypeIcon = () => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return '✓'
|
||||
case 'error':
|
||||
return '✕'
|
||||
case 'loading':
|
||||
return html`<div class="loading-spinner"></div>`
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const template: TemplateResult = html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
background: white;
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
max-width: 500px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-container.success {
|
||||
border-left: 4px solid #52c41a;
|
||||
}
|
||||
|
||||
.message-container.error {
|
||||
border-left: 4px solid #ff4d4f;
|
||||
}
|
||||
|
||||
.message-container.loading {
|
||||
border-left: 4px solid #1890ff;
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.success .message-icon {
|
||||
color: #52c41a;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error .message-icon {
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loading .message-icon {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid #f3f3f3;
|
||||
border-top: 2px solid #1890ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.message-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 12px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.message-close:hover {
|
||||
color: #666;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.removing {
|
||||
animation: slideOut 0.3s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="message-container ${type}">
|
||||
<div class="message-icon">
|
||||
${getTypeIcon()}
|
||||
</div>
|
||||
<div class="message-content">${message}</div>
|
||||
${closable ? html`
|
||||
<button class="message-close" @click=${() => this.remove()}>×</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`
|
||||
|
||||
render(template, this)
|
||||
|
||||
if (type !== 'loading' && this.options.duration && this.options.duration > 0) {
|
||||
this.setTimer()
|
||||
}
|
||||
}
|
||||
|
||||
private setTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.remove()
|
||||
}, this.options.duration)
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
|
||||
this.classList.add('removing')
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.parentNode) {
|
||||
this.parentNode.removeChild(this)
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('kv-message', KvMessage)
|
||||
|
||||
export class KvMessageManager {
|
||||
private static instance: KvMessageManager
|
||||
private container: HTMLElement | null = null
|
||||
private defaultPosition: 'center' | 'right' = 'center'
|
||||
|
||||
static getInstance(): KvMessageManager {
|
||||
if (!KvMessageManager.instance) {
|
||||
KvMessageManager.instance = new KvMessageManager()
|
||||
}
|
||||
return KvMessageManager.instance
|
||||
}
|
||||
|
||||
setDefaultPosition(position: 'center' | 'right') {
|
||||
this.defaultPosition = position
|
||||
}
|
||||
|
||||
private getContainer(position?: 'center' | 'right'): HTMLElement {
|
||||
const finalPosition = position || this.defaultPosition
|
||||
|
||||
if (!this.container) {
|
||||
this.container = document.getElementById('messages')
|
||||
if (!this.container) {
|
||||
this.container = document.createElement('div')
|
||||
this.container.id = 'messages'
|
||||
|
||||
if (finalPosition === 'center') {
|
||||
this.container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
pointer-events: none;
|
||||
`
|
||||
} else {
|
||||
this.container.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
`
|
||||
}
|
||||
|
||||
document.body.appendChild(this.container)
|
||||
}
|
||||
}
|
||||
return this.container
|
||||
}
|
||||
|
||||
show(options: KvMessageOptions): KvMessage {
|
||||
const container = this.getContainer(options.position)
|
||||
|
||||
const message = document.createElement('kv-message') as KvMessage
|
||||
message.setOptions(options)
|
||||
|
||||
message.style.cssText = 'pointer-events: auto;'
|
||||
|
||||
container.appendChild(message)
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
success(message: string, options?: { duration?: number; position?: 'center' | 'right'; closable?: boolean }): KvMessage {
|
||||
return this.show({
|
||||
type: 'success',
|
||||
message,
|
||||
duration: options?.duration || 2000,
|
||||
position: options?.position,
|
||||
closable: options?.closable
|
||||
})
|
||||
}
|
||||
|
||||
error(message: string, options?: { duration?: number; position?: 'center' | 'right'; closable?: boolean }): KvMessage {
|
||||
return this.show({
|
||||
type: 'error',
|
||||
message,
|
||||
duration: options?.duration || 3000,
|
||||
position: options?.position,
|
||||
closable: options?.closable
|
||||
})
|
||||
}
|
||||
|
||||
loading(message: string, options?: { position?: 'center' | 'right'; closable?: boolean }): KvMessage {
|
||||
return this.show({
|
||||
type: 'loading',
|
||||
message,
|
||||
duration: 0,
|
||||
position: options?.position,
|
||||
closable: options?.closable
|
||||
})
|
||||
}
|
||||
|
||||
remove(message: KvMessage) {
|
||||
message.remove()
|
||||
}
|
||||
|
||||
clear() {
|
||||
const container = this.getContainer()
|
||||
const messages = container.querySelectorAll('kv-message')
|
||||
messages.forEach(message => {
|
||||
(message as KvMessage).remove()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const createMessage = () => KvMessageManager.getInstance()
|
||||
|
||||
// 将 createMessage 暴露到全局,以便 HTML 中的 JavaScript 可以使用
|
||||
declare global {
|
||||
interface Window {
|
||||
createMessage: typeof createMessage
|
||||
}
|
||||
}
|
||||
|
||||
window.createMessage = createMessage
|
||||
Reference in New Issue
Block a user