From 142e67145e5d787101a793ec1d2bd99faeaeb173 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Mon, 13 May 2024 12:50:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=20=E4=BC=98=E5=8C=96=E4=BA=BA?= =?UTF-8?q?=E6=9C=BA=E8=AF=86=E5=88=AB=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + .../auth-captcha/auth-captcha.controller.ts | 37 ++++++++++++++++--- .../auth-captcha/auth-captcha.scss | 21 ++++++++++- .../auth-captcha/auth-captcha.state.ts | 8 ++++ .../auth-captcha/auth-captcha.tsx | 11 ++++++ src/view-engine/login-view.engine.ts | 29 ++++++++++++++- 6 files changed, 99 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b544b87..c42948610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ 并且此项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/). ## [Unreleased] +### Changed +- 优化人机识别校验 ## [0.7.12] - 2024-05-12 ### Added diff --git a/src/panel-component/auth-captcha/auth-captcha.controller.ts b/src/panel-component/auth-captcha/auth-captcha.controller.ts index 27dfa0cbd..7eece6557 100644 --- a/src/panel-component/auth-captcha/auth-captcha.controller.ts +++ b/src/panel-component/auth-captcha/auth-captcha.controller.ts @@ -51,9 +51,30 @@ export class AuthCaptchaController extends PanelItemController { */ protected async onInit(): Promise { super.onInit(); + (this.panel.view as IData).evt.on('onAfterLogin', (event: IData) => { + if (!event.ok) { + this.loadCaptcha(); + } + }); await this.loadCaptcha(); } + /** + * 值校验 + * + * @return {*} {Promise} + * @memberof AuthCaptchaController + */ + async validate(): Promise { + // 人机识别目前仅支持空值校验 + if (this.state.code) { + this.state.error = undefined; + return true; + } + this.state.error = '请输入验证码'; + return false; + } + /** * 加载验证码 * @@ -71,11 +92,17 @@ export class AuthCaptchaController extends PanelItemController { }, data: {}, }; - const res = await axios(requestConfig); - if (res.status === 200 && res.data) { - this.state.state = res.data.state; - this.state.image = res.data.image; + try { + const res = await axios(requestConfig); + if (res.status === 200 && res.data) { + this.state.state = res.data.state; + this.state.image = res.data.image; + } + } catch (error) { + this.state.state = ''; + this.state.image = ''; + } finally { + this.state.loading = false; } - this.state.loading = false; } } diff --git a/src/panel-component/auth-captcha/auth-captcha.scss b/src/panel-component/auth-captcha/auth-captcha.scss index 38fe39fe7..4c3e3a4a4 100644 --- a/src/panel-component/auth-captcha/auth-captcha.scss +++ b/src/panel-component/auth-captcha/auth-captcha.scss @@ -1,5 +1,6 @@ @include b(auth-captcha) { display: flex; + position: relative; @include e('captcha') { width: calc(100% - 120px); } @@ -9,11 +10,27 @@ height: 100%; cursor: pointer; @include m('hint') { + width: 100%; + height: 100%; display: flex; align-items: center; justify-content: center; - width: 100%; - height: 100%; + color: var(--ibiz-color-text-3); + background-color: var(--ibiz-color-fill-0); + } + } + @include e('error') { + left: 0; + top: 100%; + line-height: 1; + font-size: 12px; + padding-top: 2px; + position: absolute; + color: var(--ibiz-color-danger); + } + @include when(error) { + .el-input__wrapper { + box-shadow: 0 0 0 1px var(--ibiz-color-danger) inset; } } } diff --git a/src/panel-component/auth-captcha/auth-captcha.state.ts b/src/panel-component/auth-captcha/auth-captcha.state.ts index bbf529369..9139babc9 100644 --- a/src/panel-component/auth-captcha/auth-captcha.state.ts +++ b/src/panel-component/auth-captcha/auth-captcha.state.ts @@ -39,4 +39,12 @@ export class AuthCaptchaState extends PanelItemState { * @memberof AuthCaptchaState */ loading: boolean = false; + + /** + * 错误信息 + * + * @type {string} + * @memberof AuthCaptchaState + */ + error?: string; } diff --git a/src/panel-component/auth-captcha/auth-captcha.tsx b/src/panel-component/auth-captcha/auth-captcha.tsx index d75a92297..7b058fbc4 100644 --- a/src/panel-component/auth-captcha/auth-captcha.tsx +++ b/src/panel-component/auth-captcha/auth-captcha.tsx @@ -29,6 +29,7 @@ export const AuthCaptcha = defineComponent({ ...result, ...props.controller.containerClass, ns.is('hidden', !props.controller.state.visible), + ns.is('error', !!c.state.error), ]; return result; }); @@ -42,11 +43,16 @@ export const AuthCaptcha = defineComponent({ } }; + const onChange = () => { + c.validate(); + }; + return { c, ns, classArr, onClick, + onChange, }; }, render() { @@ -55,6 +61,8 @@ export const AuthCaptcha = defineComponent({ + {this.c.state.error && ( +
{this.c.state.error}
+ )} ); }, diff --git a/src/view-engine/login-view.engine.ts b/src/view-engine/login-view.engine.ts index 6caaa2e2e..64586486d 100644 --- a/src/view-engine/login-view.engine.ts +++ b/src/view-engine/login-view.engine.ts @@ -1,16 +1,20 @@ import { + IViewState, SysUIActionTag, ViewController, ViewEngineBase, + ILoginViewEvent, } from '@ibiz-template/runtime'; import { RouteLocationNormalizedLoaded, useRoute } from 'vue-router'; -import { IPanelField } from '@ibiz/model-core'; +import { IPanelField, IAppView } from '@ibiz/model-core'; import { PanelFieldController } from '@ibiz-template/vue3-util'; import { notNilEmpty } from 'qx-util'; export class LoginViewEngine extends ViewEngineBase { route: RouteLocationNormalizedLoaded = useRoute(); + protected declare view: ViewController; + get AppLoginView(): ViewController { return this.view.getController('AppLoginView') as ViewController; } @@ -38,7 +42,28 @@ export class LoginViewEngine extends ViewEngineBase { return super.call(key, args); } + /** + * 校验 + * + * @return {*} {Promise} + * @memberof LoginViewEngine + */ + async validate(): Promise { + let state = true; + const viewPanel = this.AppLoginView.layoutPanel; + if (viewPanel) { + const authCaptcha = viewPanel.panelItems.auth_captcha; + if (authCaptcha) { + state = await (authCaptcha as IData).validate(); + } + } + return state; + } + async login(args: IData): Promise { + if (!(await this.validate())) { + return; + } let rememberme; const headers: IData = {}; if (this.AppLoginView.layoutPanel) { @@ -67,7 +92,7 @@ export class LoginViewEngine extends ViewEngineBase { rememberme, headers, ); - + this.view.evt.emit('onAfterLogin', { ok: bol }); if (bol === true) { window.location.hash = (this.route.query.ru as string) || '/'; window.location.reload(); -- Gitee From eeae72b26ce9cc611ba2aaa50ee2f319ec0c5390 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Mon, 13 May 2024 18:17:11 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=20=E6=9B=B4=E6=96=B0=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E7=99=BB=E5=BD=95=E8=A7=86=E5=9B=BE=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth-captcha/auth-captcha.controller.ts | 55 ++++++++++++++----- .../auth-captcha/auth-captcha.tsx | 1 + .../panel-button/panel-button.controller.ts | 15 ++++- src/view-engine/login-view.engine.ts | 40 ++++---------- 4 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/panel-component/auth-captcha/auth-captcha.controller.ts b/src/panel-component/auth-captcha/auth-captcha.controller.ts index 7eece6557..02cd95c9f 100644 --- a/src/panel-component/auth-captcha/auth-captcha.controller.ts +++ b/src/panel-component/auth-captcha/auth-captcha.controller.ts @@ -1,8 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable @typescript-eslint/no-this-alias */ +import { reactive } from 'vue'; import { IPanelRawItem } from '@ibiz/model-core'; -import { PanelItemController, PanelNotifyState } from '@ibiz-template/runtime'; +import { + AppLoginViewController, + PanelItemController, + PanelNotifyState, +} from '@ibiz-template/runtime'; import axios, { AxiosRequestConfig } from 'axios'; import { AuthCaptchaState } from './auth-captcha.state'; @@ -16,6 +21,17 @@ import { AuthCaptchaState } from './auth-captcha.state'; export class AuthCaptchaController extends PanelItemController { declare state: AuthCaptchaState; + /** + * 验证码数据 + * + * @private + * @memberof AuthCaptchaController + */ + private captcha = reactive({ + 'Captcha-State': '', + 'Captcha-Code': '', + }); + protected createState(): AuthCaptchaState { return new AuthCaptchaState(this.parent?.state); } @@ -29,17 +45,7 @@ export class AuthCaptchaController extends PanelItemController { */ async panelStateNotify(_state: PanelNotifyState): Promise { super.panelStateNotify(_state); - const that = this; - Object.defineProperty(that.panel.state.data, 'captcha', { - enumerable: true, - configurable: true, - get() { - return { - captcha_state: that.state.state, - captcha_code: that.state.code, - }; - }, - }); + this.data.captcha = this.captcha; } /** @@ -51,12 +57,18 @@ export class AuthCaptchaController extends PanelItemController { */ protected async onInit(): Promise { super.onInit(); - (this.panel.view as IData).evt.on('onAfterLogin', (event: IData) => { - if (!event.ok) { + await this.loadCaptcha(); + const view = this.panel.view as AppLoginViewController; + view.hooks.beforeLogin.tapPromise(async context => { + if (!context.parentId || context.parentId === this.dataParent.model.id!) { + context.validate = context.validate && (await this.validate()); + } + }); + view.hooks.afterLogin.tap(context => { + if (!context.ok) { this.loadCaptcha(); } }); - await this.loadCaptcha(); } /** @@ -75,6 +87,18 @@ export class AuthCaptchaController extends PanelItemController { return false; } + /** + * 值改变 + * + * @memberof AuthCaptchaController + */ + onChange(): void { + Object.assign(this.captcha, { + 'Captcha-State': this.state.state, + 'Captcha-Code': this.state.code, + }); + } + /** * 加载验证码 * @@ -103,6 +127,7 @@ export class AuthCaptchaController extends PanelItemController { this.state.image = ''; } finally { this.state.loading = false; + this.onChange(); } } } diff --git a/src/panel-component/auth-captcha/auth-captcha.tsx b/src/panel-component/auth-captcha/auth-captcha.tsx index 7b058fbc4..96a37d6e2 100644 --- a/src/panel-component/auth-captcha/auth-captcha.tsx +++ b/src/panel-component/auth-captcha/auth-captcha.tsx @@ -44,6 +44,7 @@ export const AuthCaptcha = defineComponent({ }; const onChange = () => { + c.onChange(); c.validate(); }; diff --git a/src/panel-component/panel-button/panel-button.controller.ts b/src/panel-component/panel-button/panel-button.controller.ts index ce08e385f..554fe246c 100644 --- a/src/panel-component/panel-button/panel-button.controller.ts +++ b/src/panel-component/panel-button/panel-button.controller.ts @@ -1,4 +1,5 @@ import { + AppLoginViewController, PanelController, PanelItemController, PanelNotifyState, @@ -134,13 +135,25 @@ export class PanelButtonController extends PanelItemController { } event.stopPropagation(); event.preventDefault(); + // 登录按钮在应用登录视图上需提前校验登录表单 + let view = this.panel.view; + if ( + actionType === 'UIACTION' && + uiactionId === 'login' && + view.model.viewType === 'APPLOGINVIEW' && + !(await (view as AppLoginViewController).validate( + this.dataParent.model.id!, + )) + ) { + return; + } await UIActionUtil.execAndResolved( uiactionId!, { context: this.panel.context, params: this.panel.params, data: [this.data], - view: this.panel.view, + view: view, event, noWaitRoute: true, }, diff --git a/src/view-engine/login-view.engine.ts b/src/view-engine/login-view.engine.ts index 64586486d..0c9ea7dff 100644 --- a/src/view-engine/login-view.engine.ts +++ b/src/view-engine/login-view.engine.ts @@ -1,9 +1,10 @@ import { - IViewState, SysUIActionTag, ViewController, ViewEngineBase, ILoginViewEvent, + AppLoginViewController, + IAppLoginViewState, } from '@ibiz-template/runtime'; import { RouteLocationNormalizedLoaded, useRoute } from 'vue-router'; import { IPanelField, IAppView } from '@ibiz/model-core'; @@ -13,7 +14,11 @@ import { notNilEmpty } from 'qx-util'; export class LoginViewEngine extends ViewEngineBase { route: RouteLocationNormalizedLoaded = useRoute(); - protected declare view: ViewController; + protected declare view: AppLoginViewController< + IAppView, + IAppLoginViewState, + ILoginViewEvent + >; get AppLoginView(): ViewController { return this.view.getController('AppLoginView') as ViewController; @@ -42,28 +47,7 @@ export class LoginViewEngine extends ViewEngineBase { return super.call(key, args); } - /** - * 校验 - * - * @return {*} {Promise} - * @memberof LoginViewEngine - */ - async validate(): Promise { - let state = true; - const viewPanel = this.AppLoginView.layoutPanel; - if (viewPanel) { - const authCaptcha = viewPanel.panelItems.auth_captcha; - if (authCaptcha) { - state = await (authCaptcha as IData).validate(); - } - } - return state; - } - async login(args: IData): Promise { - if (!(await this.validate())) { - return; - } let rememberme; const headers: IData = {}; if (this.AppLoginView.layoutPanel) { @@ -72,16 +56,16 @@ export class LoginViewEngine extends ViewEngineBase { if (typeof panelData.isRemember === 'boolean') { rememberme = panelData.isRemember; } - // 验证码 - if (panelData.captcha) { - Object.assign(headers, panelData.captcha); - } // 自定义请求头数据 if (panelData.srfheaders) { Object.assign(headers, panelData.srfheaders); } } const data = args.data[0] || {}; + // 验证码从登录表单中获取 + if (data.captcha) { + Object.assign(headers, data.captcha); + } let username = data.username; if (notNilEmpty(data.orgid)) { username = `${data.username}@${data.orgid}`; @@ -92,7 +76,7 @@ export class LoginViewEngine extends ViewEngineBase { rememberme, headers, ); - this.view.evt.emit('onAfterLogin', { ok: bol }); + this.view.hooks.afterLogin.call({ ok: bol }); if (bol === true) { window.location.hash = (this.route.query.ru as string) || '/'; window.location.reload(); -- Gitee From 8031227b43e084ebc48104130fd923b458e6cb72 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Mon, 13 May 2024 18:43:40 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=20=E6=9B=B4=E6=96=B0=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E8=A7=86=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../panel-button/panel-button.controller.ts | 3 ++- src/view-engine/login-view.engine.ts | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/panel-component/panel-button/panel-button.controller.ts b/src/panel-component/panel-button/panel-button.controller.ts index 554fe246c..39cc4037b 100644 --- a/src/panel-component/panel-button/panel-button.controller.ts +++ b/src/panel-component/panel-button/panel-button.controller.ts @@ -1,3 +1,4 @@ +/* eslint-disable object-shorthand */ import { AppLoginViewController, PanelController, @@ -136,7 +137,7 @@ export class PanelButtonController extends PanelItemController { event.stopPropagation(); event.preventDefault(); // 登录按钮在应用登录视图上需提前校验登录表单 - let view = this.panel.view; + const view = this.panel.view; if ( actionType === 'UIACTION' && uiactionId === 'login' && diff --git a/src/view-engine/login-view.engine.ts b/src/view-engine/login-view.engine.ts index 0c9ea7dff..310e254a6 100644 --- a/src/view-engine/login-view.engine.ts +++ b/src/view-engine/login-view.engine.ts @@ -2,9 +2,9 @@ import { SysUIActionTag, ViewController, ViewEngineBase, - ILoginViewEvent, AppLoginViewController, - IAppLoginViewState, + IViewState, + IViewEvent, } from '@ibiz-template/runtime'; import { RouteLocationNormalizedLoaded, useRoute } from 'vue-router'; import { IPanelField, IAppView } from '@ibiz/model-core'; @@ -16,8 +16,8 @@ export class LoginViewEngine extends ViewEngineBase { protected declare view: AppLoginViewController< IAppView, - IAppLoginViewState, - ILoginViewEvent + IViewState, + IViewEvent >; get AppLoginView(): ViewController { @@ -50,22 +50,22 @@ export class LoginViewEngine extends ViewEngineBase { async login(args: IData): Promise { let rememberme; const headers: IData = {}; + const data = args.data[0] || {}; if (this.AppLoginView.layoutPanel) { const panelData: IData = this.AppLoginView.layoutPanel.data; // 记住我 if (typeof panelData.isRemember === 'boolean') { rememberme = panelData.isRemember; } + // 验证码从登录表单中获取 + if (data.captcha) { + Object.assign(headers, data.captcha); + } // 自定义请求头数据 if (panelData.srfheaders) { Object.assign(headers, panelData.srfheaders); } } - const data = args.data[0] || {}; - // 验证码从登录表单中获取 - if (data.captcha) { - Object.assign(headers, data.captcha); - } let username = data.username; if (notNilEmpty(data.orgid)) { username = `${data.username}@${data.orgid}`; -- Gitee