@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.
- package/dist/kv-login.es.js +3288 -0
- package/dist/kv-login.umd.js +429 -0
- package/index.html +84 -0
- package/package.json +31 -0
- package/src/main.ts +2 -0
- package/src/modules/login-handle.ts +188 -0
- package/src/modules/query.ts +10 -0
- package/src/modules/wx/load-js.ts +21 -0
- package/src/modules/wx/tencent-captcha.ts +70 -0
- package/src/modules/wx/ws-login.ts +61 -0
- package/src/modules/wx-mp/qr.ts +57 -0
- package/src/pages/kv-login.ts +466 -0
- package/src/pages/kv-message.ts +351 -0
- package/vite-lib.config.ts +12 -0
- package/vite.config.ts +15 -0
|
@@ -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)
|