This commit is contained in:
dap
2024-09-11 09:38:40 +08:00
307 changed files with 7263 additions and 3017 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@vben/web-antd",
"version": "5.2.2",
"version": "5.3.0-beta.2",
"homepage": "https://vben.pro",
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
"repository": {
@@ -51,7 +51,7 @@
"lodash-es": "^4.17.21",
"pinia": "2.2.2",
"tinymce": "^7.3.0",
"vue": "^3.5.3",
"vue": "^3.5.4",
"vue-router": "^4.4.3"
},
"devDependencies": {

View File

@@ -0,0 +1,114 @@
import type {
BaseFormComponentType,
VbenFormSchema as FormSchema,
VbenFormProps,
} from '@vben/common-ui';
import { h } from 'vue';
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import {
AutoComplete,
Button,
Checkbox,
CheckboxGroup,
DatePicker,
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
TimePicker,
TreeSelect,
Upload,
} from 'ant-design-vue';
// 业务表单组件适配
export type FormComponentType =
| 'AutoComplete'
| 'Checkbox'
| 'CheckboxGroup'
| 'DatePicker'
| 'Divider'
| 'Input'
| 'InputNumber'
| 'InputPassword'
| 'Mentions'
| 'Radio'
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'Select'
| 'Space'
| 'Switch'
| 'TimePicker'
| 'TreeSelect'
| 'Upload'
| BaseFormComponentType;
// 初始化表单组件并注册到form组件内部
setupVbenForm<FormComponentType>({
components: {
AutoComplete,
Checkbox,
CheckboxGroup,
DatePicker,
// 自定义默认的重置按钮
DefaultResetActionButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'default' }, slots);
},
// 自定义默认的提交按钮
DefaultSubmitActionButton: (props, { attrs, slots }) => {
return h(Button, { ...props, attrs, type: 'primary' }, slots);
},
Divider,
Input,
InputNumber,
InputPassword,
Mentions,
Radio,
RadioGroup,
RangePicker,
Rate,
Select,
Space,
Switch,
TimePicker,
TreeSelect,
Upload,
},
config: {
baseModelPropName: 'value',
modelPropNameMap: {
Checkbox: 'checked',
Radio: 'checked',
Switch: 'checked',
Upload: 'fileList',
},
},
defineRules: {
required: (value, _params, ctx) => {
if ((!value && value !== 0) || value.length === 0) {
return $t('formRules.required', [ctx.label]);
}
return true;
},
},
});
const useVbenForm = useForm<FormComponentType>;
export { useVbenForm, z };
export type VbenFormSchema = FormSchema<FormComponentType>;
export type { VbenFormProps };

View File

@@ -0,0 +1 @@
export * from './form';

View File

@@ -12,7 +12,7 @@ import {
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { storeToRefs, useAccessStore, useUserStore } from '@vben/stores';
import { useAccessStore, useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';
import { message } from 'ant-design-vue';
@@ -22,6 +22,7 @@ import { $t } from '#/locales';
import { resetRoutes } from '#/router';
import { useAuthStore, useNotifyStore } from '#/store';
import { useTenantStore } from '#/store/tenant';
import LoginForm from '#/views/_core/authentication/login.vue';
const userStore = useUserStore();
const authStore = useAuthStore();
@@ -75,8 +76,6 @@ const menus = computed(() => {
return defaultMenus;
});
const { loginLoading } = storeToRefs(authStore);
const avatar = computed(() => {
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
});
@@ -125,11 +124,9 @@ function handleViewAll() {
<AuthenticationLoginExpiredModal
v-model:open="accessStore.loginExpired"
:avatar
:loading="loginLoading"
password-placeholder="123456"
username-placeholder="vben"
@submit="authStore.authLogin"
/>
>
<LoginForm />
</AuthenticationLoginExpiredModal>
</template>
<template #lock-screen>
<LockScreen :avatar @to-login="handleLogout" />

View File

@@ -1,15 +1,49 @@
<script lang="ts" setup>
import type { LoginCodeParams } from '@vben/common-ui';
import type { LoginCodeParams, VbenFormSchema } from '@vben/common-ui';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { AuthenticationCodeLogin } from '@vben/common-ui';
import { LOGIN_PATH } from '@vben/constants';
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
defineOptions({ name: 'CodeLogin' });
const loading = ref(false);
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenInput',
componentProps: {
placeholder: $t('authentication.mobile'),
},
fieldName: 'phoneNumber',
label: $t('authentication.mobile'),
rules: z
.string()
.min(1, { message: $t('authentication.mobileTip') })
.refine((v) => /^\d{11}$/.test(v), {
message: $t('authentication.mobileErrortip'),
}),
},
{
component: 'VbenPinInput',
componentProps: {
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
placeholder: $t('authentication.code'),
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
},
];
});
/**
* 异步处理登录操作
* Asynchronously handle the login process
@@ -22,8 +56,8 @@ async function handleLogin(values: LoginCodeParams) {
<template>
<AuthenticationCodeLogin
:form-schema="formSchema"
:loading="loading"
:login-path="LOGIN_PATH"
@submit="handleLogin"
/>
</template>

View File

@@ -1,13 +1,32 @@
<script lang="ts" setup>
import { ref } from 'vue';
import type { VbenFormSchema } from '@vben/common-ui';
import { AuthenticationForgetPassword } from '@vben/common-ui';
import { LOGIN_PATH } from '@vben/constants';
import { computed, ref } from 'vue';
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
defineOptions({ name: 'ForgetPassword' });
const loading = ref(false);
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenInput',
componentProps: {
placeholder: 'example@example.com',
},
fieldName: 'email',
label: $t('authentication.email'),
rules: z
.string()
.min(1, { message: $t('authentication.emailTip') })
.email($t('authentication.emailValidErrorTip')),
},
];
});
function handleSubmit(value: string) {
console.log('reset email:', value);
}
@@ -15,8 +34,8 @@ function handleSubmit(value: string) {
<template>
<AuthenticationForgetPassword
:form-schema="formSchema"
:loading="loading"
:login-path="LOGIN_PATH"
@submit="handleSubmit"
/>
</template>

View File

@@ -1,93 +1,91 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import type { VbenFormSchema } from '@vben/common-ui';
import type { BasicOption } from '@vben/types';
import { AuthenticationLogin } from '@vben/common-ui';
import { computed } from 'vue';
import { omit } from 'lodash-es';
import { AuthenticationLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { tenantList, type TenantResp } from '#/api';
import { captchaImage, type CaptchaResponse } from '#/api/core/captcha';
import { useAuthStore } from '#/store';
import OauthLogin from './oauth-login.vue';
defineOptions({ name: 'Login' });
const authStore = useAuthStore();
const captchaInfo = ref<CaptchaResponse>({
captchaEnabled: false,
img: '',
uuid: '',
const MOCK_USER_OPTIONS: BasicOption[] = [
{
label: '超级管理员',
value: 'vben',
},
{
label: '管理员',
value: 'admin',
},
{
label: '用户',
value: 'jack',
},
];
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenSelect',
componentProps: {
options: MOCK_USER_OPTIONS,
placeholder: $t('authentication.selectAccount'),
},
fieldName: 'selectAccount',
label: $t('authentication.selectAccount'),
rules: z
.string()
.min(1, { message: $t('authentication.selectAccount') })
.optional()
.default('vben'),
},
{
component: 'VbenInput',
componentProps: {
placeholder: $t('authentication.usernameTip'),
},
dependencies: {
trigger(values, form) {
if (values.selectAccount) {
const findUser = MOCK_USER_OPTIONS.find(
(item) => item.value === values.selectAccount,
);
if (findUser) {
form.setValues({
password: '123456',
username: findUser.value,
});
}
}
},
triggerFields: ['selectAccount'],
},
fieldName: 'username',
label: $t('authentication.username'),
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
},
{
component: 'VbenInputPassword',
componentProps: {
placeholder: $t('authentication.password'),
},
fieldName: 'password',
label: $t('authentication.password'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
},
];
});
async function loadCaptcha() {
const resp = await captchaImage();
if (resp.captchaEnabled) {
resp.img = `data:image/png;base64,${resp.img}`;
}
captchaInfo.value = resp;
}
const tenantInfo = ref<TenantResp>({
tenantEnabled: false,
voList: [],
});
async function loadTenant() {
const resp = await tenantList();
tenantInfo.value = resp;
}
onMounted(() => {
loadCaptcha();
loadTenant();
});
interface LoginForm {
code?: string;
grantType: string;
password: string;
tenantId: string;
username: string;
}
const loginRef = ref<InstanceType<typeof AuthenticationLogin>>();
async function handleAccountLogin(values: LoginForm) {
try {
const requestParam: any = omit(values, ['code']);
// 验证码
if (captchaInfo.value.captchaEnabled) {
requestParam.code = values.code;
requestParam.uuid = captchaInfo.value.uuid;
}
// 登录
await authStore.authLogin(requestParam);
} catch (error) {
console.error(error);
// 处理验证码错误
if (error instanceof Error) {
// 刷新验证码
loginRef.value?.resetCaptcha();
}
}
}
</script>
<template>
<AuthenticationLogin
ref="loginRef"
:captcha-base64="captchaInfo.img"
:form-schema="formSchema"
:loading="authStore.loginLoading"
:show-register="false"
:tenant-options="tenantInfo.voList"
:use-captcha="captchaInfo.captchaEnabled"
:use-tenant="tenantInfo.tenantEnabled"
@captcha-click="loadCaptcha"
@submit="handleAccountLogin"
>
<template #third-party-login>
<OauthLogin />
</template>
</AuthenticationLogin>
@submit="authStore.authLogin"
/>
</template>

View File

@@ -1,15 +1,91 @@
<script lang="ts" setup>
import type { LoginAndRegisterParams } from '@vben/common-ui';
import type { LoginAndRegisterParams, VbenFormSchema } from '@vben/common-ui';
import { ref } from 'vue';
import { computed, h, ref } from 'vue';
import { AuthenticationRegister } from '@vben/common-ui';
import { LOGIN_PATH } from '@vben/constants';
import { AuthenticationRegister, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
defineOptions({ name: 'Register' });
const loading = ref(false);
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenInput',
componentProps: {
placeholder: $t('authentication.usernameTip'),
},
fieldName: 'username',
label: $t('authentication.username'),
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
},
{
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: $t('authentication.password'),
},
fieldName: 'password',
label: $t('authentication.password'),
renderComponentContent() {
return {
strengthText: () => $t('authentication.passwordStrength'),
};
},
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
},
{
component: 'VbenInputPassword',
componentProps: {
placeholder: $t('authentication.confirmPassword'),
},
dependencies: {
rules(values) {
const { password } = values;
return z
.string()
.min(1, { message: $t('authentication.passwordTip') })
.refine((value) => value === password, {
message: $t('authentication.confirmPasswordTip'),
});
},
triggerFields: ['password'],
},
fieldName: 'confirmPassword',
label: $t('authentication.confirmPassword'),
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
},
{
component: 'VbenCheckbox',
fieldName: 'agreePolicy',
renderComponentContent: () => ({
default: () =>
h('span', [
$t('authentication.agree'),
h(
'a',
{
class:
'cursor-pointer text-primary ml-1 hover:text-primary-hover',
href: '',
},
[
$t('authentication.privacyPolicy'),
'&',
$t('authentication.terms'),
],
),
]),
}),
rules: z.boolean().refine((value) => !!value, {
message: $t('authentication.agreeTip'),
}),
},
];
});
function handleSubmit(value: LoginAndRegisterParams) {
console.log('register submit:', value);
}
@@ -17,8 +93,8 @@ function handleSubmit(value: LoginAndRegisterParams) {
<template>
<AuthenticationRegister
:form-schema="formSchema"
:loading="loading"
:login-path="LOGIN_PATH"
@submit="handleSubmit"
/>
</template>