diff --git a/packages/kv-login/.npmrc b/packages/kv-login/.npmrc
deleted file mode 100644
index 7446745..0000000
--- a/packages/kv-login/.npmrc
+++ /dev/null
@@ -1,2 +0,0 @@
-//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
-//registry.npmjs.org/:_authToken=${NPM_TOKEN}
\ No newline at end of file
diff --git a/packages/kv-login/bun.config.ts b/packages/kv-login/bun.config.ts
deleted file mode 100644
index 4ba7316..0000000
--- a/packages/kv-login/bun.config.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { build } from 'bun';
-
-await build({
- entrypoints: ["./src/main.ts"],
- outdir: './dist',
- target: 'browser',
- format: 'esm',
- naming: {
- entry: 'app.js',
- },
- minify: false,
- sourcemap: false,
-});
-
-console.log('✅ Build complete: dist/app.js');
diff --git a/packages/kv-login/index.html b/packages/kv-login/index.html
deleted file mode 100644
index f164f39..0000000
--- a/packages/kv-login/index.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
-
- KvMessage Demo
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/kv-login/package.json b/packages/kv-login/package.json
deleted file mode 100644
index a8fb6e2..0000000
--- a/packages/kv-login/package.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "name": "@kevisual/kv-login",
- "version": "0.1.4",
- "description": "",
- "main": "src/main.ts",
- "scripts": {
- "dev": "vite",
- "build": "bun bun.config.ts",
- "postbuild": "dts -i src/main.ts -o app.d.ts",
- "build:test": "vite build",
- "prepub": "rm -rf ./dist && pnpm run build:test",
- "pub": "ev deploy ./dist -k login -v 0.1.4 -u -y yes"
- },
- "keywords": [],
- "files": [
- "types",
- "src",
- "dist"
- ],
- "author": "abearxiong (https://www.xiongxiao.me)",
- "license": "MIT",
- "packageManager": "pnpm@10.26.2",
- "publishConfig": {
- "access": "public"
- },
- "type": "module",
- "dependencies": {
- "@kevisual/context": "^0.0.4",
- "@kevisual/query-login": "^0.0.7",
- "crypto-js": "^4.2.0",
- "lit-html": "^3.3.2",
- "qrcode": "^1.5.4"
- },
- "exports": {
- ".": "./dist/app.js",
- "./types": "./types/index.d.ts"
- },
- "types": "./types/index.d.ts",
- "devDependencies": {
- "@kevisual/api": "^0.0.8",
- "@types/bun": "^1.3.5"
- }
-}
\ No newline at end of file
diff --git a/packages/kv-login/readme.md b/packages/kv-login/readme.md
deleted file mode 100644
index 699f2e4..0000000
--- a/packages/kv-login/readme.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# 可视化登录组件
-
-## 主题色
-
-黑白
-
-
-```html
-
-
-
-```
\ No newline at end of file
diff --git a/packages/kv-login/src/main.ts b/packages/kv-login/src/main.ts
deleted file mode 100644
index 43d4853..0000000
--- a/packages/kv-login/src/main.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import './pages/kv-login'
-import './pages/kv-message'
-
-export { loginEmitter } from './pages/kv-login'
-
-export { checkPluginLogin, clearCode } from './modules/login-handle';
\ No newline at end of file
diff --git a/packages/kv-login/src/modules/login-handle.ts b/packages/kv-login/src/modules/login-handle.ts
deleted file mode 100644
index 359ecab..0000000
--- a/packages/kv-login/src/modules/login-handle.ts
+++ /dev/null
@@ -1,288 +0,0 @@
-import { query } from './query.ts';
-import { createMessage } from '../pages/kv-message.ts';
-import { WX_MP_APP_ID } from '../pages/kv-login.ts';
-import { emit } from './mitt.ts';
-import { Query } from '@kevisual/query';
-import { QueryLoginBrowser } from '@kevisual/api/login'
-export const message = createMessage();
-type LoginOpts = {
- loginMethod: 'password' | 'web' | 'phone' | 'wechat' | 'wechat-mp' | 'wechat-mp-ticket',
- data: any,
- el: HTMLElement
-}
-/**
- * 登录成功后重定向到首页
- */
-export const redirectHome = () => {
- const href = window.location.href;
- const url = new URL(href);
- const redirect = url.searchParams.get('redirect');
- if (redirect) {
- const href = decodeURIComponent(redirect);
- window.open(href, '_self');
- }
- // 从url上清除 code 参数, 清除 state 参数
- emit({ type: 'login-success', data: {} });
- setTimeout(() => {
- clearCode();
- }, 1500);
-}
-export const loginHandle = async (opts: LoginOpts) => {
- const { loginMethod, data, el } = opts
- switch (loginMethod) {
- case 'password':
- await loginByPassword(data)
- break
- case 'phone':
- await loginByPhone(data)
- break
- case 'wechat-mp':
- await loginByWeChatMp(data)
- break
- case 'wechat':
- await loginByWeChat(data)
- break
- case 'web':
- await loginByWeb(data)
- break
- default:
- console.warn('未知的登录方式:', loginMethod)
- }
-}
-const loginByWeb = async (data: {}) => {
- const url = new URL("https://kevisual.cn/api/router");
- const query = new Query({ url: "https://kevisual.cn/api/router" })
- const login = new QueryLoginBrowser({ query })
- // @ts-ignore
- const res = login.loginWithWeb(url.origin, {})
- console.log('打开网页登录:', res)
- window.open(res.url, '_blank');
- const status = await login.pollLoginStatus(res);
- if (status) {
- redirectHome()
- } else {
- message.error('网页登录失败,请重试')
- }
-}
-/**
- * 使用用户名和密码登录
- * @param data
- * @returns
- */
-const loginByPassword = async (data: { username: string, password: string }) => {
- console.log('使用用户名密码登录:', data)
- let needLogin = true; // 这里可以根据实际情况决定是否需要登录, 只能判断密码登录和手机号登录
-
- const isLogin = await query.checkLocalToken()
- if (isLogin) {
- const loginUser = await query.checkLocalUser()
- if (loginUser?.username === data?.username) {
- const res = await query.getMe()
- if (res.code === 200) {
- needLogin = false
- console.log('已登录,跳过登录步骤')
- message.success('已登录')
- }
- }
- }
- if (!needLogin) {
- redirectHome()
- return;
- }
- const res = await query.login({
- username: data.username,
- password: data.password
- })
- if (res.code === 200) {
- console.log('登录成功')
- message.success('登录成功')
- redirectHome()
- } else {
- message.error(`登录失败: ${res.message}`)
- }
-}
-
-const loginByPhone = async (data: { phone: string, code: string }) => {
- console.log('使用手机号登录:', data)
-}
-
-const loginByWeChat = async (data: { wechatCode: string }) => {
- console.log('使用微信登录:', data)
-}
-const loginByWeChatMp = async (data: { wechatMpCode: string }) => {
- console.log('使用微信公众号登录:', data)
-}
-
-export const clearCode = () => {
- const url = new URL(window.location.href);
- // 清理 URL 中的 code 参数
- url.searchParams.delete('code');
- url.searchParams.delete('state');
- url.searchParams.delete('user-check');
- url.searchParams.delete('redirect');
- window.history.replaceState({}, document.title, url.toString());
-}
-export const checkWechat = async () => {
- const url = new URL(window.location.href);
- const code = url.searchParams.get('code');
- const state = url.searchParams.get('state');
- if (state?.includes?.('-')) {
- // 公众号登录流程,不在这里处理
- return;
- }
- if (!code) {
- return;
- }
- const res = await query.loginByWechat({ code });
- if (res.code === 200) {
- message.success('登录成功');
- redirectHome();
- } else {
- message.error(res.message || '登录失败');
- clearCode();
- }
-};
-
-export const checkMpWechat = async () => {
- const url = new URL(window.location.href);
- const originState = url.searchParams.get('state');
- const [mpLogin, state] = originState ? originState.split('-') : [null, null];
- console.log('检查微信公众号登录流程:', mpLogin, state, originState);
- if (mpLogin === '1') {
- // 手机端扫描的时候访问的链接,跳转到微信公众号授权页面
- checkMpWechatInWx()
- } else if (mpLogin === '2') {
- const code = url.searchParams.get('code');
- // 推送登录成功状态到扫码端
- const res2 = await query.post({
- path: 'wx',
- key: 'mplogin',
- state,
- code
- })
- if (res2.code === 200) {
- message.success('登录成功');
- } else {
- message.error(res2.message || '登录失败');
- }
- closePage();
- }
-}
-export const checkPluginLogin = async () => {
- const userCheck = 'user-check';
- const url = new URL(location.href);
- const redirect = url.searchParams.get('redirect');
- const redirectUrl = redirect ? decodeURIComponent(redirect) : '';
- const checkKey = url.searchParams.get(userCheck);
- if (redirect && checkKey) {
- // 通过refresh_token 刷新token
- const me = await query.getMe();
- if (me.code === 200) {
- message.success('登录插件中...');
- const token = await query.cacheStore.getAccessToken();
- const newRedirectUrl = new URL(redirectUrl);
- newRedirectUrl.searchParams.set('token', token + '');
- setTimeout(() => {
- window.open(newRedirectUrl.toString(), '_blank');
- }, 2000);
- return;
- }
- // 刷新token失败,登陆页自己跳转
- }
- console.log('checkKey', checkKey, redirectUrl);
-}
-const isWechat = () => {
- const ua = navigator.userAgent.toLowerCase();
- return /micromessenger/i.test(ua);
-};
-
-const closePage = (time = 2000) => {
- if (!isWechat()) {
- setTimeout(() => {
- window.close();
- }, time);
- return;
- }
- // @ts-ignore
- if (window.WeixinJSBridge) {
- setTimeout(() => {
- // @ts-ignore
- window.WeixinJSBridge.call('closeWindow');
- }, time);
- } else {
- setTimeout(() => {
- window.close();
- }, time);
- }
-};
-const checkMpWechatInWx = async () => {
- const wxAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect`
- const appid = WX_MP_APP_ID;
- const url = new URL(window.location.href);
- const originState = url.searchParams.get('state');
- let [mpLogin, state] = originState ? originState.split('-') : [null, null];
-
- const redirectURL = new URL(url.pathname, url.origin);
- state = '2-' + state; // 标记为第二步登录
- const redirect_uri = encodeURIComponent(redirectURL.toString())
- document.body.innerHTML = `正在准备跳转到微信公众号授权页面...
`;
- const scope = `snsapi_userinfo`
- if (!state) {
- alert('Invalid state. Please try again later.');
- closePage();
- return;
- }
- const link = wxAuthUrl.replace('APPID', appid).replace('REDIRECT_URI', redirect_uri).replace('SCOPE', scope).replace('STATE', state);
- setTimeout(() => {
- window.location.href = link;
- }, 100);
-}
-
-setTimeout(() => {
- checkMpWechat();
-}, 100);
-
-export const getQrCode = async () => {
- const res = await query.post({
- path: 'wx',
- key: 'get-qrcode-ticket'
- })
- if (res.code !== 200) {
- message.error('获取二维码失败');
- return null;
- }
- return res?.data as { ticket: string, url: string }
-}
-
-export const checkMpQrCodeLogin = (ticket: string) => {
- let run = true;
- const fetchLoginStatus = async () => {
- const res = await query.post({
- path: 'wx',
- key: 'check-qrcode-login',
- payload: { ticket }
- })
- if (res.code === 200) {
- message.success('登录成功');
- clearTimeout(timer);
- redirectHome();
- } else {
- // message.error(res.message || '登录失败');
- if (res.code === 401) {
- console.log('等待扫码登录...');
- } else {
- console.log('扫码登录状态:', res);
- }
- if (run) {
- setTimeout(fetchLoginStatus, 2000);
- }
- }
- }
- const timer = setTimeout(fetchLoginStatus, 2000);
- const close = () => {
- console.log('停止检测扫码登录状态');
- clearTimeout(timer);
- run = false
- }
- return close;
-}
diff --git a/packages/kv-login/src/modules/mitt.ts b/packages/kv-login/src/modules/mitt.ts
deleted file mode 100644
index b79a923..0000000
--- a/packages/kv-login/src/modules/mitt.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-export interface EventData {
- type: string;
- data: T;
-}
-
-export type EventHandler = (event: EventData) => void;
-
-export class EventEmitter {
- private events: Map> = new Map();
-
- /**
- * 监听事件
- * @param type 事件类型
- * @param handler 事件处理函数
- */
- on(type: string, handler: EventHandler): void {
- if (!this.events.has(type)) {
- this.events.set(type, new Set());
- }
- this.events.get(type)!.add(handler);
- }
-
- /**
- * 移除事件监听器
- * @param type 事件类型
- * @param handler 事件处理函数 (可选,如果不提供则移除该类型的所有监听器)
- */
- off(type: string, handler?: EventHandler): void {
- if (!this.events.has(type)) {
- return;
- }
-
- if (handler) {
- this.events.get(type)!.delete(handler);
- // 如果该类型没有监听器了,删除该类型
- if (this.events.get(type)!.size === 0) {
- this.events.delete(type);
- }
- } else {
- // 移除该类型的所有监听器
- this.events.delete(type);
- }
- }
-
- /**
- * 触发事件
- * @param event 事件对象,包含 type 和 data
- */
- emit(event: EventData): void {
- const { type } = event;
-
- if (!this.events.has(type)) {
- return;
- }
-
- const handlers = this.events.get(type)!;
- handlers.forEach(handler => {
- try {
- handler(event);
- } catch (error) {
- console.error(`Error in event handler for type "${type}":`, error);
- }
- });
- }
-
- /**
- * 触发事件(简化版本,直接传递type和data)
- * @param type 事件类型
- * @param data 事件数据
- */
- emitSimple(type: string, data: T): void {
- this.emit({ type, data });
- }
-
- /**
- * 清空所有事件监听器
- */
- clear(): void {
- this.events.clear();
- }
-
- /**
- * 获取指定类型的监听器数量
- * @param type 事件类型
- * @returns 监听器数量
- */
- listenerCount(type: string): number {
- return this.events.get(type)?.size || 0;
- }
-
- /**
- * 获取所有事件类型
- * @returns 事件类型数组
- */
- eventNames(): string[] {
- return Array.from(this.events.keys());
- }
-
- /**
- * 检查是否有指定类型的监听器
- * @param type 事件类型
- * @returns 是否有监听器
- */
- hasListeners(type: string): boolean {
- return this.events.has(type) && this.events.get(type)!.size > 0;
- }
-
- /**
- * 只监听一次事件
- * @param type 事件类型
- * @param handler 事件处理函数
- */
- once(type: string, handler: EventHandler): void {
- const onceHandler: EventHandler = (event) => {
- handler(event);
- this.off(type, onceHandler);
- };
- this.on(type, onceHandler);
- }
-}
-
-// 创建默认的事件发射器实例
-export const eventEmitter = new EventEmitter();
-
-// 导出便捷方法
-export const on = (type: string, handler: EventHandler) => eventEmitter.on(type, handler);
-export const off = (type: string, handler?: EventHandler) => eventEmitter.off(type, handler);
-export const emit = (event: EventData) => eventEmitter.emit(event);
-export const emitSimple = (type: string, data: T) => eventEmitter.emitSimple(type, data);
-export const clear = () => eventEmitter.clear();
-export const once = (type: string, handler: EventHandler) => eventEmitter.once(type, handler);
-
-// 默认导出
-export default eventEmitter;
\ No newline at end of file
diff --git a/packages/kv-login/src/modules/query.ts b/packages/kv-login/src/modules/query.ts
deleted file mode 100644
index 43b43a4..0000000
--- a/packages/kv-login/src/modules/query.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Query } from '@kevisual/query'
-import { QueryLoginBrowser } from '@kevisual/query-login';
-
-
-export const queryBase = new Query()
-
-export const query = new QueryLoginBrowser({
- query: queryBase,
-})
-
diff --git a/packages/kv-login/src/modules/wx-mp/qr.ts b/packages/kv-login/src/modules/wx-mp/qr.ts
deleted file mode 100644
index bbdb2b4..0000000
--- a/packages/kv-login/src/modules/wx-mp/qr.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import QRCode, { QRCodeToDataURLOptions } from 'qrcode';
-import { redirectHome } from '../login-handle.ts';
-import { query } from '../query.ts';
-export const useCreateLoginQRCode = (el?: HTMLCanvasElement) => {
- var opts: QRCodeToDataURLOptions = {
- errorCorrectionLevel: 'H',
- type: 'image/jpeg',
- margin: 1,
- width: 300,
- };
- let timer: any = null;
- const createQrcode = async (state: string) => {
- const url = new URL(window.location.href);
- const loginUrl = new URL(url.pathname, url.origin);
- loginUrl.searchParams.set('state', '1-' + state);
- console.log('生成登录二维码链接:', loginUrl.toString());
- var img = el || document.getElementById('qrcode')! as HTMLCanvasElement;
- const res = await QRCode.toDataURL(img!, loginUrl.toString(), opts);
- };
- const checkLogin = async (state: string) => {
- const res = await fetch(`/api/router?path=wx&key=checkLogin&state=${state}`).then((res) => res.json());
- if (res.code === 200) {
- console.log(res);
- const token = res.data;
- if (token) {
- localStorage.setItem('token', token.accessToken);
- await query.setLoginToken(token);
- }
- clear();
- setTimeout(() => {
- redirectHome();
- }, 1000);
- } else {
- timer = setTimeout(() => {
- checkLogin(state);
- console.log('继续检测登录状态');
- }, 2000);
- }
- };
- // 随机生成一个state
- const state = Math.random().toString(36).substring(2, 15);
- createQrcode(state);
- checkLogin(state);
- const timer2 = setInterval(() => {
- const state = Math.random().toString(36).substring(2, 15);
- clearTimeout(timer); // 清除定时器
- createQrcode(state); // 90秒后更新二维码
- checkLogin(state);
- console.log('更新二维码');
- }, 90000);
- const clear = () => {
- clearTimeout(timer);
- clearInterval(timer2);
- console.log('停止检测登录状态');
- }
- return { createQrcode, clear };
-};
\ No newline at end of file
diff --git a/packages/kv-login/src/modules/wx/load-js.ts b/packages/kv-login/src/modules/wx/load-js.ts
deleted file mode 100644
index a02c12c..0000000
--- a/packages/kv-login/src/modules/wx/load-js.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-//
-
-export const dynimicLoadTcapTcha = async (): Promise => {
- return new Promise((resolve, reject) => {
- const script = document.createElement('script')
- script.type = 'text/javascript'
- script.id = 'tencent-captcha'
- if (document.getElementById('tencent-captcha')) {
- resolve(true)
- return
- }
- script.src = 'https://turing.captcha.qcloud.com/TCaptcha.js'
- script.onload = () => {
- resolve(true)
- }
- script.onerror = (error) => {
- reject(error)
- }
- document.body.appendChild(script)
- })
-}
diff --git a/packages/kv-login/src/modules/wx/tencent-captcha.ts b/packages/kv-login/src/modules/wx/tencent-captcha.ts
deleted file mode 100644
index a1169b2..0000000
--- a/packages/kv-login/src/modules/wx/tencent-captcha.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-// 定义回调函数
-export function callback(res: any) {
- // 第一个参数传入回调结果,结果如下:
- // ret Int 验证结果,0:验证成功。2:用户主动关闭验证码。
- // ticket String 验证成功的票据,当且仅当 ret = 0 时 ticket 有值。
- // CaptchaAppId String 验证码应用ID。
- // bizState Any 自定义透传参数。
- // randstr String 本次验证的随机串,后续票据校验时需传递该参数。
- console.log('callback:', res);
- // res(用户主动关闭验证码)= {ret: 2, ticket: null}
- // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"}
- // res(请求验证码发生错误,验证码自动返回terror_前缀的容灾票据) = {ret: 0, ticket: "String", randstr: "String", errorCode: Number, errorMessage: "String"}
- // 此处代码仅为验证结果的展示示例,真实业务接入,建议基于ticket和errorCode情况做不同的业务处理
- if (res.ret === 0) {
- // 复制结果至剪切板
- var str = '【randstr】->【' + res.randstr + '】 【ticket】->【' + res.ticket + '】';
- var ipt = document.createElement('input');
- ipt.value = str;
- document.body.appendChild(ipt);
- ipt.select();
- document.body.removeChild(ipt);
- alert('1. 返回结果(randstr、ticket)已复制到剪切板,ctrl+v 查看。 2. 打开浏览器控制台,查看完整返回结果。');
- }
-}
-export type TencentCaptcha = {
- actionDuration?: number;
- appid?: string;
- bizState?: any;
- randstr?: string;
- ret: number;
- sid?: string;
- ticket?: string;
- errorCode?: number;
- errorMessage?: string;
- verifyDuration?: number;
-};
-// 定义验证码触发事件
-export const checkCaptcha = (captchaAppId: string): Promise => {
- return new Promise((resolve, reject) => {
- const callback = (res: TencentCaptcha) => {
- console.log('callback:', res);
- if (res.ret === 0) {
- resolve(res);
- } else {
- reject(res);
- }
- };
- const appid = captchaAppId;
- try {
- // 生成一个验证码对象
- // CaptchaAppId:登录验证码控制台,从【验证管理】页面进行查看。如果未创建过验证,请先新建验证。注意:不可使用客户端类型为小程序的CaptchaAppId,会导致数据统计错误。
- //callback:定义的回调函数
- // @ts-ignore
- var captcha = new TencentCaptcha(appid, callback, {});
- // 调用方法,显示验证码
- captcha.show();
- } catch (error) {
- // 加载异常,调用验证码js加载错误处理函数
- var ticket = 'terror_1001_' + appid + '_' + Math.floor(new Date().getTime() / 1000);
- // 生成容灾票据或自行做其它处理
- callback({
- ret: 0,
- randstr: '@' + Math.random().toString(36).substring(2),
- ticket: ticket,
- errorCode: 1001,
- errorMessage: 'jsload_error',
- });
- }
- });
-};
diff --git a/packages/kv-login/src/modules/wx/ws-login.ts b/packages/kv-login/src/modules/wx/ws-login.ts
deleted file mode 100644
index bdfbeba..0000000
--- a/packages/kv-login/src/modules/wx/ws-login.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-type WxLoginConfig = {
- redirect_uri?: string;
- appid?: string;
- scope?: string;
- state?: string;
- style?: string;
-};
-export const createLogin = async (config?: WxLoginConfig) => {
- let redirect_uri = config?.redirect_uri;
- const { appid } = config || {};
- if (!redirect_uri) {
- redirect_uri = window.location.href;
- }
- const url = new URL(redirect_uri); // remove code and state params
- url.searchParams.delete('code');
- url.searchParams.delete('state');
- redirect_uri = url.toString();
-
- console.log('redirect_uri', redirect_uri);
- if (!appid) {
- console.error('appid is not cant be empty');
- return;
- }
- // @ts-ignore
- const obj = new WxLogin({
- self_redirect: false,
- id: 'weixinLogin', // 需要显示的容器id
- appid: appid, // 微信开放平台appid wx*******
- scope: 'snsapi_login', // 网页默认即可 snsapi_userinfo
- redirect_uri: encodeURIComponent(redirect_uri), // 授权成功后回调的url
- state: Math.ceil(Math.random() * 1000), // 可设置为简单的随机数加session用来校验
- stylelite: true, // 是否使用简洁模式
- // https://juejin.cn/post/6982473580063752223
- href: "data:text/css;base64,LmltcG93ZXJCb3ggLnFyY29kZSB7d2lkdGg6IDIwMHB4O30NCi5pbXBvd2VyQm94IC50aXRsZSB7ZGlzcGxheTogbm9uZTt9DQouaW1wb3dlckJveCAuaW5mbyB7d2lkdGg6IDIwMHB4O30NCi5zdGF0dXNfaWNvbiB7ZGlzcGxheTogbm9uZX0NCi5pbXBvd2VyQm94IC5zdGF0dXMge3RleHQtYWxpZ246IGNlbnRlcjt9"
- });
- const login = document.querySelector('#weixinLogin')
- if (login) {
- // login 下的 iframe 样式调整
- const iframe = login.querySelector('iframe');
- if (iframe) {
- // iframe.style.width = '200px';
- iframe.style.height = '300px';
- }
- }
- return obj;
-};
-export const wxId = 'weixinLogin';
-export function setWxerwma(config?: WxLoginConfig) {
- const s = document.createElement('script');
- s.type = 'text/javascript';
- s.src = '//res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js';
- s.id = 'weixinLogin-js';
- if (document.getElementById('weixinLogin-js')) {
- createLogin(config);
- return;
- }
- const wxElement = document.body.appendChild(s);
- wxElement.onload = function () {
- createLogin(config);
- };
-}
diff --git a/packages/kv-login/src/pages/kv-login.ts b/packages/kv-login/src/pages/kv-login.ts
deleted file mode 100644
index 2632e14..0000000
--- a/packages/kv-login/src/pages/kv-login.ts
+++ /dev/null
@@ -1,590 +0,0 @@
-import { render, html } from 'lit-html'
-import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'
-import { loginHandle, checkWechat, getQrCode, checkMpQrCodeLogin, redirectHome } from '../modules/login-handle.ts'
-import { setWxerwma } from '../modules/wx/ws-login.ts';
-import { useCreateLoginQRCode } from '../modules/wx-mp/qr.ts';
-import { eventEmitter } from '../modules/mitt.ts';
-import { useContextKey } from '@kevisual/context'
-
-export const loginEmitter = useContextKey('login-emitter', eventEmitter);
-export const WX_MP_APP_ID = "wxff97d569b1db16b6";
-interface LoginMethod {
- id: LoginMethods
- name: string
- icon: any
- appid?: string
-}
-const wxmpSvg = ``
-const wxOpenSvg = ``
-const phone = ``
-const pwd = ``
-const web = ``
-const icons: any = {
- pwd,
- web,
- phone,
- wxmpSvg,
- wxOpenSvg
-}
-const DefaultLoginMethods: LoginMethod[] = [
- { id: 'password', name: '密码登录', icon: 'pwd' },
- { id: 'web', name: '网页登录', icon: 'web' },
- { id: 'wechat', name: '微信登录', icon: 'wxmpSvg', appid: "wx9378885c8390e09b" },
- { id: 'wechat-mp', name: '微信公众号', icon: 'wxOpenSvg', appid: WX_MP_APP_ID },
- { id: 'wechat-mp-ticket', name: '微信公众号', icon: 'wxOpenSvg' },
- { id: 'phone', name: '手机号登录', icon: 'phone' }
-]
-const LoginMethods = ['password', 'web', 'phone', 'wechat', 'wechat-mp', 'wechat-mp-ticket'] as const;
-type LoginMethods = 'password' | 'web' | 'phone' | 'wechat' | 'wechat-mp' | 'wechat-mp-ticket';
-
-const getLoginMethodByDomain = (): LoginMethod[] => {
- let domain = window.location.host
- let methods: LoginMethods[] = []
- const has51 = domain.includes('localhost') && (domain.endsWith('51515') || domain.endsWith('51015'));
- if (has51) {
- domain = 'localhost'
- }
- switch (domain) {
- case 'kevisual.xiongxiao.me':
- methods = ['password', 'wechat-mp']
- break;
- case 'kevisual.cn':
- methods = ['password', 'wechat-mp-ticket', 'wechat',]
- break;
- case 'localhost':
- methods = ['password', 'web']
- break
- default:
- methods = ['password', 'web', 'phone', 'wechat', 'wechat-mp', 'wechat-mp-ticket']
- break;
- }
- return DefaultLoginMethods.filter(method => methods.includes(method.id))
-}
-const getLoginMethod = (methods: LoginMethods[]): LoginMethod[] => {
- return DefaultLoginMethods.filter(method => methods.includes(method.id))
-}
-class KvLogin extends HTMLElement {
- private selectedMethod: LoginMethods = 'password'
-
- private loginMethods: LoginMethod[] = getLoginMethodByDomain();
- setLoginMethods(methods: LoginMethod[]) {
- this.loginMethods = methods
- this.render()
- }
- constructor() {
- super()
- }
-
- connectedCallback() {
- this.attachShadow({ mode: 'open' })
- this.render()
- this.bindEvents()
- checkWechat()
- const method = this.getAttribute('method');
- if (method) {
- const methods = method ? method.split(',') as LoginMethods[] : [];
- if (methods.length > 0) {
- const loginMethods = methods.filter(m => LoginMethods.includes(m));
- if (loginMethods.length > 0) {
- this.loginMethods = getLoginMethod(loginMethods)
- this.selectedMethod = loginMethods[0]
- return;
- }
- }
- this.loginMethods = getLoginMethodByDomain();
- this.selectedMethod = this.loginMethods[0].id;
- }
- }
- #clearTimer: any = null;
- private selectLoginMethod(methodId: LoginMethods) {
- this.selectedMethod = methodId
- this.render()
- if (this.#clearTimer) {
- this.#clearTimer();
- this.#clearTimer = null;
- }
- }
- private getMethodData(methodId: LoginMethods): LoginMethod | undefined {
- return this.loginMethods.find(method => method.id === methodId);
- }
- private bindEvents() {
- if (!this.shadowRoot) return
- // 使用事件委托来处理登录方式切换
- this.shadowRoot.addEventListener('click', (e) => {
- const target = e.target as HTMLElement
- const methodButton = target.closest('.login-method')
- if (methodButton) {
- const methodId = methodButton.getAttribute('data-method') as LoginMethods
- if (methodId) {
- this.selectLoginMethod(methodId)
- }
- }
- })
-
- // 使用事件委托来处理表单提交
- this.shadowRoot.addEventListener('submit', (e) => {
- const target = e.target as HTMLElement
- if (target && target.id === 'loginForm') {
- e.preventDefault()
- this.handleLogin()
- }
- })
- loginEmitter.on('login-success', () => {
- console.log('收到登录成功事件,处理后续逻辑')
- });
- }
-
- private handleLogin() {
- const formData = this.getFormData()
- // console.log('登录方式:', this.selectedMethod)
- // console.log('登录数据:', formData)
- loginHandle({
- loginMethod: this.selectedMethod,
- data: formData,
- el: this
- })
- // 这里可以触发自定义事件,通知父组件
- this.dispatchEvent(new CustomEvent('login', {
- detail: {
- method: this.selectedMethod,
- data: formData
- },
- bubbles: true
- }))
- }
-
- private getFormData(): any {
- if (!this.shadowRoot) return {}
-
- switch (this.selectedMethod) {
- case 'password':
- const username = this.shadowRoot.querySelector('#username') as HTMLInputElement
- const password = this.shadowRoot.querySelector('#password') as HTMLInputElement
- return {
- username: username?.value || '',
- password: password?.value || ''
- }
- case 'web':
- return {}
- case 'phone':
- const phone = this.shadowRoot.querySelector('#phone') as HTMLInputElement
- const code = this.shadowRoot.querySelector('#code') as HTMLInputElement
- return {
- phone: phone?.value || '',
- code: code?.value || ''
- }
-
- case 'wechat':
- return {
- wechatCode: 'mock_wechat_code'
- }
- case 'wechat-mp':
- return {
- wechatMpCode: 'mock_wechat_mp_code'
- }
- default:
- return {}
- }
- }
-
- private renderPasswordForm() {
- return html`
-
- `
- }
- private renderWebForm() {
- return html`
-
-
-
-
- `
- }
- private renderPhoneForm() {
- return html`
-
- `
- }
-
- private renderWechatForm() {
- return html`
-
-
-
- `
- }
- private renderWechatMpForm() {
- const that = this
- setTimeout(() => {
- const qrcode = that.shadowRoot!.querySelector('#qrcode');
- const { clear } = useCreateLoginQRCode(qrcode as HTMLCanvasElement);
- that.#clearTimer = clear;
- }, 0)
- return html`
-
- `
- }
- private renderWechatMpTicketForm() {
- const that = this;
- setTimeout(async () => {
- const data = await getQrCode();
- if (!data) return;
- const imgEl = that.shadowRoot!.querySelector('.qrcode') as HTMLImageElement;
- if (data.url) {
- imgEl.src = data.url;
- // TODO: 轮询检测登录状态
- const clear = checkMpQrCodeLogin(data.ticket)
- // 当切换登录方式时,停止轮询
- that.#clearTimer = clear
- }
- }, 0)
- return html`
-
-
-
-
![]()
-
请使用微信扫描二维码登录
-
-
-
- `
- }
-
- private sendVerificationCode() {
- console.log('发送验证码')
- // 这里可以实现发送验证码的逻辑
- }
-
- private refreshQR() {
- console.log('刷新二维码')
- // 这里可以实现刷新二维码的逻辑
- }
-
-
- private renderLoginForm() {
- const data = this.getMethodData(this.selectedMethod);
- switch (this.selectedMethod) {
- case 'password':
- return this.renderPasswordForm()
- case 'web':
- return this.renderWebForm()
- case 'phone':
- return this.renderPhoneForm()
- case 'wechat':
- setWxerwma({ appid: data?.appid! || "" });
- return this.renderWechatForm()
- case 'wechat-mp':
- return this.renderWechatMpForm()
- case 'wechat-mp-ticket':
- return this.renderWechatMpTicketForm()
- default:
- return this.renderPasswordForm()
- }
- }
-
- render() {
- if (!this.shadowRoot) return
-
- const renderIcon = (icon: any) => {
- // 如果是emoji字符,直接返回
- if (typeof icon === 'string' && !icons[icon]) {
- return html`${icon}`
- }
- // 如果是SVG引用,从icons对象获取
- if (typeof icon === 'string' && icons[icon]) {
- return html`${unsafeHTML(icons[icon])}`
- }
- // 如果直接是SVG内容
- if (typeof icon === 'string' && (icon.includes('