diff --git a/apps/web-antd/src/adapter/vxe-table.ts b/apps/web-antd/src/adapter/vxe-table.ts index aaeb037f4..7068eac93 100644 --- a/apps/web-antd/src/adapter/vxe-table.ts +++ b/apps/web-antd/src/adapter/vxe-table.ts @@ -1,4 +1,5 @@ import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; +import type { Recordable } from '@vben/types'; import { h } from 'vue'; diff --git a/apps/web-antd/src/api/core/user.ts b/apps/web-antd/src/api/core/user.ts index 2b915968c..bd549d756 100644 --- a/apps/web-antd/src/api/core/user.ts +++ b/apps/web-antd/src/api/core/user.ts @@ -7,7 +7,7 @@ import { requestClient } from '#/api/request'; export interface MyUserInfo extends UserInfo { id: number; nickname: string; - email: string; + email?: string; phone?: string; dept?: string; last_login_time: string; @@ -19,7 +19,7 @@ export interface SysUserResult { dept_id?: number; username: string; nickname: string; - email: string; + email?: string; phone?: string; avatar?: string; status: number; @@ -46,18 +46,13 @@ export interface SysUpdateUserParams { username: string; nickname: string; avatar?: string; - email: string; + email?: string; phone?: string; roles: number[]; } -export interface SysAddUserParams { - dept_id?: number; - username: string; - nickname: string; +export interface SysAddUserParams extends SysUpdateUserParams { password: string; - email: string; - roles: number[]; } export interface SysResetPasswordParams { @@ -66,6 +61,16 @@ export interface SysResetPasswordParams { confirm_password: string; } +export interface SysUpdateUserPhoneParams { + phone: string; + captcha: string; +} + +export interface SysUpdateUserEmailParams { + email: string; + captcha: string; +} + /** * 获取用户信息 */ @@ -99,6 +104,20 @@ export async function updateSysUserPasswordApi( return requestClient.put(`/api/v1/sys/users/${pk}/password`, data); } -export async function deleteSysUserApi(username: string) { - return requestClient.delete(`/api/v1/sys/users/${username}`); +export async function deleteSysUserApi(pk: number) { + return requestClient.delete(`/api/v1/sys/users/${pk}`); +} + +export async function updateSysUserPhoneApi( + pk: number, + data: SysUpdateUserPhoneParams, +) { + return requestClient.put(`/api/v1/sys/users/${pk}/phones`, data); +} + +export async function updateSysUserEmailApi( + pk: number, + data: SysUpdateUserEmailParams, +) { + return requestClient.put(`/api/v1/sys/users/${pk}/emails`, data); } diff --git a/apps/web-antd/src/plugins/aliyun_sms/api/index.ts b/apps/web-antd/src/plugins/aliyun_sms/api/index.ts new file mode 100644 index 000000000..e36631abc --- /dev/null +++ b/apps/web-antd/src/plugins/aliyun_sms/api/index.ts @@ -0,0 +1,12 @@ +import { requestClient } from '#/api/request'; + +interface phoneCaptchaParams { + phone: string; +} + +/** + * 发送短信验证码 + */ +export async function getPhoneCaptchaApi(data: phoneCaptchaParams) { + return requestClient.post('/api/v1/phone/captcha', data); +} diff --git a/apps/web-antd/src/plugins/email/api/index.ts b/apps/web-antd/src/plugins/email/api/index.ts new file mode 100644 index 000000000..801786e1d --- /dev/null +++ b/apps/web-antd/src/plugins/email/api/index.ts @@ -0,0 +1,12 @@ +import { requestClient } from '#/api/request'; + +export interface emailCaptchaParams { + email: string; +} + +/** + * 获取邮箱验证码 + */ +export async function getEmailCaptchaApi(data: emailCaptchaParams) { + return requestClient.post('/api/v1/email/captcha', data); +} diff --git a/apps/web-antd/src/views/_core/profile/data.ts b/apps/web-antd/src/views/_core/profile/data.ts index ba7c418f8..7eaa1d52d 100644 --- a/apps/web-antd/src/views/_core/profile/data.ts +++ b/apps/web-antd/src/views/_core/profile/data.ts @@ -1,3 +1,5 @@ +import type { AnyFunction } from '@vben/types'; + import type { VbenFormSchema } from '#/adapter/form'; import type { OnActionClickFn, VxeGridProps } from '#/adapter/vxe-table'; import type { OnlineMonitorResult } from '#/api'; @@ -6,13 +8,77 @@ import { $t } from '@vben/locales'; import { z } from '#/adapter/form'; -export const schema: VbenFormSchema[] = [ +const CODE_LENGTH = 6; +export function phoneSchema(sendCodeFunc: AnyFunction): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'phone', + label: '手机号', + rules: z + .string() + .min(1, { message: $t('authentication.mobileTip') }) + .refine((v) => /^\d{11}$/.test(v), { + message: $t('authentication.mobileErrortip'), + }), + }, + { + component: 'VbenPinInput', + componentProps: { + codeLength: CODE_LENGTH, + createText: (countdown: number) => { + return countdown > 0 + ? $t('authentication.sendText', [countdown]) + : $t('authentication.sendCode'); + }, + handleSendCode: sendCodeFunc, + placeholder: $t('authentication.code'), + }, + fieldName: 'captcha', + label: '验证码', + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), + }, + ]; +} + +export function emailSchema(sendCodeFunc: AnyFunction): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'email', + label: '邮箱', + rules: z.string().email({ message: '无效的邮箱地址' }), + }, + { + component: 'VbenPinInput', + componentProps: { + codeLength: CODE_LENGTH, + createText: (countdown: number) => { + return countdown > 0 + ? $t('authentication.sendText', [countdown]) + : $t('authentication.sendCode'); + }, + handleSendCode: sendCodeFunc, + placeholder: $t('authentication.code'), + }, + fieldName: 'captcha', + label: '验证码', + rules: z.string().length(CODE_LENGTH, { + message: $t('authentication.codeTip', [CODE_LENGTH]), + }), + }, + ]; +} + +export const passwordSchema: VbenFormSchema[] = [ { component: 'InputPassword', fieldName: 'old_password', - label: '旧密码', + label: '当前密码', rules: z - .string({ message: '请输入旧密码' }) + .string({ message: '请输入当前密码' }) .min(6, '密码长度不能少于 6 个字符') .max(20, '密码长度不能超过 20 个字符'), }, diff --git a/apps/web-antd/src/views/_core/profile/index.vue b/apps/web-antd/src/views/_core/profile/index.vue index 4b1d63417..3680666c6 100644 --- a/apps/web-antd/src/views/_core/profile/index.vue +++ b/apps/web-antd/src/views/_core/profile/index.vue @@ -9,7 +9,7 @@ import { getUserInfoApi } from '#/api'; import ComingSoon from '#/views/_core/fallback/coming-soon.vue'; import BasicInfo from '#/views/_core/profile/basic-info.vue'; import OnlineDevice from '#/views/_core/profile/online-device.vue'; -import ResetPassword from '#/views/_core/profile/reset-password.vue'; +import Security from '#/views/_core/profile/security.vue'; const userinfo = ref(); const tabList = [ @@ -64,9 +64,9 @@ onMounted(() => { @tab-change="(key: string) => onTabChange(key)" >
- +
-
+
diff --git a/apps/web-antd/src/views/_core/profile/reset-password.vue b/apps/web-antd/src/views/_core/profile/reset-password.vue deleted file mode 100644 index 71400747b..000000000 --- a/apps/web-antd/src/views/_core/profile/reset-password.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - diff --git a/apps/web-antd/src/views/_core/profile/security.vue b/apps/web-antd/src/views/_core/profile/security.vue new file mode 100644 index 000000000..8282600ec --- /dev/null +++ b/apps/web-antd/src/views/_core/profile/security.vue @@ -0,0 +1,223 @@ + + + diff --git a/apps/web-antd/src/views/system/user/index.vue b/apps/web-antd/src/views/system/user/index.vue index a0b7d7fca..86a530a97 100644 --- a/apps/web-antd/src/views/system/user/index.vue +++ b/apps/web-antd/src/views/system/user/index.vue @@ -109,7 +109,7 @@ function onRefresh() { function onActionClick({ code, row }: OnActionClickParams) { switch (code) { case 'delete': { - deleteSysUserApi(row.username).then(() => { + deleteSysUserApi(row.id).then(() => { message.success({ content: $t('ui.actionMessage.deleteSuccess', [row.username]), key: 'action_process_msg', @@ -283,7 +283,7 @@ onMounted(() => {