@kevisual/kv-login 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,351 @@
1
+ import { html, render, TemplateResult } from 'lit-html'
2
+
3
+ export interface KvMessageOptions {
4
+ type?: 'success' | 'error' | 'loading'
5
+ message: string
6
+ duration?: number
7
+ closable?: boolean
8
+ position?: 'center' | 'right'
9
+ }
10
+
11
+ class KvMessage extends HTMLElement {
12
+ private options: KvMessageOptions
13
+ private timer: number | null = null
14
+
15
+ constructor() {
16
+ super()
17
+ this.options = {
18
+ type: 'success',
19
+ message: '',
20
+ duration: 2000,
21
+ closable: true
22
+ }
23
+ }
24
+
25
+ connectedCallback() {
26
+ this.render()
27
+ }
28
+
29
+ setOptions(options: KvMessageOptions) {
30
+ this.options = { ...this.options, ...options }
31
+ this.render()
32
+ }
33
+
34
+ private render() {
35
+ const { type, message, closable } = this.options
36
+
37
+ const getTypeIcon = () => {
38
+ switch (type) {
39
+ case 'success':
40
+ return '✓'
41
+ case 'error':
42
+ return '✕'
43
+ case 'loading':
44
+ return html`<div class="loading-spinner"></div>`
45
+ default:
46
+ return ''
47
+ }
48
+ }
49
+
50
+ const template: TemplateResult = html`
51
+ <style>
52
+ :host {
53
+ display: block;
54
+ margin-bottom: 12px;
55
+ animation: slideIn 0.3s ease-out;
56
+ }
57
+
58
+ .message-container {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 8px;
62
+ padding: 12px 16px;
63
+ border-radius: 6px;
64
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
65
+ background: white;
66
+ position: relative;
67
+ min-width: 300px;
68
+ max-width: 500px;
69
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
70
+ font-size: 14px;
71
+ line-height: 1.4;
72
+ }
73
+
74
+ .message-container.success {
75
+ border-left: 4px solid #52c41a;
76
+ }
77
+
78
+ .message-container.error {
79
+ border-left: 4px solid #ff4d4f;
80
+ }
81
+
82
+ .message-container.loading {
83
+ border-left: 4px solid #1890ff;
84
+ }
85
+
86
+ .message-icon {
87
+ display: flex;
88
+ align-items: center;
89
+ justify-content: center;
90
+ width: 16px;
91
+ height: 16px;
92
+ flex-shrink: 0;
93
+ }
94
+
95
+ .success .message-icon {
96
+ color: #52c41a;
97
+ font-weight: bold;
98
+ }
99
+
100
+ .error .message-icon {
101
+ color: #ff4d4f;
102
+ font-weight: bold;
103
+ }
104
+
105
+ .loading .message-icon {
106
+ color: #1890ff;
107
+ }
108
+
109
+ .loading-spinner {
110
+ width: 14px;
111
+ height: 14px;
112
+ border: 2px solid #f3f3f3;
113
+ border-top: 2px solid #1890ff;
114
+ border-radius: 50%;
115
+ animation: spin 1s linear infinite;
116
+ }
117
+
118
+ .message-content {
119
+ flex: 1;
120
+ color: #333;
121
+ }
122
+
123
+ .message-close {
124
+ display: flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ width: 16px;
128
+ height: 16px;
129
+ cursor: pointer;
130
+ color: #999;
131
+ background: none;
132
+ border: none;
133
+ font-size: 12px;
134
+ border-radius: 50%;
135
+ transition: all 0.2s;
136
+ }
137
+
138
+ .message-close:hover {
139
+ color: #666;
140
+ background: #f0f0f0;
141
+ }
142
+
143
+ @keyframes slideIn {
144
+ from {
145
+ transform: translateX(100%);
146
+ opacity: 0;
147
+ }
148
+ to {
149
+ transform: translateX(0);
150
+ opacity: 1;
151
+ }
152
+ }
153
+
154
+ @keyframes slideOut {
155
+ from {
156
+ transform: translateX(0);
157
+ opacity: 1;
158
+ }
159
+ to {
160
+ transform: translateX(100%);
161
+ opacity: 0;
162
+ }
163
+ }
164
+
165
+ @keyframes spin {
166
+ 0% { transform: rotate(0deg); }
167
+ 100% { transform: rotate(360deg); }
168
+ }
169
+
170
+ .removing {
171
+ animation: slideOut 0.3s ease-out forwards;
172
+ }
173
+ </style>
174
+
175
+ <div class="message-container ${type}">
176
+ <div class="message-icon">
177
+ ${getTypeIcon()}
178
+ </div>
179
+ <div class="message-content">${message}</div>
180
+ ${closable ? html`
181
+ <button class="message-close" @click=${() => this.remove()}>&times;</button>
182
+ ` : ''}
183
+ </div>
184
+ `
185
+
186
+ render(template, this)
187
+
188
+ if (type !== 'loading' && this.options.duration && this.options.duration > 0) {
189
+ this.setTimer()
190
+ }
191
+ }
192
+
193
+ private setTimer() {
194
+ if (this.timer) {
195
+ clearTimeout(this.timer)
196
+ }
197
+
198
+ this.timer = window.setTimeout(() => {
199
+ this.remove()
200
+ }, this.options.duration)
201
+ }
202
+
203
+ remove() {
204
+ if (this.timer) {
205
+ clearTimeout(this.timer)
206
+ this.timer = null
207
+ }
208
+
209
+ this.classList.add('removing')
210
+
211
+ setTimeout(() => {
212
+ if (this.parentNode) {
213
+ this.parentNode.removeChild(this)
214
+ }
215
+ }, 300)
216
+ }
217
+
218
+ disconnectedCallback() {
219
+ if (this.timer) {
220
+ clearTimeout(this.timer)
221
+ this.timer = null
222
+ }
223
+ }
224
+ }
225
+
226
+ customElements.define('kv-message', KvMessage)
227
+
228
+ export class KvMessageManager {
229
+ private static instance: KvMessageManager
230
+ private container: HTMLElement | null = null
231
+ private defaultPosition: 'center' | 'right' = 'center'
232
+
233
+ static getInstance(): KvMessageManager {
234
+ if (!KvMessageManager.instance) {
235
+ KvMessageManager.instance = new KvMessageManager()
236
+ }
237
+ return KvMessageManager.instance
238
+ }
239
+
240
+ setDefaultPosition(position: 'center' | 'right') {
241
+ this.defaultPosition = position
242
+ }
243
+
244
+ private getContainer(position?: 'center' | 'right'): HTMLElement {
245
+ const finalPosition = position || this.defaultPosition
246
+
247
+ if (!this.container) {
248
+ this.container = document.getElementById('messages')
249
+ if (!this.container) {
250
+ this.container = document.createElement('div')
251
+ this.container.id = 'messages'
252
+
253
+ if (finalPosition === 'center') {
254
+ this.container.style.cssText = `
255
+ position: fixed;
256
+ top: 20px;
257
+ left: 50%;
258
+ transform: translateX(-50%);
259
+ z-index: 9999;
260
+ display: flex;
261
+ gap: 8px;
262
+ flex-direction: column;
263
+ align-items: center;
264
+ pointer-events: none;
265
+ `
266
+ } else {
267
+ this.container.style.cssText = `
268
+ position: fixed;
269
+ top: 20px;
270
+ right: 20px;
271
+ z-index: 9999;
272
+ display: flex;
273
+ gap: 8px;
274
+ flex-direction: column;
275
+ align-items: flex-end;
276
+ pointer-events: none;
277
+ `
278
+ }
279
+
280
+ document.body.appendChild(this.container)
281
+ }
282
+ }
283
+ return this.container
284
+ }
285
+
286
+ show(options: KvMessageOptions): KvMessage {
287
+ const container = this.getContainer(options.position)
288
+
289
+ const message = document.createElement('kv-message') as KvMessage
290
+ message.setOptions(options)
291
+
292
+ message.style.cssText = 'pointer-events: auto;'
293
+
294
+ container.appendChild(message)
295
+
296
+ return message
297
+ }
298
+
299
+ success(message: string, options?: { duration?: number; position?: 'center' | 'right'; closable?: boolean }): KvMessage {
300
+ return this.show({
301
+ type: 'success',
302
+ message,
303
+ duration: options?.duration || 2000,
304
+ position: options?.position,
305
+ closable: options?.closable
306
+ })
307
+ }
308
+
309
+ error(message: string, options?: { duration?: number; position?: 'center' | 'right'; closable?: boolean }): KvMessage {
310
+ return this.show({
311
+ type: 'error',
312
+ message,
313
+ duration: options?.duration || 3000,
314
+ position: options?.position,
315
+ closable: options?.closable
316
+ })
317
+ }
318
+
319
+ loading(message: string, options?: { position?: 'center' | 'right'; closable?: boolean }): KvMessage {
320
+ return this.show({
321
+ type: 'loading',
322
+ message,
323
+ duration: 0,
324
+ position: options?.position,
325
+ closable: options?.closable
326
+ })
327
+ }
328
+
329
+ remove(message: KvMessage) {
330
+ message.remove()
331
+ }
332
+
333
+ clear() {
334
+ const container = this.getContainer()
335
+ const messages = container.querySelectorAll('kv-message')
336
+ messages.forEach(message => {
337
+ (message as KvMessage).remove()
338
+ })
339
+ }
340
+ }
341
+
342
+ export const createMessage = () => KvMessageManager.getInstance()
343
+
344
+ // 将 createMessage 暴露到全局,以便 HTML 中的 JavaScript 可以使用
345
+ declare global {
346
+ interface Window {
347
+ createMessage: typeof createMessage
348
+ }
349
+ }
350
+
351
+ window.createMessage = createMessage
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ const entry = './src/main.ts';
4
+ export default defineConfig({
5
+ build: {
6
+ lib: {
7
+ entry,
8
+ name: 'KvLogin',
9
+ fileName: (format) => `kv-login.${format}.js`,
10
+ }
11
+ }
12
+ });
package/vite.config.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from 'vite';
2
+
3
+ const idDev = process.env.NODE_ENV === 'development';
4
+ export default defineConfig({
5
+ base: idDev ? '/' : '/root/kv-login-test/',
6
+ server: {
7
+ proxy: {
8
+ '/api': {
9
+ target: 'https://kevisual.xiongxiao.me',
10
+ changeOrigin: true,
11
+ secure: false,
12
+ }
13
+ }
14
+ }
15
+ });