mirror of
https://gitee.com/yudaocode/yudao-ui-admin-vben.git
synced 2025-12-30 02:22:25 +00:00
Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -4,7 +4,8 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"codeLogin": "Code Login",
|
"codeLogin": "Code Login",
|
||||||
"qrcodeLogin": "Qr Code Login",
|
"qrcodeLogin": "Qr Code Login",
|
||||||
"forgetPassword": "Forget Password"
|
"forgetPassword": "Forget Password",
|
||||||
|
"profile": "Profile"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "注册",
|
"register": "注册",
|
||||||
"codeLogin": "验证码登录",
|
"codeLogin": "验证码登录",
|
||||||
"qrcodeLogin": "二维码登录",
|
"qrcodeLogin": "二维码登录",
|
||||||
"forgetPassword": "忘记密码"
|
"forgetPassword": "忘记密码",
|
||||||
|
"profile": "个人中心"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
|
|||||||
65
apps/web-antd/src/views/_core/profile/base-setting.vue
Normal file
65
apps/web-antd/src/views/_core/profile/base-setting.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileBaseSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { getUserInfoApi } from '#/api';
|
||||||
|
|
||||||
|
const profileBaseSettingRef = ref();
|
||||||
|
|
||||||
|
const MOCK_ROLES_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: '管理员',
|
||||||
|
value: 'super',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'realName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'username',
|
||||||
|
component: 'Input',
|
||||||
|
label: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'roles',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
mode: 'tags',
|
||||||
|
options: MOCK_ROLES_OPTIONS,
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'introduction',
|
||||||
|
component: 'Textarea',
|
||||||
|
label: '个人简介',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getUserInfoApi();
|
||||||
|
profileBaseSettingRef.value.getFormApi().setValues(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileNotificationSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '其他用户的消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'systemMessage',
|
||||||
|
label: '系统消息',
|
||||||
|
description: '系统消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'todoTask',
|
||||||
|
label: '待办任务',
|
||||||
|
description: '待办任务将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileNotificationSetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
66
apps/web-antd/src/views/_core/profile/password-setting.vue
Normal file
66
apps/web-antd/src/views/_core/profile/password-setting.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const profilePasswordSettingRef = ref();
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'oldPassword',
|
||||||
|
label: '旧密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入旧密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'newPassword',
|
||||||
|
label: '新密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请输入新密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'confirmPassword',
|
||||||
|
label: '确认密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请再次输入新密码',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules(values) {
|
||||||
|
const { newPassword } = values;
|
||||||
|
return z
|
||||||
|
.string({ required_error: '请再次输入新密码' })
|
||||||
|
.min(1, { message: '请再次输入新密码' })
|
||||||
|
.refine((value) => value === newPassword, {
|
||||||
|
message: '两次输入的密码不一致',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerFields: ['newPassword'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
message.success('密码修改成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfilePasswordSetting
|
||||||
|
ref="profilePasswordSettingRef"
|
||||||
|
class="w-1/3"
|
||||||
|
:form-schema="formSchema"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
43
apps/web-antd/src/views/_core/profile/security-setting.vue
Normal file
43
apps/web-antd/src/views/_core/profile/security-setting.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileSecuritySetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '当前密码强度:强',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityPhone',
|
||||||
|
label: '密保手机',
|
||||||
|
description: '已绑定手机:138****8293',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityQuestion',
|
||||||
|
label: '密保问题',
|
||||||
|
description: '未设置密保问题,密保问题可有效保护账户安全',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityEmail',
|
||||||
|
label: '备用邮箱',
|
||||||
|
description: '已绑定邮箱:ant***sign.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
fieldName: 'securityMfa',
|
||||||
|
label: 'MFA 设备',
|
||||||
|
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileSecuritySetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -24,7 +24,7 @@ onMounted(() => {
|
|||||||
{ name: '定制', value: 310 },
|
{ name: '定制', value: 310 },
|
||||||
{ name: '技术支持', value: 274 },
|
{ name: '技术支持', value: 274 },
|
||||||
{ name: '远程', value: 400 },
|
{ name: '远程', value: 400 },
|
||||||
].sort((a, b) => {
|
].toSorted((a, b) => {
|
||||||
return a.value - b.value;
|
return a.value - b.value;
|
||||||
}),
|
}),
|
||||||
name: '商业占比',
|
name: '商业占比',
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"codeLogin": "Code Login",
|
"codeLogin": "Code Login",
|
||||||
"qrcodeLogin": "Qr Code Login",
|
"qrcodeLogin": "Qr Code Login",
|
||||||
"forgetPassword": "Forget Password"
|
"forgetPassword": "Forget Password",
|
||||||
|
"profile": "Profile"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "注册",
|
"register": "注册",
|
||||||
"codeLogin": "验证码登录",
|
"codeLogin": "验证码登录",
|
||||||
"qrcodeLogin": "二维码登录",
|
"qrcodeLogin": "二维码登录",
|
||||||
"forgetPassword": "忘记密码"
|
"forgetPassword": "忘记密码",
|
||||||
|
"profile": "个人中心"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
|
|||||||
65
apps/web-ele/src/views/_core/profile/base-setting.vue
Normal file
65
apps/web-ele/src/views/_core/profile/base-setting.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileBaseSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { getUserInfoApi } from '#/api';
|
||||||
|
|
||||||
|
const profileBaseSettingRef = ref();
|
||||||
|
|
||||||
|
const MOCK_ROLES_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: '管理员',
|
||||||
|
value: 'super',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'realName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'username',
|
||||||
|
component: 'Input',
|
||||||
|
label: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'roles',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
mode: 'tags',
|
||||||
|
options: MOCK_ROLES_OPTIONS,
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'introduction',
|
||||||
|
component: 'Textarea',
|
||||||
|
label: '个人简介',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getUserInfoApi();
|
||||||
|
profileBaseSettingRef.value.getFormApi().setValues(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileNotificationSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '其他用户的消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'systemMessage',
|
||||||
|
label: '系统消息',
|
||||||
|
description: '系统消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'todoTask',
|
||||||
|
label: '待办任务',
|
||||||
|
description: '待办任务将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileNotificationSetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
66
apps/web-ele/src/views/_core/profile/password-setting.vue
Normal file
66
apps/web-ele/src/views/_core/profile/password-setting.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
const profilePasswordSettingRef = ref();
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'oldPassword',
|
||||||
|
label: '旧密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入旧密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'newPassword',
|
||||||
|
label: '新密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请输入新密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'confirmPassword',
|
||||||
|
label: '确认密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请再次输入新密码',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules(values) {
|
||||||
|
const { newPassword } = values;
|
||||||
|
return z
|
||||||
|
.string({ required_error: '请再次输入新密码' })
|
||||||
|
.min(1, { message: '请再次输入新密码' })
|
||||||
|
.refine((value) => value === newPassword, {
|
||||||
|
message: '两次输入的密码不一致',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerFields: ['newPassword'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
ElMessage.success('密码修改成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfilePasswordSetting
|
||||||
|
ref="profilePasswordSettingRef"
|
||||||
|
class="w-1/3"
|
||||||
|
:form-schema="formSchema"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
43
apps/web-ele/src/views/_core/profile/security-setting.vue
Normal file
43
apps/web-ele/src/views/_core/profile/security-setting.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileSecuritySetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '当前密码强度:强',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityPhone',
|
||||||
|
label: '密保手机',
|
||||||
|
description: '已绑定手机:138****8293',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityQuestion',
|
||||||
|
label: '密保问题',
|
||||||
|
description: '未设置密保问题,密保问题可有效保护账户安全',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityEmail',
|
||||||
|
label: '备用邮箱',
|
||||||
|
description: '已绑定邮箱:ant***sign.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
fieldName: 'securityMfa',
|
||||||
|
label: 'MFA 设备',
|
||||||
|
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileSecuritySetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -24,7 +24,7 @@ onMounted(() => {
|
|||||||
{ name: '定制', value: 310 },
|
{ name: '定制', value: 310 },
|
||||||
{ name: '技术支持', value: 274 },
|
{ name: '技术支持', value: 274 },
|
||||||
{ name: '远程', value: 400 },
|
{ name: '远程', value: 400 },
|
||||||
].sort((a, b) => {
|
].toSorted((a, b) => {
|
||||||
return a.value - b.value;
|
return a.value - b.value;
|
||||||
}),
|
}),
|
||||||
name: '商业占比',
|
name: '商业占比',
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"codeLogin": "Code Login",
|
"codeLogin": "Code Login",
|
||||||
"qrcodeLogin": "Qr Code Login",
|
"qrcodeLogin": "Qr Code Login",
|
||||||
"forgetPassword": "Forget Password"
|
"forgetPassword": "Forget Password",
|
||||||
|
"profile": "Profile"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "注册",
|
"register": "注册",
|
||||||
"codeLogin": "验证码登录",
|
"codeLogin": "验证码登录",
|
||||||
"qrcodeLogin": "二维码登录",
|
"qrcodeLogin": "二维码登录",
|
||||||
"forgetPassword": "忘记密码"
|
"forgetPassword": "忘记密码",
|
||||||
|
"profile": "个人中心"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
|
|||||||
65
apps/web-naive/src/views/_core/profile/base-setting.vue
Normal file
65
apps/web-naive/src/views/_core/profile/base-setting.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileBaseSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { getUserInfoApi } from '#/api';
|
||||||
|
|
||||||
|
const profileBaseSettingRef = ref();
|
||||||
|
|
||||||
|
const MOCK_ROLES_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: '管理员',
|
||||||
|
value: 'super',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'realName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'username',
|
||||||
|
component: 'Input',
|
||||||
|
label: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'roles',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
mode: 'tags',
|
||||||
|
options: MOCK_ROLES_OPTIONS,
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'introduction',
|
||||||
|
component: 'Textarea',
|
||||||
|
label: '个人简介',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getUserInfoApi();
|
||||||
|
profileBaseSettingRef.value.getFormApi().setValues(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
49
apps/web-naive/src/views/_core/profile/index.vue
Normal file
49
apps/web-naive/src/views/_core/profile/index.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { Profile } from '@vben/common-ui';
|
||||||
|
import { useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import ProfileBase from './base-setting.vue';
|
||||||
|
import ProfileNotificationSetting from './notification-setting.vue';
|
||||||
|
import ProfilePasswordSetting from './password-setting.vue';
|
||||||
|
import ProfileSecuritySetting from './security-setting.vue';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const tabsValue = ref<string>('basic');
|
||||||
|
|
||||||
|
const tabs = ref([
|
||||||
|
{
|
||||||
|
label: '基本设置',
|
||||||
|
value: 'basic',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '安全设置',
|
||||||
|
value: 'security',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '修改密码',
|
||||||
|
value: 'password',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '新消息提醒',
|
||||||
|
value: 'notice',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Profile
|
||||||
|
v-model:model-value="tabsValue"
|
||||||
|
title="个人中心"
|
||||||
|
:user-info="userStore.userInfo"
|
||||||
|
:tabs="tabs"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<ProfileBase v-if="tabsValue === 'basic'" />
|
||||||
|
<ProfileSecuritySetting v-if="tabsValue === 'security'" />
|
||||||
|
<ProfilePasswordSetting v-if="tabsValue === 'password'" />
|
||||||
|
<ProfileNotificationSetting v-if="tabsValue === 'notice'" />
|
||||||
|
</template>
|
||||||
|
</Profile>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileNotificationSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '其他用户的消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'systemMessage',
|
||||||
|
label: '系统消息',
|
||||||
|
description: '系统消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'todoTask',
|
||||||
|
label: '待办任务',
|
||||||
|
description: '待办任务将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileNotificationSetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
66
apps/web-naive/src/views/_core/profile/password-setting.vue
Normal file
66
apps/web-naive/src/views/_core/profile/password-setting.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
|
|
||||||
|
const profilePasswordSettingRef = ref();
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'oldPassword',
|
||||||
|
label: '旧密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入旧密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'newPassword',
|
||||||
|
label: '新密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请输入新密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'confirmPassword',
|
||||||
|
label: '确认密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请再次输入新密码',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules(values) {
|
||||||
|
const { newPassword } = values;
|
||||||
|
return z
|
||||||
|
.string({ required_error: '请再次输入新密码' })
|
||||||
|
.min(1, { message: '请再次输入新密码' })
|
||||||
|
.refine((value) => value === newPassword, {
|
||||||
|
message: '两次输入的密码不一致',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerFields: ['newPassword'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
message.success('密码修改成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfilePasswordSetting
|
||||||
|
ref="profilePasswordSettingRef"
|
||||||
|
class="w-1/3"
|
||||||
|
:form-schema="formSchema"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
43
apps/web-naive/src/views/_core/profile/security-setting.vue
Normal file
43
apps/web-naive/src/views/_core/profile/security-setting.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileSecuritySetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '当前密码强度:强',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityPhone',
|
||||||
|
label: '密保手机',
|
||||||
|
description: '已绑定手机:138****8293',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityQuestion',
|
||||||
|
label: '密保问题',
|
||||||
|
description: '未设置密保问题,密保问题可有效保护账户安全',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityEmail',
|
||||||
|
label: '备用邮箱',
|
||||||
|
description: '已绑定邮箱:ant***sign.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
fieldName: 'securityMfa',
|
||||||
|
label: 'MFA 设备',
|
||||||
|
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileSecuritySetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -24,7 +24,7 @@ onMounted(() => {
|
|||||||
{ name: '定制', value: 310 },
|
{ name: '定制', value: 310 },
|
||||||
{ name: '技术支持', value: 274 },
|
{ name: '技术支持', value: 274 },
|
||||||
{ name: '远程', value: 400 },
|
{ name: '远程', value: 400 },
|
||||||
].sort((a, b) => {
|
].toSorted((a, b) => {
|
||||||
return a.value - b.value;
|
return a.value - b.value;
|
||||||
}),
|
}),
|
||||||
name: '商业占比',
|
name: '商业占比',
|
||||||
|
|||||||
@@ -44,14 +44,11 @@
|
|||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"cropperjs": "catalog:",
|
"cropperjs": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"lodash-es": "^4.17.21",
|
"es-toolkit": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"tdesign-vue-next": "^1.17.1",
|
"tdesign-vue-next": "catalog:",
|
||||||
"tinymce": "catalog:",
|
"tinymce": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/lodash-es": "^4.17.12"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { onMounted } from 'vue';
|
|||||||
|
|
||||||
import { usePreferences } from '@vben/preferences';
|
import { usePreferences } from '@vben/preferences';
|
||||||
|
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'es-toolkit/compat';
|
||||||
import { ConfigProvider } from 'tdesign-vue-next';
|
import { ConfigProvider } from 'tdesign-vue-next';
|
||||||
import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
|
import zhConfig from 'tdesign-vue-next/es/locale/zh_CN';
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
|||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { initStores } from '@vben/stores';
|
import { initStores } from '@vben/stores';
|
||||||
import '@vben/styles';
|
import '@vben/styles';
|
||||||
import '@vben/styles/antd';
|
|
||||||
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
@@ -16,7 +15,9 @@ import { initSetupVbenForm } from './adapter/form';
|
|||||||
import App from './app.vue';
|
import App from './app.vue';
|
||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
|
|
||||||
|
// import '@vben/styles/antd';
|
||||||
// 引入组件库的少量全局样式变量
|
// 引入组件库的少量全局样式变量
|
||||||
|
import 'tdesign-vue-next/es/style/index.css';
|
||||||
|
|
||||||
async function bootstrap(namespace: string) {
|
async function bootstrap(namespace: string) {
|
||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
@@ -23,6 +24,7 @@ import LoginForm from '#/views/_core/authentication/login.vue';
|
|||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
const notifications = ref<NotificationItem[]>([
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
|
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
|
||||||
date: '3小时前',
|
date: '3小时前',
|
||||||
isRead: true,
|
isRead: true,
|
||||||
@@ -30,6 +32,7 @@ const notifications = ref<NotificationItem[]>([
|
|||||||
title: '收到了 14 份新周报',
|
title: '收到了 14 份新周报',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
avatar: 'https://avatar.vercel.sh/1',
|
||||||
date: '刚刚',
|
date: '刚刚',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
@@ -37,6 +40,7 @@ const notifications = ref<NotificationItem[]>([
|
|||||||
title: '朱偏右 回复了你',
|
title: '朱偏右 回复了你',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
avatar: 'https://avatar.vercel.sh/1',
|
||||||
date: '2024-01-01',
|
date: '2024-01-01',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
@@ -44,14 +48,34 @@ const notifications = ref<NotificationItem[]>([
|
|||||||
title: '曲丽丽 评论了你',
|
title: '曲丽丽 评论了你',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 4,
|
||||||
avatar: 'https://avatar.vercel.sh/satori',
|
avatar: 'https://avatar.vercel.sh/satori',
|
||||||
date: '1天前',
|
date: '1天前',
|
||||||
isRead: false,
|
isRead: false,
|
||||||
message: '描述信息描述信息描述信息',
|
message: '描述信息描述信息描述信息',
|
||||||
title: '代办提醒',
|
title: '代办提醒',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
avatar: 'https://avatar.vercel.sh/satori',
|
||||||
|
date: '1天前',
|
||||||
|
isRead: false,
|
||||||
|
message: '描述信息描述信息描述信息',
|
||||||
|
title: '跳转Workspace示例',
|
||||||
|
link: '/workspace',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
avatar: 'https://avatar.vercel.sh/satori',
|
||||||
|
date: '1天前',
|
||||||
|
isRead: false,
|
||||||
|
message: '描述信息描述信息描述信息',
|
||||||
|
title: '跳转外部链接示例',
|
||||||
|
link: 'https://doc.vben.pro',
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
@@ -61,6 +85,13 @@ const showDot = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const menus = computed(() => [
|
const menus = computed(() => [
|
||||||
|
{
|
||||||
|
handler: () => {
|
||||||
|
router.push({ name: 'Profile' });
|
||||||
|
},
|
||||||
|
icon: 'lucide:user',
|
||||||
|
text: $t('page.auth.profile'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
openWindow(VBEN_DOC_URL, {
|
openWindow(VBEN_DOC_URL, {
|
||||||
@@ -102,6 +133,17 @@ function handleNoticeClear() {
|
|||||||
notifications.value = [];
|
notifications.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markRead(id: number | string) {
|
||||||
|
const item = notifications.value.find((item) => item.id === id);
|
||||||
|
if (item) {
|
||||||
|
item.isRead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(id: number | string) {
|
||||||
|
notifications.value = notifications.value.filter((item) => item.id !== id);
|
||||||
|
}
|
||||||
|
|
||||||
function handleMakeAll() {
|
function handleMakeAll() {
|
||||||
notifications.value.forEach((item) => (item.isRead = true));
|
notifications.value.forEach((item) => (item.isRead = true));
|
||||||
}
|
}
|
||||||
@@ -144,6 +186,8 @@ watch(
|
|||||||
:dot="showDot"
|
:dot="showDot"
|
||||||
:notifications="notifications"
|
:notifications="notifications"
|
||||||
@clear="handleNoticeClear"
|
@clear="handleNoticeClear"
|
||||||
|
@read="(item) => item.id && markRead(item.id)"
|
||||||
|
@remove="(item) => item.id && remove(item.id)"
|
||||||
@make-all="handleMakeAll"
|
@make-all="handleMakeAll"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "Register",
|
"register": "Register",
|
||||||
"codeLogin": "Code Login",
|
"codeLogin": "Code Login",
|
||||||
"qrcodeLogin": "Qr Code Login",
|
"qrcodeLogin": "Qr Code Login",
|
||||||
"forgetPassword": "Forget Password"
|
"forgetPassword": "Forget Password",
|
||||||
|
"profile": "Profile"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
"register": "注册",
|
"register": "注册",
|
||||||
"codeLogin": "验证码登录",
|
"codeLogin": "验证码登录",
|
||||||
"qrcodeLogin": "二维码登录",
|
"qrcodeLogin": "二维码登录",
|
||||||
"forgetPassword": "忘记密码"
|
"forgetPassword": "忘记密码",
|
||||||
|
"profile": "个人中心"
|
||||||
},
|
},
|
||||||
"dashboard": {
|
"dashboard": {
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
|
|||||||
65
apps/web-tdesign/src/views/_core/profile/base-setting.vue
Normal file
65
apps/web-tdesign/src/views/_core/profile/base-setting.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { BasicOption } from '@vben/types';
|
||||||
|
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileBaseSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { getUserInfoApi } from '#/api';
|
||||||
|
|
||||||
|
const profileBaseSettingRef = ref();
|
||||||
|
|
||||||
|
const MOCK_ROLES_OPTIONS: BasicOption[] = [
|
||||||
|
{
|
||||||
|
label: '管理员',
|
||||||
|
value: 'super',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '用户',
|
||||||
|
value: 'user',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
value: 'test',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'realName',
|
||||||
|
component: 'Input',
|
||||||
|
label: '姓名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'username',
|
||||||
|
component: 'Input',
|
||||||
|
label: '用户名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'roles',
|
||||||
|
component: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
mode: 'tags',
|
||||||
|
options: MOCK_ROLES_OPTIONS,
|
||||||
|
},
|
||||||
|
label: '角色',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'introduction',
|
||||||
|
component: 'Textarea',
|
||||||
|
label: '个人简介',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const data = await getUserInfoApi();
|
||||||
|
profileBaseSettingRef.value.getFormApi().setValues(data);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileNotificationSetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '其他用户的消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'systemMessage',
|
||||||
|
label: '系统消息',
|
||||||
|
description: '系统消息将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'todoTask',
|
||||||
|
label: '待办任务',
|
||||||
|
description: '待办任务将以站内信的形式通知',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileNotificationSetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
|
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import { ProfilePasswordSetting, z } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/tdesign';
|
||||||
|
|
||||||
|
const profilePasswordSettingRef = ref();
|
||||||
|
|
||||||
|
const formSchema = computed((): VbenFormSchema[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldName: 'oldPassword',
|
||||||
|
label: '旧密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入旧密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'newPassword',
|
||||||
|
label: '新密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请输入新密码',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldName: 'confirmPassword',
|
||||||
|
label: '确认密码',
|
||||||
|
component: 'VbenInputPassword',
|
||||||
|
componentProps: {
|
||||||
|
passwordStrength: true,
|
||||||
|
placeholder: '请再次输入新密码',
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
rules(values) {
|
||||||
|
const { newPassword } = values;
|
||||||
|
return z
|
||||||
|
.string({ required_error: '请再次输入新密码' })
|
||||||
|
.min(1, { message: '请再次输入新密码' })
|
||||||
|
.refine((value) => value === newPassword, {
|
||||||
|
message: '两次输入的密码不一致',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
triggerFields: ['newPassword'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
message.success('密码修改成功');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfilePasswordSetting
|
||||||
|
ref="profilePasswordSettingRef"
|
||||||
|
class="w-1/3"
|
||||||
|
:form-schema="formSchema"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { ProfileSecuritySetting } from '@vben/common-ui';
|
||||||
|
|
||||||
|
const formSchema = computed(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'accountPassword',
|
||||||
|
label: '账户密码',
|
||||||
|
description: '当前密码强度:强',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityPhone',
|
||||||
|
label: '密保手机',
|
||||||
|
description: '已绑定手机:138****8293',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityQuestion',
|
||||||
|
label: '密保问题',
|
||||||
|
description: '未设置密保问题,密保问题可有效保护账户安全',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
fieldName: 'securityEmail',
|
||||||
|
label: '备用邮箱',
|
||||||
|
description: '已绑定邮箱:ant***sign.com',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
fieldName: 'securityMfa',
|
||||||
|
label: 'MFA 设备',
|
||||||
|
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ProfileSecuritySetting :form-schema="formSchema" />
|
||||||
|
</template>
|
||||||
@@ -24,7 +24,7 @@ onMounted(() => {
|
|||||||
{ name: '定制', value: 310 },
|
{ name: '定制', value: 310 },
|
||||||
{ name: '技术支持', value: 274 },
|
{ name: '技术支持', value: 274 },
|
||||||
{ name: '远程', value: 400 },
|
{ name: '远程', value: 400 },
|
||||||
].sort((a, b) => {
|
].toSorted((a, b) => {
|
||||||
return a.value - b.value;
|
return a.value - b.value;
|
||||||
}),
|
}),
|
||||||
name: '商业占比',
|
name: '商业占比',
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"styl",
|
"styl",
|
||||||
"taze",
|
"taze",
|
||||||
"Tinymce",
|
"Tinymce",
|
||||||
|
"tdesign",
|
||||||
"ui-kit",
|
"ui-kit",
|
||||||
"uicons",
|
"uicons",
|
||||||
"unplugin",
|
"unplugin",
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ const parsedFiles = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="border-border shadow-float relative rounded-xl border">
|
<div class="relative rounded-xl border border-border shadow-float">
|
||||||
<div
|
<div
|
||||||
class="not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6"
|
class="not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6"
|
||||||
>
|
>
|
||||||
<div class="flex w-full max-w-[700px] px-2">
|
<div class="flex w-full max-w-[700px] px-2">
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<slot v-if="parsedFiles.length > 0"></slot>
|
<slot v-if="parsedFiles.length > 0"></slot>
|
||||||
<div v-else class="text-destructive text-sm">
|
<div v-else class="text-sm text-destructive">
|
||||||
<span class="bg-destructive text-foreground rounded-sm px-1 py-1">
|
<span class="rounded-sm bg-destructive px-1 py-1 text-foreground">
|
||||||
ERROR:
|
ERROR:
|
||||||
</span>
|
</span>
|
||||||
The preview directory does not exist. Please check the 'dir'
|
The preview directory does not exist. Please check the 'dir'
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ const toggleOpen = () => {
|
|||||||
<template>
|
<template>
|
||||||
<TabsRoot
|
<TabsRoot
|
||||||
v-model="currentTab"
|
v-model="currentTab"
|
||||||
class="bg-background-deep border-border overflow-hidden rounded-b-xl border-t"
|
class="overflow-hidden rounded-b-xl border-t border-border bg-background-deep"
|
||||||
@update:model-value="open = true"
|
@update:model-value="open = true"
|
||||||
>
|
>
|
||||||
<div class="border-border bg-background flex border-b-2 pr-2">
|
<div class="flex border-b-2 border-border bg-background pr-2">
|
||||||
<div class="flex w-full items-center justify-between text-[13px]">
|
<div class="flex w-full items-center justify-between text-[13px]">
|
||||||
<TabsList class="relative flex">
|
<TabsList class="relative flex">
|
||||||
<template v-if="open">
|
<template v-if="open">
|
||||||
@@ -64,7 +64,7 @@ const toggleOpen = () => {
|
|||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
:value="tab.label"
|
:value="tab.label"
|
||||||
class="border-box text-foreground px-4 py-3 data-[state=active]:text-[var(--vp-c-indigo-1)]"
|
class="border-box px-4 py-3 text-foreground data-[state=active]:text-[var(--vp-c-indigo-1)]"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
@@ -81,7 +81,7 @@ const toggleOpen = () => {
|
|||||||
<VbenTooltip side="top">
|
<VbenTooltip side="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<Code
|
<Code
|
||||||
class="hover:bg-accent size-7 cursor-pointer rounded-full p-1.5"
|
class="size-7 cursor-pointer rounded-full p-1.5 hover:bg-accent"
|
||||||
@click="toggleOpen"
|
@click="toggleOpen"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -101,7 +101,7 @@ const toggleOpen = () => {
|
|||||||
as-child
|
as-child
|
||||||
class="rounded-xl"
|
class="rounded-xl"
|
||||||
>
|
>
|
||||||
<div class="text-foreground relative rounded-xl">
|
<div class="relative rounded-xl text-foreground">
|
||||||
<component :is="tab.component" class="border-0" />
|
<component :is="tab.component" class="border-0" />
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export const demoPreviewPlugin = (md: MarkdownRenderer) => {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const firstString = 'index.vue';
|
const firstString = 'index.vue';
|
||||||
childFiles = childFiles.sort((a, b) => {
|
childFiles = childFiles.toSorted((a, b) => {
|
||||||
if (a === firstString) return -1;
|
if (a === firstString) return -1;
|
||||||
if (b === firstString) return 1;
|
if (b === firstString) return 1;
|
||||||
return a.localeCompare(b, 'en', { sensitivity: 'base' });
|
return a.localeCompare(b, 'en', { sensitivity: 'base' });
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
|
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
|
||||||
|
| handleCollapsedChange | 表单收起展开状态变化回调 | `(collapsed: boolean) => void` | - |
|
||||||
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
||||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function handleUpdate(len: number) {
|
|||||||
<div
|
<div
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item"
|
:key="item"
|
||||||
class="even:bg-heavy bg-muted flex-center h-[220px] w-full"
|
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
|
||||||
>
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ function handleUpdate(len: number) {
|
|||||||
<div
|
<div
|
||||||
v-for="item in list"
|
v-for="item in list"
|
||||||
:key="item"
|
:key="item"
|
||||||
class="even:bg-heavy bg-muted flex-center h-[220px] w-full"
|
class="flex-center h-[220px] w-full bg-muted even:bg-heavy"
|
||||||
>
|
>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const scopeComplete = execSync('git status --porcelain || true')
|
|||||||
.trim()
|
.trim()
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.find((r) => ~r.indexOf('M src'))
|
.find((r) => ~r.indexOf('M src'))
|
||||||
?.replace(/(\/)/g, '%%')
|
?.replaceAll(/(\/)/g, '%%')
|
||||||
?.match(/src%%((\w|-)*)/)?.[1]
|
?.match(/src%%((\w|-)*)/)?.[1]
|
||||||
?.replace(/s$/, '');
|
?.replace(/s$/, '');
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import createCommand from 'eslint-plugin-command/config';
|
|||||||
export async function command() {
|
export async function command() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
// @ts-expect-error - no types
|
|
||||||
...createCommand(),
|
...createCommand(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export async function node(): Promise<Linter.Config[]> {
|
|||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
ignores: [],
|
ignores: [],
|
||||||
version: '>=18.0.0',
|
version: '>=20.12.0',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'n/prefer-global/buffer': ['error', 'never'],
|
'n/prefer-global/buffer': ['error', 'never'],
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { interopDefault } from '../util';
|
|||||||
|
|
||||||
export async function perfectionist(): Promise<Linter.Config[]> {
|
export async function perfectionist(): Promise<Linter.Config[]> {
|
||||||
const perfectionistPlugin = await interopDefault(
|
const perfectionistPlugin = await interopDefault(
|
||||||
// @ts-expect-error - no types
|
|
||||||
import('eslint-plugin-perfectionist'),
|
import('eslint-plugin-perfectionist'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { interopDefault } from '../util';
|
|||||||
|
|
||||||
export async function turbo(): Promise<Linter.Config[]> {
|
export async function turbo(): Promise<Linter.Config[]> {
|
||||||
const [pluginTurbo] = await Promise.all([
|
const [pluginTurbo] = await Promise.all([
|
||||||
// @ts-expect-error - no types
|
|
||||||
interopDefault(import('eslint-config-turbo')),
|
interopDefault(import('eslint-config-turbo')),
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { interopDefault } from '../util';
|
|||||||
export async function typescript(): Promise<Linter.Config[]> {
|
export async function typescript(): Promise<Linter.Config[]> {
|
||||||
const [pluginTs, parserTs] = await Promise.all([
|
const [pluginTs, parserTs] = await Promise.all([
|
||||||
interopDefault(import('@typescript-eslint/eslint-plugin')),
|
interopDefault(import('@typescript-eslint/eslint-plugin')),
|
||||||
// @ts-expect-error missing types
|
|
||||||
interopDefault(import('@typescript-eslint/parser')),
|
interopDefault(import('@typescript-eslint/parser')),
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
@@ -27,11 +26,11 @@ export async function typescript(): Promise<Linter.Config[]> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'@typescript-eslint': pluginTs,
|
'@typescript-eslint': pluginTs as any,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...pluginTs.configs['eslint-recommended'].overrides?.[0].rules,
|
...pluginTs.configs['eslint-recommended']?.overrides?.[0]?.rules,
|
||||||
...pluginTs.configs.strict.rules,
|
...pluginTs.configs.strict?.rules,
|
||||||
'@typescript-eslint/ban-ts-comment': [
|
'@typescript-eslint/ban-ts-comment': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ export async function vue(): Promise<Linter.Config[]> {
|
|||||||
const [pluginVue, parserVue, parserTs] = await Promise.all([
|
const [pluginVue, parserVue, parserTs] = await Promise.all([
|
||||||
interopDefault(import('eslint-plugin-vue')),
|
interopDefault(import('eslint-plugin-vue')),
|
||||||
interopDefault(import('vue-eslint-parser')),
|
interopDefault(import('vue-eslint-parser')),
|
||||||
// @ts-expect-error missing types
|
|
||||||
interopDefault(import('@typescript-eslint/parser')),
|
interopDefault(import('@typescript-eslint/parser')),
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"extends": "@vben/tsconfig/node.json",
|
"extends": "@vben/tsconfig/node.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"@tailwindcss/typography": "catalog:",
|
"@tailwindcss/typography": "catalog:",
|
||||||
"autoprefixer": "catalog:",
|
"autoprefixer": "catalog:",
|
||||||
"cssnano": "catalog:",
|
"cssnano": "catalog:",
|
||||||
|
"jiti": "catalog:",
|
||||||
"postcss": "catalog:",
|
"postcss": "catalog:",
|
||||||
"postcss-antd-fixes": "catalog:",
|
"postcss-antd-fixes": "catalog:",
|
||||||
"postcss-import": "catalog:",
|
"postcss-import": "catalog:",
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ function createCssOptions(injectGlobalScss = true): CSSOptions {
|
|||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
api: 'modern',
|
// api: 'modern',
|
||||||
importers: [new NodePackageImporter()],
|
importers: [new NodePackageImporter()],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
"node": ">=20.12.0",
|
"node": ">=20.12.0",
|
||||||
"pnpm": ">=10.14.0"
|
"pnpm": ">=10.14.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.14.0",
|
"packageManager": "pnpm@10.22.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
"@ctrl/tinycolor": "catalog:",
|
"@ctrl/tinycolor": "catalog:",
|
||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
"esbuild": "0.25.3",
|
"esbuild": "0.25.3",
|
||||||
|
"jiti": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:"
|
"vue": "catalog:"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
import './default.css';
|
import './default.css';
|
||||||
import './dark.css';
|
import './dark.css';
|
||||||
|
|
||||||
export {};
|
|
||||||
|
|||||||
@@ -4,5 +4,3 @@ import './css/global.css';
|
|||||||
import './css/transition.css';
|
import './css/transition.css';
|
||||||
import './css/nprogress.css';
|
import './css/nprogress.css';
|
||||||
import './css/ui.css';
|
import './css/ui.css';
|
||||||
|
|
||||||
export {};
|
|
||||||
|
|||||||
@@ -86,11 +86,9 @@
|
|||||||
"crypto-js": "catalog:",
|
"crypto-js": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"defu": "catalog:",
|
"defu": "catalog:",
|
||||||
|
"es-toolkit": "catalog:",
|
||||||
"jsencrypt": "catalog:",
|
"jsencrypt": "catalog:",
|
||||||
"lodash.clonedeep": "catalog:",
|
"lodash.clonedeep": "catalog:",
|
||||||
"lodash.get": "catalog:",
|
|
||||||
"lodash.isequal": "catalog:",
|
|
||||||
"lodash.set": "catalog:",
|
|
||||||
"nprogress": "catalog:",
|
"nprogress": "catalog:",
|
||||||
"tailwind-merge": "catalog:",
|
"tailwind-merge": "catalog:",
|
||||||
"theme-colors": "catalog:"
|
"theme-colors": "catalog:"
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export * from './upload';
|
|||||||
export * from './util';
|
export * from './util';
|
||||||
export * from './uuid'; // add by 芋艿:从 vben2.0 复制
|
export * from './uuid'; // add by 芋艿:从 vben2.0 复制
|
||||||
export * from './window';
|
export * from './window';
|
||||||
|
export { get, isEqual, set } from 'es-toolkit/compat';
|
||||||
|
// export { cloneDeep } from 'es-toolkit/object';
|
||||||
export { default as cloneDeep } from 'lodash.clonedeep';
|
export { default as cloneDeep } from 'lodash.clonedeep';
|
||||||
export { default as get } from 'lodash.get';
|
|
||||||
export { default as isEqual } from 'lodash.isequal';
|
|
||||||
export { default as set } from 'lodash.set';
|
|
||||||
|
|||||||
@@ -221,12 +221,8 @@ class PreferenceManager {
|
|||||||
const dom = document.documentElement;
|
const dom = document.documentElement;
|
||||||
const COLOR_WEAK = 'invert-mode';
|
const COLOR_WEAK = 'invert-mode';
|
||||||
const COLOR_GRAY = 'grayscale-mode';
|
const COLOR_GRAY = 'grayscale-mode';
|
||||||
colorWeakMode
|
dom.classList.toggle(COLOR_WEAK, colorWeakMode);
|
||||||
? dom.classList.add(COLOR_WEAK)
|
dom.classList.toggle(COLOR_GRAY, colorGrayMode);
|
||||||
: dom.classList.remove(COLOR_WEAK);
|
|
||||||
colorGrayMode
|
|
||||||
? dom.classList.add(COLOR_GRAY)
|
|
||||||
: dom.classList.remove(COLOR_GRAY);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async function handleSubmit(e: Event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const values = toRaw(await props.formApi.getValues());
|
const values = toRaw(await props.formApi.getValues()) ?? {};
|
||||||
await props.handleSubmit?.(values);
|
await props.handleSubmit?.(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ async function handleReset(e: Event) {
|
|||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
const props = unref(rootProps);
|
const props = unref(rootProps);
|
||||||
|
|
||||||
const values = toRaw(await props.formApi?.getValues());
|
const values = toRaw(await props.formApi?.getValues()) ?? {};
|
||||||
|
|
||||||
if (isFunction(props.handleReset)) {
|
if (isFunction(props.handleReset)) {
|
||||||
await props.handleReset?.(values);
|
await props.handleReset?.(values);
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ function getDefaultState(): VbenFormProps {
|
|||||||
handleReset: undefined,
|
handleReset: undefined,
|
||||||
handleSubmit: undefined,
|
handleSubmit: undefined,
|
||||||
handleValuesChange: undefined,
|
handleValuesChange: undefined,
|
||||||
|
handleCollapsedChange: undefined,
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
resetButtonOptions: {},
|
resetButtonOptions: {},
|
||||||
schema: [],
|
schema: [],
|
||||||
|
|||||||
@@ -341,7 +341,7 @@ onUnmounted(() => {
|
|||||||
:is="FieldComponent"
|
:is="FieldComponent"
|
||||||
ref="fieldComponentRef"
|
ref="fieldComponentRef"
|
||||||
:class="{
|
:class="{
|
||||||
'border-destructive focus:border-destructive hover:border-destructive/80 focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
|
'border-destructive hover:border-destructive/80 focus:border-destructive focus:shadow-[0_0_0_2px_rgba(255,38,5,0.06)]':
|
||||||
isInValid,
|
isInValid,
|
||||||
}"
|
}"
|
||||||
v-bind="createComponentProps(slotProps)"
|
v-bind="createComponentProps(slotProps)"
|
||||||
@@ -369,7 +369,7 @@ onUnmounted(() => {
|
|||||||
<CircleAlert
|
<CircleAlert
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'text-foreground/80 hover:text-foreground inline-flex size-5 cursor-pointer',
|
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const props = defineProps<Props>();
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<FormLabel :class="cn('flex items-center', props.class)">
|
<FormLabel :class="cn('flex items-center', props.class)">
|
||||||
<span v-if="required" class="text-destructive mr-[2px]">*</span>
|
<span v-if="required" class="mr-[2px] text-destructive">*</span>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
|
<VbenHelpTooltip v-if="help" trigger-class="size-3.5 ml-1">
|
||||||
<VbenRenderContent :content="help" />
|
<VbenRenderContent :content="help" />
|
||||||
|
|||||||
@@ -381,6 +381,10 @@ export interface VbenFormProps<
|
|||||||
* 表单字段映射
|
* 表单字段映射
|
||||||
*/
|
*/
|
||||||
fieldMappingTime?: FieldMappingTime;
|
fieldMappingTime?: FieldMappingTime;
|
||||||
|
/**
|
||||||
|
* 表单收起展开状态变化回调
|
||||||
|
*/
|
||||||
|
handleCollapsedChange?: (collapsed: boolean) => void;
|
||||||
/**
|
/**
|
||||||
* 表单重置回调
|
* 表单重置回调
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useForm } from 'vee-validate';
|
|||||||
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
|
import { object, ZodIntersection, ZodNumber, ZodObject, ZodString } from 'zod';
|
||||||
import { getDefaultsForSchema } from 'zod-defaults';
|
import { getDefaultsForSchema } from 'zod-defaults';
|
||||||
|
|
||||||
type ExtendFormProps = VbenFormProps & { formApi: ExtendedFormApi };
|
type ExtendFormProps = VbenFormProps & { formApi?: ExtendedFormApi };
|
||||||
|
|
||||||
export const [injectFormProps, provideFormProps] =
|
export const [injectFormProps, provideFormProps] =
|
||||||
createContext<[ComputedRef<ExtendFormProps> | ExtendFormProps, FormActions]>(
|
createContext<[ComputedRef<ExtendFormProps> | ExtendFormProps, FormActions]>(
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ const { delegatedSlots, form } = useFormInitial(props);
|
|||||||
provideFormProps([props, form]);
|
provideFormProps([props, form]);
|
||||||
|
|
||||||
const handleUpdateCollapsed = (value: boolean) => {
|
const handleUpdateCollapsed = (value: boolean) => {
|
||||||
currentCollapsed.value = !!value;
|
currentCollapsed.value = value;
|
||||||
|
// 触发收起展开状态变化回调
|
||||||
|
props.handleCollapsedChange?.(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
} from './use-form-context';
|
} from './use-form-context';
|
||||||
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
// 通过 extends 会导致热更新卡死,所以重复写了一遍
|
||||||
interface Props extends VbenFormProps {
|
interface Props extends VbenFormProps {
|
||||||
formApi: ExtendedFormApi;
|
formApi?: ExtendedFormApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
@@ -44,11 +44,13 @@ provideComponentRefMap(componentRefMap);
|
|||||||
props.formApi?.mount?.(form, componentRefMap);
|
props.formApi?.mount?.(form, componentRefMap);
|
||||||
|
|
||||||
const handleUpdateCollapsed = (value: boolean) => {
|
const handleUpdateCollapsed = (value: boolean) => {
|
||||||
props.formApi?.setState({ collapsed: !!value });
|
props.formApi?.setState({ collapsed: value });
|
||||||
|
// 触发收起展开状态变化回调
|
||||||
|
forward.value.handleCollapsedChange?.(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleKeyDownEnter(event: KeyboardEvent) {
|
function handleKeyDownEnter(event: KeyboardEvent) {
|
||||||
if (!state.value.submitOnEnter || !forward.value.formApi?.isMounted) {
|
if (!state?.value.submitOnEnter || !forward.value.formApi?.isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 如果是 textarea 不阻止默认行为,否则会导致无法换行。
|
// 如果是 textarea 不阻止默认行为,否则会导致无法换行。
|
||||||
@@ -58,11 +60,11 @@ function handleKeyDownEnter(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
forward.value.formApi.validateAndSubmitForm();
|
forward.value.formApi?.validateAndSubmitForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
||||||
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
state?.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const valuesCache: Recordable<any> = {};
|
const valuesCache: Recordable<any> = {};
|
||||||
@@ -74,7 +76,7 @@ onMounted(async () => {
|
|||||||
() => form.values,
|
() => form.values,
|
||||||
async (newVal) => {
|
async (newVal) => {
|
||||||
if (forward.value.handleValuesChange) {
|
if (forward.value.handleValuesChange) {
|
||||||
const fields = state.value.schema?.map((item) => {
|
const fields = state?.value.schema?.map((item) => {
|
||||||
return item.fieldName;
|
return item.fieldName;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,8 +93,9 @@ onMounted(async () => {
|
|||||||
|
|
||||||
if (changedFields.length > 0) {
|
if (changedFields.length > 0) {
|
||||||
// 调用handleValuesChange回调,传入所有表单值的深拷贝和变更的字段列表
|
// 调用handleValuesChange回调,传入所有表单值的深拷贝和变更的字段列表
|
||||||
|
const values = await forward.value.formApi?.getValues();
|
||||||
forward.value.handleValuesChange(
|
forward.value.handleValuesChange(
|
||||||
cloneDeep(await forward.value.formApi.getValues()),
|
cloneDeep(values ?? {}) as Record<string, any>,
|
||||||
changedFields,
|
changedFields,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -109,7 +112,7 @@ onMounted(async () => {
|
|||||||
<Form
|
<Form
|
||||||
@keydown.enter="handleKeyDownEnter"
|
@keydown.enter="handleKeyDownEnter"
|
||||||
v-bind="forward"
|
v-bind="forward"
|
||||||
:collapsed="state.collapsed"
|
:collapsed="state?.collapsed"
|
||||||
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
:component-bind-event-map="COMPONENT_BIND_EVENT_MAP"
|
||||||
:component-map="COMPONENT_MAP"
|
:component-map="COMPONENT_MAP"
|
||||||
:form="form"
|
:form="form"
|
||||||
@@ -126,7 +129,7 @@ onMounted(async () => {
|
|||||||
<slot v-bind="slotProps">
|
<slot v-bind="slotProps">
|
||||||
<FormActions
|
<FormActions
|
||||||
v-if="forward.showDefaultActions"
|
v-if="forward.showDefaultActions"
|
||||||
:model-value="state.collapsed"
|
:model-value="state?.collapsed"
|
||||||
@update:model-value="handleUpdateCollapsed"
|
@update:model-value="handleUpdateCollapsed"
|
||||||
>
|
>
|
||||||
<template #reset-before="resetSlotProps">
|
<template #reset-before="resetSlotProps">
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const style = computed((): CSSProperties => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main ref="contentElement" :style="style" class="bg-background-deep relative">
|
<main ref="contentElement" :style="style" class="relative bg-background-deep">
|
||||||
<Slot :style="overlayStyle">
|
<Slot :style="overlayStyle">
|
||||||
<slot name="overlay"></slot>
|
<slot name="overlay"></slot>
|
||||||
</Slot>
|
</Slot>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const style = computed((): CSSProperties => {
|
|||||||
<template>
|
<template>
|
||||||
<footer
|
<footer
|
||||||
:style="style"
|
:style="style"
|
||||||
class="bg-background-deep bottom-0 w-full transition-all duration-200"
|
class="bottom-0 w-full bg-background-deep transition-all duration-200"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const logoStyle = computed((): CSSProperties => {
|
|||||||
<header
|
<header
|
||||||
:class="theme"
|
:class="theme"
|
||||||
:style="style"
|
:style="style"
|
||||||
class="border-border bg-header top-0 flex w-full flex-[0_0_auto] items-center border-b pl-2 transition-[margin-top] duration-200"
|
class="top-0 flex w-full flex-[0_0_auto] items-center border-b border-border bg-header pl-2 transition-[margin-top] duration-200"
|
||||||
>
|
>
|
||||||
<div v-if="slots.logo" :style="logoStyle">
|
<div v-if="slots.logo" :style="logoStyle">
|
||||||
<slot name="logo"></slot>
|
<slot name="logo"></slot>
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ function handleMouseleave() {
|
|||||||
theme,
|
theme,
|
||||||
{
|
{
|
||||||
'bg-sidebar-deep': isSidebarMixed,
|
'bg-sidebar-deep': isSidebarMixed,
|
||||||
'bg-sidebar border-border border-r': !isSidebarMixed,
|
'border-r border-border bg-sidebar': !isSidebarMixed,
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:style="style"
|
:style="style"
|
||||||
@@ -295,7 +295,7 @@ function handleMouseleave() {
|
|||||||
'border-l': extraVisible,
|
'border-l': extraVisible,
|
||||||
}"
|
}"
|
||||||
:style="extraStyle"
|
:style="extraStyle"
|
||||||
class="border-border bg-sidebar fixed top-0 h-full overflow-hidden border-r transition-all duration-200"
|
class="fixed top-0 h-full overflow-hidden border-r border-border bg-sidebar transition-all duration-200"
|
||||||
>
|
>
|
||||||
<SidebarCollapseButton
|
<SidebarCollapseButton
|
||||||
v-if="isSidebarMixed && expandOnHover"
|
v-if="isSidebarMixed && expandOnHover"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const style = computed((): CSSProperties => {
|
|||||||
<template>
|
<template>
|
||||||
<section
|
<section
|
||||||
:style="style"
|
:style="style"
|
||||||
class="border-border bg-background flex w-full border-b transition-all"
|
class="flex w-full border-b border-border bg-background transition-all"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ function handleCollapsed() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm p-1"
|
class="flex-center absolute bottom-2 left-3 z-10 cursor-pointer rounded-sm bg-accent p-1 text-foreground/60 hover:bg-accent-hover hover:text-foreground"
|
||||||
@click.stop="handleCollapsed"
|
@click.stop="handleCollapsed"
|
||||||
>
|
>
|
||||||
<ChevronsRight v-if="collapsed" class="size-4" />
|
<ChevronsRight v-if="collapsed" class="size-4" />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ function toggleFixed() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="flex-center hover:text-foreground text-foreground/60 hover:bg-accent-hover bg-accent absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm p-[5px] transition-all duration-300"
|
class="flex-center absolute bottom-2 right-3 z-10 cursor-pointer rounded-sm bg-accent p-[5px] text-foreground/60 transition-all duration-300 hover:bg-accent-hover hover:text-foreground"
|
||||||
@click="toggleFixed"
|
@click="toggleFixed"
|
||||||
>
|
>
|
||||||
<PinOff v-if="!expandOnHover" class="size-3.5" />
|
<PinOff v-if="!expandOnHover" class="size-3.5" />
|
||||||
|
|||||||
@@ -610,7 +610,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
|||||||
<div
|
<div
|
||||||
v-if="maskVisible"
|
v-if="maskVisible"
|
||||||
:style="maskStyle"
|
:style="maskStyle"
|
||||||
class="bg-overlay fixed left-0 top-0 h-full w-full transition-[background-color] duration-200"
|
class="fixed left-0 top-0 h-full w-full bg-overlay transition-[background-color] duration-200"
|
||||||
@click="handleClickMask"
|
@click="handleClickMask"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const badgeStyle = computed(() => {
|
|||||||
v-else
|
v-else
|
||||||
:class="badgeClass"
|
:class="badgeClass"
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
class="text-primary-foreground flex-center rounded-xl px-1.5 py-0.5 text-[10px]"
|
class="flex-center rounded-xl px-1.5 py-0.5 text-[10px] text-primary-foreground"
|
||||||
>
|
>
|
||||||
{{ badge }}
|
{{ badge }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -125,16 +125,16 @@ $namespace: vben;
|
|||||||
border-color 0.15s ease;
|
border-color 0.15s ease;
|
||||||
|
|
||||||
&.is-active {
|
&.is-active {
|
||||||
@apply text-primary bg-primary dark:bg-accent;
|
@apply bg-primary text-primary dark:bg-accent;
|
||||||
|
|
||||||
.#{$namespace}-normal-menu__name,
|
.#{$namespace}-normal-menu__name,
|
||||||
.#{$namespace}-normal-menu__icon {
|
.#{$namespace}-normal-menu__icon {
|
||||||
@apply text-primary-foreground font-semibold;
|
@apply font-semibold text-primary-foreground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.is-active):hover {
|
&:not(.is-active):hover {
|
||||||
@apply dark:bg-accent text-primary bg-heavy dark:text-foreground;
|
@apply bg-heavy text-primary dark:bg-accent dark:text-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -144,24 +144,26 @@ export async function vbenPrompt<T = any>(
|
|||||||
|
|
||||||
const modelValue = ref<T | undefined>(defaultValue);
|
const modelValue = ref<T | undefined>(defaultValue);
|
||||||
const inputComponentRef = ref<null | VNode>(null);
|
const inputComponentRef = ref<null | VNode>(null);
|
||||||
const staticContents: Component[] = [];
|
const staticContents: Component[] = [
|
||||||
|
h(VbenRenderContent, { content, renderBr: true }),
|
||||||
staticContents.push(h(VbenRenderContent, { content, renderBr: true }));
|
];
|
||||||
|
|
||||||
const modelPropName = _modelPropName || 'modelValue';
|
const modelPropName = _modelPropName || 'modelValue';
|
||||||
const componentProps = { ..._componentProps };
|
const componentProps = { ..._componentProps };
|
||||||
|
|
||||||
// 每次渲染时都会重新计算的内容函数
|
// 每次渲染时都会重新计算的内容函数
|
||||||
const contentRenderer = () => {
|
const contentRenderer = () => {
|
||||||
const currentProps = { ...componentProps };
|
const currentProps = {
|
||||||
|
...componentProps,
|
||||||
|
[modelPropName]: modelValue.value,
|
||||||
|
[`onUpdate:${modelPropName}`]: (val: T) => {
|
||||||
|
modelValue.value = val;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// 设置当前值
|
// 设置当前值
|
||||||
currentProps[modelPropName] = modelValue.value;
|
|
||||||
|
|
||||||
// 设置更新处理函数
|
// 设置更新处理函数
|
||||||
currentProps[`onUpdate:${modelPropName}`] = (val: T) => {
|
|
||||||
modelValue.value = val;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建输入组件
|
// 创建输入组件
|
||||||
inputComponentRef.value = h(
|
inputComponentRef.value = h(
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ async function handleOpenChange(val: boolean) {
|
|||||||
containerClass,
|
containerClass,
|
||||||
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-[var(--radius)]',
|
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:w-[520px] sm:max-w-[80%] sm:rounded-[var(--radius)]',
|
||||||
{
|
{
|
||||||
'border-border border': bordered,
|
'border border-border': bordered,
|
||||||
'shadow-3xl': !bordered,
|
'shadow-3xl': !bordered,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -168,7 +168,7 @@ async function handleOpenChange(val: boolean) {
|
|||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
@click="handleCancel"
|
@click="handleCancel"
|
||||||
>
|
>
|
||||||
<X class="text-muted-foreground size-4" />
|
<X class="size-4 text-muted-foreground" />
|
||||||
</VbenButton>
|
</VbenButton>
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ const getForceMount = computed(() => {
|
|||||||
v-if="closable && closeIconPlacement === 'left'"
|
v-if="closable && closeIconPlacement === 'left'"
|
||||||
as-child
|
as-child
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-secondary"
|
||||||
>
|
>
|
||||||
<slot name="close-icon">
|
<slot name="close-icon">
|
||||||
<VbenIconButton>
|
<VbenIconButton>
|
||||||
@@ -264,7 +264,7 @@ const getForceMount = computed(() => {
|
|||||||
v-if="closable && closeIconPlacement === 'right'"
|
v-if="closable && closeIconPlacement === 'right'"
|
||||||
as-child
|
as-child
|
||||||
:disabled="submitting"
|
:disabled="submitting"
|
||||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
class="ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-secondary"
|
||||||
>
|
>
|
||||||
<slot name="close-icon">
|
<slot name="close-icon">
|
||||||
<VbenIconButton>
|
<VbenIconButton>
|
||||||
|
|||||||
@@ -104,6 +104,10 @@ const shouldDraggable = computed(
|
|||||||
() => draggable.value && !shouldFullscreen.value && header.value,
|
() => draggable.value && !shouldFullscreen.value && header.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const shouldCentered = computed(
|
||||||
|
() => centered.value && !shouldFullscreen.value,
|
||||||
|
);
|
||||||
|
|
||||||
const getAppendTo = computed(() => {
|
const getAppendTo = computed(() => {
|
||||||
return appendToMain.value
|
return appendToMain.value
|
||||||
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
||||||
@@ -115,6 +119,7 @@ const { dragging, transform } = useModalDraggable(
|
|||||||
headerRef,
|
headerRef,
|
||||||
shouldDraggable,
|
shouldDraggable,
|
||||||
getAppendTo,
|
getAppendTo,
|
||||||
|
shouldCentered,
|
||||||
);
|
);
|
||||||
|
|
||||||
const firstOpened = ref(false);
|
const firstOpened = ref(false);
|
||||||
@@ -132,7 +137,9 @@ watch(
|
|||||||
dialogRef.value = innerContentRef.$el;
|
dialogRef.value = innerContentRef.$el;
|
||||||
// reopen modal reassign value
|
// reopen modal reassign value
|
||||||
const { offsetX, offsetY } = transform;
|
const { offsetX, offsetY } = transform;
|
||||||
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
dialogRef.value.style.transform = shouldCentered.value
|
||||||
|
? `translate(${offsetX}px, calc(-50% + ${offsetY}px))`
|
||||||
|
: `translate(${offsetX}px, ${offsetY}px)`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
@@ -235,11 +242,11 @@ function handleClosed() {
|
|||||||
shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-[var(--radius)]',
|
shouldFullscreen ? 'sm:rounded-none' : 'sm:rounded-[var(--radius)]',
|
||||||
modalClass,
|
modalClass,
|
||||||
{
|
{
|
||||||
'border-border border': bordered,
|
'border border-border': bordered,
|
||||||
'shadow-3xl': !bordered,
|
'shadow-3xl': !bordered,
|
||||||
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0':
|
'left-0 top-0 size-full max-h-full !translate-x-0 !translate-y-0':
|
||||||
shouldFullscreen,
|
shouldFullscreen,
|
||||||
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
'top-1/2': centered && !shouldFullscreen,
|
||||||
'duration-300': !dragging,
|
'duration-300': !dragging,
|
||||||
hidden: isClosed,
|
hidden: isClosed,
|
||||||
},
|
},
|
||||||
@@ -311,7 +318,7 @@ function handleClosed() {
|
|||||||
<VbenLoading v-if="showLoading || submitting" spinning />
|
<VbenLoading v-if="showLoading || submitting" spinning />
|
||||||
<VbenIconButton
|
<VbenIconButton
|
||||||
v-if="fullscreenButton"
|
v-if="fullscreenButton"
|
||||||
class="hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
class="flex-center absolute right-10 top-3 hidden size-6 rounded-full px-1 text-lg text-foreground/80 opacity-70 transition-opacity hover:bg-accent hover:text-accent-foreground hover:opacity-100 focus:outline-none disabled:pointer-events-none sm:block"
|
||||||
@click="handleFullscreen"
|
@click="handleFullscreen"
|
||||||
>
|
>
|
||||||
<Shrink v-if="fullscreen" class="size-3.5" />
|
<Shrink v-if="fullscreen" class="size-3.5" />
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function useModalDraggable(
|
|||||||
dragRef: Ref<HTMLElement | undefined>,
|
dragRef: Ref<HTMLElement | undefined>,
|
||||||
draggable: ComputedRef<boolean>,
|
draggable: ComputedRef<boolean>,
|
||||||
containerSelector?: ComputedRef<string | undefined>,
|
containerSelector?: ComputedRef<string | undefined>,
|
||||||
|
centered?: ComputedRef<boolean>,
|
||||||
) {
|
) {
|
||||||
const transform = reactive({
|
const transform = reactive({
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
@@ -73,7 +74,10 @@ export function useModalDraggable(
|
|||||||
transform.offsetY = moveY;
|
transform.offsetY = moveY;
|
||||||
|
|
||||||
if (targetRef.value) {
|
if (targetRef.value) {
|
||||||
targetRef.value.style.transform = `translate(${moveX}px, ${moveY}px)`;
|
const isCentered = centered?.value;
|
||||||
|
targetRef.value.style.transform = isCentered
|
||||||
|
? `translate(${moveX}px, calc(-50% + ${moveY}px))`
|
||||||
|
: `translate(${moveX}px, ${moveY}px)`;
|
||||||
dragging.value = true;
|
dragging.value = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -108,7 +112,7 @@ export function useModalDraggable(
|
|||||||
|
|
||||||
const target = unrefElement(targetRef);
|
const target = unrefElement(targetRef);
|
||||||
if (target) {
|
if (target) {
|
||||||
target.style.transform = 'none';
|
target.style.transform = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const rootStyle = computed(() => {
|
|||||||
<span
|
<span
|
||||||
v-if="dot"
|
v-if="dot"
|
||||||
:class="dotClass"
|
:class="dotClass"
|
||||||
class="border-background absolute bottom-0 right-0 size-3 rounded-full border-2"
|
class="absolute bottom-0 right-0 size-3 rounded-full border-2 border-background"
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const { handleClick, visible } = useBackTop(props);
|
|||||||
<VbenButton
|
<VbenButton
|
||||||
v-if="visible"
|
v-if="visible"
|
||||||
:style="backTopStyle"
|
:style="backTopStyle"
|
||||||
class="dark:bg-accent dark:hover:bg-heavy bg-background hover:bg-heavy data shadow-float z-popup fixed bottom-10 size-10 rounded-full duration-500"
|
class="data z-popup fixed bottom-10 size-10 rounded-full bg-background shadow-float duration-500 hover:bg-heavy dark:bg-accent dark:hover:bg-heavy"
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="icon"
|
variant="icon"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function handleClick(index: number, path?: string) {
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
:class="{
|
:class="{
|
||||||
'text-foreground font-normal':
|
'font-normal text-foreground':
|
||||||
index === breadcrumbs.length - 1,
|
index === breadcrumbs.length - 1,
|
||||||
}"
|
}"
|
||||||
>{{ item.title }}
|
>{{ item.title }}
|
||||||
@@ -55,7 +55,7 @@ li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li a {
|
li a {
|
||||||
@apply text-muted-foreground bg-accent relative mr-9 flex h-7 items-center py-0 pl-[5px] pr-2 text-[13px];
|
@apply relative mr-9 flex h-7 items-center bg-accent py-0 pl-[5px] pr-2 text-[13px] text-muted-foreground;
|
||||||
}
|
}
|
||||||
|
|
||||||
li a > span {
|
li a > span {
|
||||||
@@ -84,7 +84,7 @@ li:last-child a::after {
|
|||||||
|
|
||||||
li a::before,
|
li a::before,
|
||||||
li a::after {
|
li a::after {
|
||||||
@apply border-accent absolute top-0 h-0 w-0 border-[.875rem] border-solid content-[''];
|
@apply absolute top-0 h-0 w-0 border-[.875rem] border-solid border-accent content-[''];
|
||||||
}
|
}
|
||||||
|
|
||||||
li a::before {
|
li a::before {
|
||||||
@@ -92,7 +92,7 @@ li a::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li a::after {
|
li a::after {
|
||||||
@apply border-l-accent left-full border-transparent;
|
@apply left-full border-transparent border-l-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:not(:last-child) a:hover {
|
li:not(:last-child) a:hover {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function handleItemClick(menu: IDropdownMenuItem) {
|
|||||||
<template v-for="menu in menus" :key="menu.value">
|
<template v-for="menu in menus" :key="menu.value">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
:disabled="menu.disabled"
|
:disabled="menu.disabled"
|
||||||
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
|
class="mb-1 cursor-pointer text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground"
|
||||||
@click="handleItemClick(menu)"
|
@click="handleItemClick(menu)"
|
||||||
>
|
>
|
||||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ function handleItemClick(value: string) {
|
|||||||
? 'bg-accent text-accent-foreground'
|
? 'bg-accent text-accent-foreground'
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
class="data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground text-foreground/80 mb-1 cursor-pointer"
|
class="mb-1 cursor-pointer text-foreground/80 data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground"
|
||||||
@click="handleItemClick(menu.value)"
|
@click="handleItemClick(menu.value)"
|
||||||
>
|
>
|
||||||
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
<component :is="menu.icon" v-if="menu.icon" class="mr-2 size-4" />
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ isFullscreen.value = !!(
|
|||||||
class="hover:animate-[shrink_0.3s_ease-in-out]"
|
class="hover:animate-[shrink_0.3s_ease-in-out]"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
<Minimize v-if="isFullscreen" class="text-foreground size-4" />
|
<Minimize v-if="isFullscreen" class="size-4 text-foreground" />
|
||||||
<Maximize v-else class="text-foreground size-4" />
|
<Maximize v-else class="size-4 text-foreground" />
|
||||||
</VbenIconButton>
|
</VbenIconButton>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const show = ref(false);
|
|||||||
/>
|
/>
|
||||||
<template v-if="passwordStrength">
|
<template v-if="passwordStrength">
|
||||||
<PasswordStrength :password="modelValue" />
|
<PasswordStrength :password="modelValue" />
|
||||||
<p v-if="slots.strengthText" class="text-muted-foreground mt-1.5 text-xs">
|
<p v-if="slots.strengthText" class="mt-1.5 text-xs text-muted-foreground">
|
||||||
<slot name="strengthText"> </slot>
|
<slot name="strengthText"> </slot>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,7 +47,7 @@ const show = ref(false);
|
|||||||
'top-3': !!passwordStrength,
|
'top-3': !!passwordStrength,
|
||||||
'top-1/2 -translate-y-1/2 items-center': !passwordStrength,
|
'top-1/2 -translate-y-1/2 items-center': !passwordStrength,
|
||||||
}"
|
}"
|
||||||
class="hover:text-foreground text-foreground/60 absolute inset-y-0 right-0 flex cursor-pointer pr-3 text-lg leading-5"
|
class="absolute inset-y-0 right-0 flex cursor-pointer pr-3 text-lg leading-5 text-foreground/60 hover:text-foreground"
|
||||||
@click="show = !show"
|
@click="show = !show"
|
||||||
>
|
>
|
||||||
<Eye v-if="show" class="size-4" />
|
<Eye v-if="show" class="size-4" />
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function checkPasswordStrength(password: string) {
|
|||||||
<div class="relative mt-2 flex items-center justify-between">
|
<div class="relative mt-2 flex items-center justify-between">
|
||||||
<template v-for="index in 5" :key="index">
|
<template v-for="index in 5" :key="index">
|
||||||
<div
|
<div
|
||||||
class="dark:bg-input-background bg-heavy relative mr-1 h-1.5 w-1/5 rounded-sm last:mr-0"
|
class="relative mr-1 h-1.5 w-1/5 rounded-sm bg-heavy last:mr-0 dark:bg-input-background"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:style="{
|
:style="{
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ const logoSrc = computed(() => {
|
|||||||
/>
|
/>
|
||||||
<template v-if="!collapsed">
|
<template v-if="!collapsed">
|
||||||
<slot name="text">
|
<slot name="text">
|
||||||
<span class="text-foreground truncate text-nowrap font-semibold">
|
<span class="truncate text-nowrap font-semibold text-foreground">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ function handleScroll(event: Event) {
|
|||||||
v-if="showShadowTop"
|
v-if="showShadowTop"
|
||||||
:class="{
|
:class="{
|
||||||
'opacity-100': !isAtTop,
|
'opacity-100': !isAtTop,
|
||||||
'border-border border-t': shadowBorder && !isAtTop,
|
'border-t border-border': shadowBorder && !isAtTop,
|
||||||
}"
|
}"
|
||||||
class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
class="scrollbar-top-shadow pointer-events-none absolute top-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
||||||
></div>
|
></div>
|
||||||
@@ -109,7 +109,7 @@ function handleScroll(event: Event) {
|
|||||||
v-if="showShadowBottom"
|
v-if="showShadowBottom"
|
||||||
:class="{
|
:class="{
|
||||||
'opacity-100': !isAtTop && !isAtBottom,
|
'opacity-100': !isAtTop && !isAtBottom,
|
||||||
'border-border border-b': shadowBorder && !isAtTop && !isAtBottom,
|
'border-b border-border': shadowBorder && !isAtTop && !isAtBottom,
|
||||||
}"
|
}"
|
||||||
class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
class="scrollbar-bottom-shadow pointer-events-none absolute bottom-0 z-10 h-12 w-full opacity-0 transition-opacity duration-300 ease-in-out will-change-[opacity]"
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@@ -45,14 +45,14 @@ function activeClass(tab: string): string[] {
|
|||||||
<Tabs v-model="activeTab" :default-value="getDefaultValue">
|
<Tabs v-model="activeTab" :default-value="getDefaultValue">
|
||||||
<TabsList
|
<TabsList
|
||||||
:style="tabsStyle"
|
:style="tabsStyle"
|
||||||
class="bg-accent !outline-heavy relative grid w-full !outline !outline-2"
|
class="relative grid w-full bg-accent !outline !outline-2 !outline-heavy"
|
||||||
>
|
>
|
||||||
<TabsIndicator :style="tabsIndicatorStyle" />
|
<TabsIndicator :style="tabsIndicatorStyle" />
|
||||||
<template v-for="tab in tabs" :key="tab.value">
|
<template v-for="tab in tabs" :key="tab.value">
|
||||||
<TabsTrigger
|
<TabsTrigger
|
||||||
:value="tab.value"
|
:value="tab.value"
|
||||||
:class="activeClass(tab.value)"
|
:class="activeClass(tab.value)"
|
||||||
class="hover:text-primary z-20 inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium disabled:pointer-events-none disabled:opacity-50"
|
class="z-20 inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium hover:text-primary disabled:pointer-events-none disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{{ tab.label }}
|
{{ tab.label }}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="bg-background text-foreground inline-flex h-full w-full items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
class="inline-flex h-full w-full items-center justify-center whitespace-nowrap rounded-md bg-background px-3 py-1 text-sm font-medium text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function onTransitionEnd() {
|
|||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'z-100 dark:bg-overlay bg-overlay-content absolute left-0 top-0 flex size-full flex-col items-center justify-center transition-all duration-500',
|
'absolute left-0 top-0 z-100 flex size-full flex-col items-center justify-center bg-overlay-content transition-all duration-500 dark:bg-overlay',
|
||||||
{
|
{
|
||||||
'invisible opacity-0': !showSpinner,
|
'invisible opacity-0': !showSpinner,
|
||||||
},
|
},
|
||||||
@@ -83,12 +83,12 @@ function onTransitionEnd() {
|
|||||||
<i
|
<i
|
||||||
v-for="index in 4"
|
v-for="index in 4"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="bg-primary absolute block size-4 origin-[50%_50%] scale-75 rounded-full opacity-30"
|
class="absolute block size-4 origin-[50%_50%] scale-75 rounded-full bg-primary opacity-30"
|
||||||
></i>
|
></i>
|
||||||
</span>
|
</span>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<div v-if="text" class="text-primary mt-4 text-xs">{{ text }}</div>
|
<div v-if="text" class="mt-4 text-xs text-primary">{{ text }}</div>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function onTransitionEnd() {
|
|||||||
<div
|
<div
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'flex-center z-100 bg-overlay-content absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500',
|
'flex-center absolute left-0 top-0 z-100 size-full bg-overlay-content backdrop-blur-sm transition-all duration-500',
|
||||||
{
|
{
|
||||||
'invisible opacity-0': !showSpinner,
|
'invisible opacity-0': !showSpinner,
|
||||||
},
|
},
|
||||||
@@ -75,7 +75,7 @@ function onTransitionEnd() {
|
|||||||
<div
|
<div
|
||||||
:class="{ paused: !renderSpinner }"
|
:class="{ paused: !renderSpinner }"
|
||||||
v-if="renderSpinner"
|
v-if="renderSpinner"
|
||||||
class="loader before:bg-primary/50 after:bg-primary relative size-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:rounded-[50%] before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:rounded after:content-['']"
|
class="loader relative size-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:rounded-[50%] before:bg-primary/50 before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:rounded after:bg-primary after:content-['']"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ defineProps<{ triggerClass?: string }>();
|
|||||||
<CircleHelp
|
<CircleHelp
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'text-foreground/80 hover:text-foreground inline-flex size-5 cursor-pointer',
|
'inline-flex size-5 cursor-pointer text-foreground/80 hover:text-foreground',
|
||||||
triggerClass,
|
triggerClass,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
:class="contentClass"
|
:class="contentClass"
|
||||||
:side="side"
|
:side="side"
|
||||||
:style="contentStyle"
|
:style="contentStyle"
|
||||||
class="side-content text-popover-foreground bg-accent rounded-md"
|
class="side-content rounded-md bg-accent text-popover-foreground"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const delegatedProps = computed(() => {
|
|||||||
<template>
|
<template>
|
||||||
<AccordionContent
|
<AccordionContent
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
class="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
class="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
>
|
>
|
||||||
<div :class="cn('pb-4 pt-0', props.class)">
|
<div :class="cn('pb-4 pt-0', props.class)">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const delegatedProps = computed(() => {
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
<slot name="icon">
|
<slot name="icon">
|
||||||
<ChevronDown
|
<ChevronDown
|
||||||
class="text-muted-foreground h-4 w-4 shrink-0 transition-transform duration-200"
|
class="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"
|
||||||
/>
|
/>
|
||||||
</slot>
|
</slot>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ defineExpose({
|
|||||||
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||||
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
||||||
{
|
{
|
||||||
'data-[state=open]:slide-in-from-top-[48%] data-[state=closed]:slide-out-to-top-[48%]':
|
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||||
!centered,
|
!centered,
|
||||||
'data-[state=open]:slide-in-from-top-[98%] data-[state=closed]:slide-out-to-top-[148%]':
|
'data-[state=closed]:slide-out-to-top-[148%] data-[state=open]:slide-in-from-top-[98%]':
|
||||||
centered,
|
centered,
|
||||||
'top-[10vh]': !centered,
|
'top-[10vh]': !centered,
|
||||||
'top-1/2 -translate-y-1/2': centered,
|
'top-1/2 -translate-y-1/2': centered,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
|||||||
<template>
|
<template>
|
||||||
<AlertDialogDescription
|
<AlertDialogDescription
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
:class="cn('text-sm text-muted-foreground', props.class)"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { useScrollLock } from '@vben-core/composables';
|
|||||||
useScrollLock();
|
useScrollLock();
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-overlay z-popup inset-0"></div>
|
<div class="z-popup inset-0 bg-overlay"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user