update add login modules

This commit is contained in:
2025-11-28 20:05:31 +08:00
parent b310413bfc
commit 2ae2b3ab4c
15 changed files with 1365 additions and 68 deletions

View 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()}>&times;</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