@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,466 @@
1
+ import { render, html } from 'lit-html'
2
+ import { loginHandle, checkWechat } from '../modules/login-handle.ts'
3
+ import { setWxerwma } from '../modules/wx/ws-login.ts';
4
+ import { useCreateLoginQRCode } from '../modules/wx-mp/qr.ts';
5
+ export const WX_MP_APP_ID = "wxff97d569b1db16b6";
6
+ interface LoginMethod {
7
+ id: LoginMethods
8
+ name: string
9
+ icon: string
10
+ appid?: string
11
+ }
12
+ const DefaultLoginMethods: LoginMethod[] = [
13
+ { id: 'password', name: '密码登录', icon: '🔒' },
14
+ { id: 'wechat', name: '微信登录', icon: '💬', appid: "wx9378885c8390e09b" },
15
+ { id: 'wechat-mp', name: '微信公众号登录', icon: '💬', appid: WX_MP_APP_ID },
16
+ { id: 'phone', name: '手机号登录', icon: '📱' }
17
+ ]
18
+ type LoginMethods = 'password' | 'phone' | 'wechat' | 'wechat-mp'
19
+ const getLoginMethodByDomain = (): LoginMethod[] => {
20
+ const domain = window.location.hostname
21
+ let methods: LoginMethods[] = []
22
+ switch (domain) {
23
+ case 'kevisual.xiongxiao.me':
24
+ methods = ['password', 'wechat-mp']
25
+ break;
26
+ case 'kevisual.cn':
27
+ methods = ['password', 'wechat']
28
+ break;
29
+ default:
30
+ methods = ['password', 'phone', 'wechat', 'wechat-mp']
31
+ break;
32
+ }
33
+ return DefaultLoginMethods.filter(method => methods.includes(method.id))
34
+ }
35
+ console.log('可用登录方式:', getLoginMethodByDomain().map(m => m.name).join(', '));
36
+ class KvLogin extends HTMLElement {
37
+ private selectedMethod: LoginMethods = 'password'
38
+
39
+ private loginMethods: LoginMethod[] = getLoginMethodByDomain();
40
+ setLoginMethods(methods: LoginMethod[]) {
41
+ this.loginMethods = methods
42
+ this.render()
43
+ }
44
+ constructor() {
45
+ super()
46
+ }
47
+
48
+ connectedCallback() {
49
+ this.attachShadow({ mode: 'open' })
50
+ this.render()
51
+ this.bindEvents()
52
+ checkWechat()
53
+
54
+ }
55
+ #clearTimer: any = null;
56
+ private selectLoginMethod(methodId: LoginMethods) {
57
+ this.selectedMethod = methodId
58
+ this.render()
59
+ if (this.#clearTimer) {
60
+ this.#clearTimer();
61
+ this.#clearTimer = null;
62
+ }
63
+ }
64
+ private getMethodData(methodId: LoginMethods): LoginMethod | undefined {
65
+ return this.loginMethods.find(method => method.id === methodId);
66
+ }
67
+ private bindEvents() {
68
+ if (!this.shadowRoot) return
69
+
70
+ // 使用事件委托来处理登录方式切换
71
+ this.shadowRoot.addEventListener('click', (e) => {
72
+ const target = e.target as HTMLElement
73
+ const methodButton = target.closest('.login-method')
74
+ if (methodButton) {
75
+ const methodId = methodButton.getAttribute('data-method') as LoginMethods
76
+ if (methodId) {
77
+ this.selectLoginMethod(methodId)
78
+ }
79
+ }
80
+ })
81
+
82
+ // 使用事件委托来处理表单提交
83
+ this.shadowRoot.addEventListener('submit', (e) => {
84
+ const target = e.target as HTMLElement
85
+ if (target && target.id === 'loginForm') {
86
+ e.preventDefault()
87
+ this.handleLogin()
88
+ }
89
+ })
90
+ }
91
+
92
+ private handleLogin() {
93
+ const formData = this.getFormData()
94
+ // console.log('登录方式:', this.selectedMethod)
95
+ // console.log('登录数据:', formData)
96
+ loginHandle({
97
+ loginMethod: this.selectedMethod,
98
+ data: formData,
99
+ el: this
100
+ })
101
+ // 这里可以触发自定义事件,通知父组件
102
+ this.dispatchEvent(new CustomEvent('login', {
103
+ detail: {
104
+ method: this.selectedMethod,
105
+ data: formData
106
+ },
107
+ bubbles: true
108
+ }))
109
+ }
110
+
111
+ private getFormData(): any {
112
+ if (!this.shadowRoot) return {}
113
+
114
+ switch (this.selectedMethod) {
115
+ case 'password':
116
+ const username = this.shadowRoot.querySelector('#username') as HTMLInputElement
117
+ const password = this.shadowRoot.querySelector('#password') as HTMLInputElement
118
+ return {
119
+ username: username?.value || '',
120
+ password: password?.value || ''
121
+ }
122
+
123
+ case 'phone':
124
+ const phone = this.shadowRoot.querySelector('#phone') as HTMLInputElement
125
+ const code = this.shadowRoot.querySelector('#code') as HTMLInputElement
126
+ return {
127
+ phone: phone?.value || '',
128
+ code: code?.value || ''
129
+ }
130
+
131
+ case 'wechat':
132
+ return {
133
+ wechatCode: 'mock_wechat_code'
134
+ }
135
+ case 'wechat-mp':
136
+ return {
137
+ wechatMpCode: 'mock_wechat_mp_code'
138
+ }
139
+ default:
140
+ return {}
141
+ }
142
+ }
143
+
144
+ private renderPasswordForm() {
145
+ return html`
146
+ <form id="loginForm" class="login-form">
147
+ <div class="form-group">
148
+ <input
149
+ type="text"
150
+ id="username"
151
+ name="username"
152
+ placeholder="请输入用户名"
153
+ autocomplete="username"
154
+ required
155
+ />
156
+ </div>
157
+ <div class="form-group">
158
+ <input
159
+ type="password"
160
+ id="password"
161
+ name="password"
162
+ placeholder="请输入密码"
163
+ autocomplete="current-password"
164
+ required
165
+ />
166
+ </div>
167
+ <button type="submit" class="login-button">登录</button>
168
+ </form>
169
+ `
170
+ }
171
+
172
+ private renderPhoneForm() {
173
+ return html`
174
+ <form id="loginForm" class="login-form">
175
+ <div class="form-group">
176
+ <input
177
+ type="tel"
178
+ id="phone"
179
+ name="phone"
180
+ placeholder="请输入手机号"
181
+ pattern="[0-9]{11}"
182
+ autocomplete="tel"
183
+ required
184
+ />
185
+ </div>
186
+ <div class="form-group code-group">
187
+ <input
188
+ type="text"
189
+ id="code"
190
+ name="code"
191
+ placeholder="请输入验证码"
192
+ autocomplete="one-time-code"
193
+ required
194
+ />
195
+ <button type="button" class="code-button" @click=${this.sendVerificationCode}>获取验证码</button>
196
+ </div>
197
+ <button type="submit" class="login-button">登录</button>
198
+ </form>
199
+ `
200
+ }
201
+
202
+ private renderWechatForm() {
203
+ return html`
204
+ <div class="wechat-login">
205
+ <slot></slot>
206
+ </div>
207
+ `
208
+ }
209
+ private renderWechatMpForm() {
210
+ const that = this
211
+ setTimeout(() => {
212
+ const qrcode = that.shadowRoot!.querySelector('#qrcode');
213
+ const { clear } = useCreateLoginQRCode(qrcode as HTMLCanvasElement);
214
+ that.#clearTimer = clear;
215
+ }, 0)
216
+ return html`
217
+ <div class="wechat-login">
218
+ <div class="qr-container">
219
+ <div class="qr-placeholder">
220
+ <canvas id='qrcode' width='300' height='300'></canvas>
221
+ <p class="qr-desc">请使用微信扫描二维码登录</p>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ `
226
+ }
227
+
228
+ private sendVerificationCode() {
229
+ console.log('发送验证码')
230
+ // 这里可以实现发送验证码的逻辑
231
+ }
232
+
233
+ private refreshQR() {
234
+ console.log('刷新二维码')
235
+ // 这里可以实现刷新二维码的逻辑
236
+ }
237
+
238
+
239
+ private renderLoginForm() {
240
+ const data = this.getMethodData(this.selectedMethod);
241
+ switch (this.selectedMethod) {
242
+ case 'password':
243
+ return this.renderPasswordForm()
244
+ case 'phone':
245
+ return this.renderPhoneForm()
246
+ case 'wechat':
247
+ setWxerwma({ appid: data?.appid! || "" });
248
+ return this.renderWechatForm()
249
+ case 'wechat-mp':
250
+ return this.renderWechatMpForm()
251
+ default:
252
+ return this.renderPasswordForm()
253
+ }
254
+ }
255
+
256
+ render() {
257
+ if (!this.shadowRoot) return
258
+
259
+ const template = html`
260
+ <style>
261
+ :host {
262
+ display: block;
263
+ width: 100%;
264
+ min-width: 400px;
265
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
266
+ }
267
+
268
+ .login-sidebar {
269
+ background: white;
270
+ border-radius: 12px;
271
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
272
+ overflow: hidden;
273
+ }
274
+
275
+ .login-methods {
276
+ display: flex;
277
+ background: #f8f9fa;
278
+ border-bottom: 1px solid #e9ecef;
279
+ }
280
+
281
+ .login-method {
282
+ flex: 1;
283
+ padding: 16px 8px;
284
+ border: none;
285
+ background: none;
286
+ cursor: pointer;
287
+ display: flex;
288
+ flex-direction: column;
289
+ align-items: center;
290
+ gap: 4px;
291
+ transition: all 0.3s ease;
292
+ position: relative;
293
+ }
294
+
295
+ .login-method:hover {
296
+ background: #e9ecef;
297
+ }
298
+
299
+ .login-method.active {
300
+ background: white;
301
+ color: #007bff;
302
+ }
303
+
304
+ .login-method.active::after {
305
+ content: '';
306
+ position: absolute;
307
+ bottom: 0;
308
+ left: 0;
309
+ right: 0;
310
+ height: 2px;
311
+ background: #007bff;
312
+ }
313
+
314
+ .method-icon {
315
+ font-size: 20px;
316
+ }
317
+
318
+ .method-name {
319
+ font-size: 12px;
320
+ font-weight: 500;
321
+ }
322
+
323
+ .login-content {
324
+ padding: 32px 24px;
325
+ }
326
+ .impowerBox .qrcode {
327
+ width: 200px !important;
328
+ }
329
+ .login-form {
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 16px;
333
+ }
334
+
335
+ .form-group {
336
+ position: relative;
337
+ }
338
+
339
+ .form-group input {
340
+ width: 100%;
341
+ padding: 12px 16px;
342
+ border: 2px solid #e9ecef;
343
+ border-radius: 8px;
344
+ font-size: 14px;
345
+ transition: border-color 0.3s ease;
346
+ box-sizing: border-box;
347
+ }
348
+
349
+ .form-group input:focus {
350
+ outline: none;
351
+ border-color: #007bff;
352
+ }
353
+
354
+ .code-group {
355
+ display: flex;
356
+ gap: 12px;
357
+ }
358
+
359
+ .code-group input {
360
+ flex: 1;
361
+ }
362
+
363
+ .code-button {
364
+ padding: 0 16px;
365
+ background: #6c757d;
366
+ color: white;
367
+ border: none;
368
+ border-radius: 8px;
369
+ font-size: 14px;
370
+ cursor: pointer;
371
+ white-space: nowrap;
372
+ transition: background-color 0.3s ease;
373
+ }
374
+
375
+ .code-button:hover {
376
+ background: #5a6268;
377
+ }
378
+
379
+ .login-button {
380
+ padding: 12px;
381
+ background: #007bff;
382
+ color: white;
383
+ border: none;
384
+ border-radius: 8px;
385
+ font-size: 16px;
386
+ font-weight: 500;
387
+ cursor: pointer;
388
+ transition: background-color 0.3s ease;
389
+ }
390
+
391
+ .login-button:hover {
392
+ background: #0056b3;
393
+ }
394
+
395
+ .wechat-login {
396
+ display: flex;
397
+ flex-direction: column;
398
+ align-items: center;
399
+ gap: 20px;
400
+ }
401
+
402
+ .qr-container {
403
+ width: 400px;
404
+ height: 400px;
405
+ border: 2px dashed #e9ecef;
406
+ border-radius: 8px;
407
+ display: flex;
408
+ align-items: center;
409
+ justify-content: center;
410
+ }
411
+
412
+ .qr-placeholder {
413
+ text-align: center;
414
+ color: #6c757d;
415
+ }
416
+
417
+ .qr-icon {
418
+ font-size: 48px;
419
+ margin-bottom: 8px;
420
+ }
421
+
422
+ .qr-desc {
423
+ font-size: 12px;
424
+ margin-top: 4px;
425
+ }
426
+
427
+ .refresh-button {
428
+ padding: 8px 16px;
429
+ background: #6c757d;
430
+ color: white;
431
+ border: none;
432
+ border-radius: 6px;
433
+ font-size: 14px;
434
+ cursor: pointer;
435
+ transition: background-color 0.3s ease;
436
+ }
437
+
438
+ .refresh-button:hover {
439
+ background: #5a6268;
440
+ }
441
+ </style>
442
+
443
+ <div class="login-sidebar">
444
+ <div class="login-methods">
445
+ ${this.loginMethods.map(method => html`
446
+ <button
447
+ class="login-method ${this.selectedMethod === method.id ? 'active' : ''}"
448
+ data-method="${method.id}"
449
+ >
450
+ <span class="method-icon">${method.icon}</span>
451
+ <span class="method-name">${method.name}</span>
452
+ </button>
453
+ `)}
454
+ </div>
455
+
456
+ <div class="login-content">
457
+ ${this.renderLoginForm()}
458
+ </div>
459
+ </div>
460
+ `
461
+
462
+ render(template, this.shadowRoot)
463
+ }
464
+ }
465
+
466
+ customElements.define('kv-login', KvLogin)