Browse Source

feat(系统架构): 重构登录验证码功能

shizhongming 2 years ago
parent
commit
fa8bd09374
  1. 8
      src/App.vue
  2. 15
      src/api/sys/app.ts
  3. 2
      src/api/sys/model/userModel.ts
  4. 3
      src/components/Verify/index.ts
  5. 55
      src/components/Verify/src/TextCaptcha.vue
  6. 8
      src/components/registerGlobComp.ts
  7. 3
      src/locales/lang/zh-CN/component.json
  8. 18
      src/store/modules/app.ts
  9. 56
      src/views/sys/login/LoginForm.vue
  10. 20
      types/store.d.ts

8
src/App.vue

@ -18,6 +18,8 @@
import { computed } from 'vue';
import { ExceptionModal } from '@/views/sys/exception';
import { useAppStore } from '@/store/modules/app';
// support Multi-language
const { getAntdLocale } = useLocale();
@ -39,4 +41,10 @@
);
// Listening to page changes and dynamically changing site titles
useTitle();
/**
* 初始化系统参数
*/
const appStore = useAppStore();
appStore.initSystemProperties();
</script>

15
src/api/sys/app.ts

@ -0,0 +1,15 @@
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
enum Api {
getAuthProperties = 'public/auth/getAuthProperties',
}
/**
*
*/
export const getAuthPropertiesApi = () => {
return defHttp.post({
service: ApiServiceEnum.SMART_AUTH,
url: Api.getAuthProperties,
});
};

2
src/api/sys/model/userModel.ts

@ -4,7 +4,7 @@
export interface LoginParams {
username: string;
password: string;
codeKey: string;
key: string;
code: string;
}

3
src/components/Verify/index.ts

@ -1,7 +1,10 @@
import { withInstall } from '@/utils';
import basicDragVerify from './src/DragVerify.vue';
import rotateDragVerify from './src/ImgRotate.vue';
import textCaptcha from './src/TextCaptcha.vue';
export const BasicDragVerify = withInstall(basicDragVerify);
export const RotateDragVerify = withInstall(rotateDragVerify);
export * from './src/typing';
export const TextCaptcha = withInstall(textCaptcha);

55
src/components/Verify/src/TextCaptcha.vue

@ -0,0 +1,55 @@
<template>
<Tooltip>
<template #title>{{ t('component.verify.refresh') }}</template>
<img :height="height" :src="imageSrc" @click="refresh" />
</Tooltip>
</template>
<script setup lang="ts">
import { Tooltip } from 'ant-design-vue';
import { useI18n } from '@/hooks/web/useI18n';
import { propTypes } from '@/utils/propTypes';
import { computed, ref, unref } from 'vue';
const props = defineProps({
height: propTypes.string.def('px'),
api: {
type: Function as PropType<() => Promise<any>>,
required: true,
},
});
const emit = defineEmits(['after-refresh']);
const { t } = useI18n();
const captchaData = ref<Recordable>({});
const imageSrc = computed(() => {
return unref(captchaData).text?.image;
});
const refresh = async () => {
captchaData.value = await props.api();
emit('after-refresh', unref(captchaData));
};
refresh();
const createValidateParameter = (data) => {
const { key, type } = unref(captchaData);
return {
key,
type,
text: {
code: data,
},
};
};
defineExpose({
refresh,
createValidateParameter,
});
</script>
<style scoped lang="less"></style>

8
src/components/registerGlobComp.ts

@ -16,6 +16,10 @@ import {
} from 'ant-design-vue';
import VXETable from 'vxe-table';
import ExcelJS from 'exceljs';
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx';
import { VXETablePluginAntd } from '@/components/SmartTable/VXETablePluginAntd';
import { i18n } from '@/locales/setupI18n';
export function registerGlobComp(app: App) {
@ -34,6 +38,10 @@ export function registerGlobComp(app: App) {
return key;
},
});
VXETable.use(VXETablePluginAntd);
VXETable.use(VXETablePluginExportXLSX, {
ExcelJS,
});
app
.use(Input)
.use(Button)

3
src/locales/lang/zh-CN/component.json

@ -123,6 +123,7 @@
"time": "验证校验成功,耗时{time}秒!",
"redoTip": "点击图片可刷新",
"dragText": "请按住滑块拖动",
"successText": "验证通过"
"successText": "验证通过",
"refresh": "点击刷新验证码"
}
}

18
src/store/modules/app.ts

@ -6,7 +6,7 @@ import type {
MultiTabsSetting,
SizeConfig,
} from '#/config';
import type { BeforeMiniState, ApiAddress } from '#/store';
import type { BeforeMiniState, ApiAddress, SystemProperties } from '#/store';
import { defineStore } from 'pinia';
import { store } from '@/store';
@ -18,6 +18,8 @@ import { darkMode } from '@/settings/designSetting';
import { resetRouter } from '@/router';
import { deepMerge } from '@/utils';
import { getAuthPropertiesApi } from '@/api/sys/app';
interface AppState {
darkMode?: ThemeEnum;
// Page loading status
@ -26,6 +28,7 @@ interface AppState {
projectConfig: ProjectConfig | null;
// When the window shrinks, remember some states, and restore these states when the window is restored
beforeMiniInfo: BeforeMiniState;
systemProperties: SystemProperties;
}
let timeId: TimeoutHandle;
export const useAppStore = defineStore({
@ -35,6 +38,7 @@ export const useAppStore = defineStore({
pageLoading: false,
projectConfig: Persistent.getLocal(PROJ_CFG_KEY),
beforeMiniInfo: {},
systemProperties: {},
}),
getters: {
getPageLoading(state): boolean {
@ -70,6 +74,9 @@ export const useAppStore = defineStore({
getSizeSetting(): SizeConfig {
return this.getProjectConfig.sizeConfig;
},
getSystemProperties(state): SystemProperties {
return state.systemProperties;
},
},
actions: {
setPageLoading(loading: boolean): void {
@ -113,6 +120,15 @@ export const useAppStore = defineStore({
setApiAddress(config: ApiAddress): void {
localStorage.setItem(API_ADDRESS, JSON.stringify(config));
},
/**
*
*/
async initSystemProperties() {
const authProperties = await getAuthPropertiesApi();
this.systemProperties = {
...authProperties,
};
},
},
});

56
src/views/sys/login/LoginForm.vue

@ -25,7 +25,8 @@
/>
</FormItem>
<ARow :gutter="16">
<!-- 文本验证码 -->
<ARow v-if="computedUseCaptcha == 'TEXT'" :gutter="16">
<ACol :span="16">
<FormItem name="captcha">
<Input
@ -37,10 +38,12 @@
</FormItem>
</ACol>
<ACol :span="8">
<Tooltip>
<template #title>{{ t('system.login.captchaRefreshTooltip') }}</template>
<img style="height: 40px" :src="computedCaptchaUrl" @click="handleChangeCaptcha" />
</Tooltip>
<TextCaptcha
ref="captchaRef"
@after-refresh="({ key }) => (formData.key = key)"
height="40px"
:api="getCaptchaApi"
/>
</ACol>
</ARow>
@ -101,15 +104,15 @@
</Form>
</template>
<script lang="ts" setup>
import { reactive, ref, unref, computed } from 'vue';
import { computed, reactive, ref, unref } from 'vue';
import { Checkbox, Form, Input, Row, Col, Button, Divider, Tooltip } from 'ant-design-vue';
import { Button, Checkbox, Col, Divider, Form, Input, Row } from 'ant-design-vue';
import {
GithubFilled,
WechatFilled,
AlipayCircleFilled,
GithubFilled,
GoogleCircleFilled,
TwitterCircleFilled,
WechatFilled,
} from '@ant-design/icons-vue';
import LoginFormTitle from './LoginFormTitle.vue';
@ -117,13 +120,16 @@
import { useMessage } from '@/hooks/web/useMessage';
import { useUserStore } from '@/store/modules/user';
import { LoginStateEnum, useLoginState, useFormRules, useFormValid } from './useLogin';
import { LoginStateEnum, useFormRules, useFormValid, useLoginState } from './useLogin';
import { useDesign } from '@/hooks/web/useDesign';
import { buildUUID } from '@/utils/uuid';
import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
import { createPassword } from '@/utils/auth';
import { useAppStore } from '@/store/modules/app';
import { TextCaptcha } from '@/components/Verify';
//import { onKeyStroke } from '@vueuse/core';
const captchaRef = ref();
const ACol = Col;
const ARow = Row;
const FormItem = Form.Item;
@ -132,6 +138,7 @@
const { notification, createErrorModal } = useMessage();
const { prefixCls } = useDesign('login');
const userStore = useUserStore();
const appStore = useAppStore();
const { setLoginState, getLoginState } = useLoginState();
const { getFormRules } = useFormRules();
@ -140,11 +147,18 @@
const loading = ref(false);
const rememberMe = ref(false);
/**
* 是否使用验证码
*/
const computedUseCaptcha = computed(() => {
return appStore.systemProperties.captchaIdent;
});
const formData = reactive({
account: 'admin',
password: '123456',
captcha: '',
captchaKey: buildUUID(),
key: '',
});
const { validForm } = useFormValid(formRef);
@ -162,8 +176,8 @@
password: createPassword(data.account, data.password),
username: data.account,
mode: 'none', //
codeKey: formData.captchaKey,
code: formData.captcha,
key: formData.key,
code: JSON.stringify(unref(captchaRef).createValidateParameter(formData.captcha)),
});
if (userInfo) {
notification.success({
@ -178,18 +192,16 @@
content: (error as unknown as Error).message || t('sys.api.networkExceptionMsg'),
getContainer: () => document.body.querySelector(`.${prefixCls}`) || document.body,
});
handleChangeCaptcha();
unref(captchaRef).refresh();
} finally {
loading.value = false;
}
}
const computedCaptchaUrl = computed(() => {
return `${defHttp.getApiUrlByService(ApiServiceEnum.SMART_AUTH)}/auth/createCaptcha?codeKey=${
formData.captchaKey
}`;
});
const handleChangeCaptcha = () => {
formData.captchaKey = buildUUID();
const getCaptchaApi = () => {
return defHttp.post({
service: ApiServiceEnum.SMART_AUTH,
url: 'auth/createCaptcha',
});
};
</script>

20
types/store.d.ts

@ -56,3 +56,23 @@ export interface TableSetting {
columns: Recordable<Nullable<Array<ColumnOptionsType>>>;
showRowSelection: Recordable<Nullable<boolean>>;
}
/**
*
*/
export interface SystemProperties {
/**
*
*/
captchaEnabled?: boolean;
/**
*
*/
captchaType?: string;
/**
*
*/
captchaIdent?: string;
}

Loading…
Cancel
Save