mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-26 16:46:19 +08:00
feat: add VbenForm component (#4352)
* feat: add form component * fix: build error * feat: add form adapter * feat: add some component * feat: add some component * feat: add example * feat: suppoer custom action button * chore: update * feat: add example * feat: add formModel,formDrawer demo * fix: build error * fix: typo * fix: ci error --------- Co-authored-by: jinmao <jinmao88@qq.com> Co-authored-by: likui628 <90845831+likui628@users.noreply.github.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/form-ui": "workspace:*",
|
||||
"@vben-core/layout-ui": "workspace:*",
|
||||
"@vben-core/menu-ui": "workspace:*",
|
||||
"@vben-core/popup-ui": "workspace:*",
|
||||
|
@@ -130,18 +130,18 @@ function clearPreferencesAndLogout() {
|
||||
|
||||
<template v-else-if="slot.name === 'preferences'">
|
||||
<PreferencesButton
|
||||
class="mr-2"
|
||||
class="mr-1"
|
||||
@clear-preferences-and-logout="clearPreferencesAndLogout"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="slot.name === 'theme-toggle'">
|
||||
<ThemeToggle class="mr-2 mt-[2px]" />
|
||||
<ThemeToggle class="mr-1 mt-[2px]" />
|
||||
</template>
|
||||
<template v-else-if="slot.name === 'language-toggle'">
|
||||
<LanguageToggle class="mr-2" />
|
||||
<LanguageToggle class="mr-1" />
|
||||
</template>
|
||||
<template v-else-if="slot.name === 'fullscreen'">
|
||||
<VbenFullScreen class="mr-2" />
|
||||
<VbenFullScreen class="mr-1" />
|
||||
</template>
|
||||
</slot>
|
||||
</template>
|
||||
|
@@ -60,7 +60,7 @@ whenever(open, () => {
|
||||
});
|
||||
|
||||
const preventDefaultBrowserSearchHotKey = (event: KeyboardEvent) => {
|
||||
if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
if (event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
@@ -8,7 +8,7 @@ import { SearchX, X } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
import { mapTree, traverseTreeValues, uniqueByField } from '@vben/utils';
|
||||
import { VbenIcon, VbenScrollbar } from '@vben-core/shadcn-ui';
|
||||
import { isHttpUrl } from '@vben-core/shared';
|
||||
import { isHttpUrl } from '@vben-core/shared/utils';
|
||||
|
||||
import { onKeyStroke, useLocalStorage, useThrottleFn } from '@vueuse/core';
|
||||
|
||||
|
@@ -30,7 +30,7 @@ async function handleUpdate(value: string) {
|
||||
@update:model-value="handleUpdate"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<Languages class="size-4" />
|
||||
<Languages class="text-foreground size-4" />
|
||||
</VbenIconButton>
|
||||
</VbenDropdownRadioMenu>
|
||||
</div>
|
||||
|
@@ -1,12 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useVbenForm, z } from '@vben-core/form-ui';
|
||||
import { useVbenModal } from '@vben-core/popup-ui';
|
||||
import {
|
||||
VbenAvatar,
|
||||
VbenButton,
|
||||
VbenInputPassword,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
avatar?: string;
|
||||
@@ -34,10 +32,29 @@ const emit = defineEmits<{
|
||||
submit: RegisterEmits['submit'];
|
||||
}>();
|
||||
|
||||
const formState = reactive({
|
||||
lockScreenPassword: '',
|
||||
submitted: false,
|
||||
});
|
||||
const [Form, { resetForm, validate }] = useVbenForm(
|
||||
reactive({
|
||||
commonConfig: {
|
||||
hideLabel: true,
|
||||
hideRequiredMark: true,
|
||||
},
|
||||
schema: computed(() => [
|
||||
{
|
||||
component: 'VbenInputPassword' as const,
|
||||
componentProps: {
|
||||
placeholder: $t('widgets.lockScreen.placeholder'),
|
||||
},
|
||||
fieldName: 'lockScreenPassword',
|
||||
formFieldProps: { validateOnBlur: false },
|
||||
label: $t('authentication.password'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('widgets.lockScreen.placeholder') }),
|
||||
},
|
||||
]),
|
||||
showDefaultActions: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const [Modal] = useVbenModal({
|
||||
onConfirm() {
|
||||
@@ -45,27 +62,16 @@ const [Modal] = useVbenModal({
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
// reset value reopen
|
||||
formState.submitted = false;
|
||||
formState.lockScreenPassword = '';
|
||||
resetForm();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const passwordStatus = computed(() => {
|
||||
return formState.submitted && !formState.lockScreenPassword
|
||||
? 'error'
|
||||
: 'default';
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
if (passwordStatus.value !== 'default') {
|
||||
return;
|
||||
async function handleSubmit() {
|
||||
const { valid, values } = await validate();
|
||||
if (valid) {
|
||||
emit('submit', values?.lockScreenPassword);
|
||||
}
|
||||
emit('submit', {
|
||||
lockScreenPassword: formState.lockScreenPassword,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,17 +96,8 @@ function handleSubmit() {
|
||||
{{ text }}
|
||||
</div>
|
||||
</div>
|
||||
<VbenInputPassword
|
||||
v-model="formState.lockScreenPassword"
|
||||
:error-tip="$t('widgets.lockScreen.placeholder')"
|
||||
:label="$t('widgets.lockScreen.password')"
|
||||
:placeholder="$t('widgets.lockScreen.placeholder')"
|
||||
:status="passwordStatus"
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
/>
|
||||
<VbenButton class="w-full" @click="handleSubmit">
|
||||
<Form />
|
||||
<VbenButton class="mt-1 w-full" @click="handleSubmit">
|
||||
{{ $t('widgets.lockScreen.screenButton') }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
|
@@ -4,11 +4,8 @@ import { computed, reactive, ref } from 'vue';
|
||||
import { LockKeyhole } from '@vben/icons';
|
||||
import { $t, useI18n } from '@vben/locales';
|
||||
import { storeToRefs, useLockStore } from '@vben/stores';
|
||||
import {
|
||||
VbenAvatar,
|
||||
VbenButton,
|
||||
VbenInputPassword,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
import { useVbenForm, z } from '@vben-core/form-ui';
|
||||
import { VbenAvatar, VbenButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { useDateFormat, useNow } from '@vueuse/core';
|
||||
|
||||
@@ -38,36 +35,40 @@ const date = useDateFormat(now, 'YYYY-MM-DD dddd', { locales: locale.value });
|
||||
const showUnlockForm = ref(false);
|
||||
const { lockScreenPassword } = storeToRefs(lockStore);
|
||||
|
||||
const formState = reactive({
|
||||
password: '',
|
||||
submitted: false,
|
||||
});
|
||||
|
||||
const validPass = computed(
|
||||
() => lockScreenPassword?.value === formState.password,
|
||||
const [Form, { form, validate }] = useVbenForm(
|
||||
reactive({
|
||||
commonConfig: {
|
||||
hideLabel: true,
|
||||
hideRequiredMark: true,
|
||||
},
|
||||
schema: computed(() => [
|
||||
{
|
||||
component: 'VbenInputPassword' as const,
|
||||
componentProps: {
|
||||
placeholder: $t('widgets.lockScreen.placeholder'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
]),
|
||||
showDefaultActions: false,
|
||||
}),
|
||||
);
|
||||
|
||||
const passwordStatus = computed(() => {
|
||||
if (formState.submitted && !validPass.value) {
|
||||
return 'error';
|
||||
const validPass = computed(
|
||||
() => lockScreenPassword?.value === form?.values?.password,
|
||||
);
|
||||
|
||||
async function handleSubmit() {
|
||||
const { valid } = await validate();
|
||||
if (valid) {
|
||||
if (validPass.value) {
|
||||
lockStore.unlockScreen();
|
||||
} else {
|
||||
form.setFieldError('password', $t('authentication.passwordErrorTip'));
|
||||
}
|
||||
}
|
||||
return 'default';
|
||||
});
|
||||
|
||||
const errorTip = computed(() => {
|
||||
return lockScreenPassword?.value === undefined || !formState.password
|
||||
? $t('widgets.lockScreen.placeholder')
|
||||
: $t('widgets.lockScreen.errorPasswordTip');
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
|
||||
if (!validPass.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
lockStore.unlockScreen();
|
||||
}
|
||||
|
||||
function toggleUnlockForm() {
|
||||
@@ -114,18 +115,9 @@ function toggleUnlockForm() {
|
||||
>
|
||||
<div class="flex-col-center mb-10 w-[300px]">
|
||||
<VbenAvatar :src="avatar" class="enter-x mb-6 size-20" />
|
||||
|
||||
<div class="enter-x mb-2 w-full items-center">
|
||||
<VbenInputPassword
|
||||
v-model="formState.password"
|
||||
:autofocus="true"
|
||||
:error-tip="errorTip"
|
||||
:label="$t('widgets.lockScreen.password')"
|
||||
:placeholder="$t('widgets.lockScreen.placeholder')"
|
||||
:status="passwordStatus"
|
||||
name="password"
|
||||
required
|
||||
type="password"
|
||||
/>
|
||||
<Form />
|
||||
</div>
|
||||
<VbenButton class="enter-x w-full" @click="handleSubmit">
|
||||
{{ $t('widgets.lockScreen.entry') }}
|
||||
|
@@ -67,7 +67,7 @@ function handleClick(item: NotificationItem) {
|
||||
>
|
||||
<template #trigger>
|
||||
<div class="flex-center mr-2 h-full" @click.stop="toggle()">
|
||||
<VbenIconButton class="bell-button relative">
|
||||
<VbenIconButton class="bell-button text-foreground relative">
|
||||
<span
|
||||
v-if="dot"
|
||||
class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded"
|
||||
|
@@ -13,7 +13,7 @@ function clearPreferencesAndLogout() {
|
||||
<template>
|
||||
<Preferences @clear-preferences-and-logout="clearPreferencesAndLogout">
|
||||
<VbenIconButton>
|
||||
<Settings class="size-4" />
|
||||
<Settings class="text-foreground size-4" />
|
||||
</VbenIconButton>
|
||||
</Preferences>
|
||||
</template>
|
||||
|
@@ -156,7 +156,7 @@ const {
|
||||
isSideMode,
|
||||
isSideNav,
|
||||
} = usePreferences();
|
||||
const { copy } = useClipboard();
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
|
||||
const [Drawer] = useVbenDrawer();
|
||||
|
||||
|
@@ -130,18 +130,18 @@ function toggleTheme(event: MouseEvent) {
|
||||
}
|
||||
|
||||
&__sun {
|
||||
@apply fill-foreground/70 stroke-none;
|
||||
@apply fill-foreground/90 stroke-none;
|
||||
|
||||
transition: transform 1.6s cubic-bezier(0.25, 0, 0.2, 1);
|
||||
transform-origin: center center;
|
||||
|
||||
&:hover > svg > & {
|
||||
@apply fill-foreground/70;
|
||||
@apply fill-foreground/90;
|
||||
}
|
||||
}
|
||||
|
||||
&__sun-beams {
|
||||
@apply stroke-foreground/70 stroke-[2px];
|
||||
@apply stroke-foreground/90 stroke-[2px];
|
||||
|
||||
transition:
|
||||
transform 1.6s cubic-bezier(0.5, 1.5, 0.75, 1.25),
|
||||
|
@@ -102,11 +102,7 @@ function handleOpenLock() {
|
||||
lockModalApi.open();
|
||||
}
|
||||
|
||||
function handleSubmitLock({
|
||||
lockScreenPassword,
|
||||
}: {
|
||||
lockScreenPassword: string;
|
||||
}) {
|
||||
function handleSubmitLock(lockScreenPassword: string) {
|
||||
lockModalApi.close();
|
||||
lockStore.lockScreen(lockScreenPassword);
|
||||
}
|
||||
|
Reference in New Issue
Block a user