diff --git a/vue/apps/web-antd/src/api/core/auth.ts b/vue/apps/web-antd/src/api/core/auth.ts index 2f960b8..374a698 100644 --- a/vue/apps/web-antd/src/api/core/auth.ts +++ b/vue/apps/web-antd/src/api/core/auth.ts @@ -8,6 +8,12 @@ export namespace AuthApi { account?: string; password?: string; } + /** 注册 */ + export interface RegisterParams { + account?: string; + code?: string; + password?: string; + } export interface Token { token: string; @@ -39,6 +45,22 @@ export async function loginApi(data: AuthApi.LoginParams) { return requestClient.post('/user/login', data); } +/** + * 发送验证码 + */ +export async function sendCodeApi(str: string) { + return requestClient.post('/user/sendCode', { str }); +} + +/** + * 注册 + */ +export async function registerApi(data: AuthApi.RegisterParams) { + // 密码加密 + data.password = sha256(data.password?.toString() || ''); + return requestClient.post('/user/register', data); +} + /** * 刷新accessToken */ diff --git a/vue/apps/web-antd/src/preferences.ts b/vue/apps/web-antd/src/preferences.ts index 6004cd1..7e4c379 100644 --- a/vue/apps/web-antd/src/preferences.ts +++ b/vue/apps/web-antd/src/preferences.ts @@ -46,5 +46,6 @@ export const overridesPreferences = defineOverridesPreferences({ }, tabbar: { middleClickToClose: true, + keepAlive: false, }, }); diff --git a/vue/apps/web-antd/src/router/routes/modules/ai.ts b/vue/apps/web-antd/src/router/routes/modules/ai.ts index f232735..9209ca0 100644 --- a/vue/apps/web-antd/src/router/routes/modules/ai.ts +++ b/vue/apps/web-antd/src/router/routes/modules/ai.ts @@ -22,7 +22,7 @@ const routes: RouteRecordRaw[] = [ authority: ['iva'], icon: 'mdi:video', title: $t('ai.intelligence_video_analysis'), - keepAlive: false, + keepAlive: true, }, component: () => import('#/views/ai/iva/index.vue'), }, @@ -33,6 +33,7 @@ const routes: RouteRecordRaw[] = [ authority: ['sca'], icon: 'mdi:ice-cream', title: $t('ai.silkworm_cocoon_analysis'), + keepAlive: false, }, component: () => import('#/views/ai/sca/index.vue'), }, @@ -43,6 +44,7 @@ const routes: RouteRecordRaw[] = [ authority: ['ysa'], icon: 'mdi:account-key-outline', title: $t('ai.young_silkworm_analysis'), + keepAlive: false, }, component: () => import('#/views/ai/ysa/index.vue'), }, @@ -53,7 +55,7 @@ const routes: RouteRecordRaw[] = [ meta: { icon: 'mdi:wall-fire', iframeSrc: 'http://171.212.101.199:13010/', - keepAlive: true, + keepAlive: false, title: '检索增强生成', }, }, diff --git a/vue/apps/web-antd/src/views/_core/authentication/register.vue b/vue/apps/web-antd/src/views/_core/authentication/register.vue index b1a5de7..e3288a5 100644 --- a/vue/apps/web-antd/src/views/_core/authentication/register.vue +++ b/vue/apps/web-antd/src/views/_core/authentication/register.vue @@ -2,25 +2,72 @@ import type { VbenFormSchema } from '@vben/common-ui'; import type { Recordable } from '@vben/types'; -import { computed, h, ref } from 'vue'; +import { computed, h, ref, useTemplateRef } from 'vue'; import { AuthenticationRegister, z } from '@vben/common-ui'; import { $t } from '@vben/locales'; +import { registerApi, sendCodeApi } from '#/api'; +import { useAuthStore } from '#/store'; + defineOptions({ name: 'Register' }); - const loading = ref(false); +const CODE_LENGTH = 4; +const registerRef = + useTemplateRef>('registerRef'); +const sending = ref(false); const formSchema = computed((): VbenFormSchema[] => { return [ { component: 'VbenInput', componentProps: { - placeholder: $t('authentication.usernameTip'), + placeholder: $t('authentication.emailTip'), }, - fieldName: 'username', - label: $t('authentication.username'), - rules: z.string().min(1, { message: $t('authentication.usernameTip') }), + fieldName: 'account', + label: $t('authentication.email'), + rules: z + .string() + .min(1, { message: $t('authentication.emailTip') }) + .refine((v) => /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(v), { + message: $t('邮箱格式错误'), + }), + }, + { + component: 'VbenPinInput', + componentProps: { + codeLength: CODE_LENGTH, + createText(countdown: number) { + if (sending.value) { + return $t('正在发送中'); + } + return countdown > 0 + ? $t('authentication.sendText', [countdown]) + : $t('authentication.sendCode'); + }, + placeholder: $t('authentication.code'), + async handleSendCode() { + sending.value = true; + try { + const formApi = registerRef.value?.getFormApi(); + if (!formApi) throw new Error('formApi is not ready'); + + await formApi.validateField('account'); + const isPhoneReady = await formApi.isFieldValid('account'); + if (!isPhoneReady) throw new Error('邮箱不可为空'); + + const { account } = await formApi.getValues(); + await sendCodeApi(account); + } finally { + sending.value = false; + } + }, + }, + fieldName: 'code', + label: $t('authentication.code'), + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), }, { component: 'VbenInputPassword', @@ -80,15 +127,32 @@ const formSchema = computed((): VbenFormSchema[] => { }, ]; }); +async function handleSubmit(value: Recordable) { + loading.value = true; + try { + const params = { + account: value.account, + code: value.code, + password: value.password, + }; + const { accessToken } = await registerApi(params); -function handleSubmit(value: Recordable) { - // eslint-disable-next-line no-console - console.log('register submit:', value); + if (accessToken) { + // 注册成功后,把 token 交给 authStore 走统一流程 + const authStore = useAuthStore(); + authStore.authLogin({ account: value.account, password: value.password }); + } + } catch (error) { + console.error('注册失败', error); + } finally { + loading.value = false; + } }