13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@@ -223,5 +223,16 @@
|
|||||||
"commentTranslate.multiLineMerge": true,
|
"commentTranslate.multiLineMerge": true,
|
||||||
"vue.server.hybridMode": true,
|
"vue.server.hybridMode": true,
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"oxc.enable": false
|
"oxc.enable": false,
|
||||||
|
"cSpell.words": [
|
||||||
|
"archiver",
|
||||||
|
"axios",
|
||||||
|
"dotenv",
|
||||||
|
"isequal",
|
||||||
|
"jspm",
|
||||||
|
"napi",
|
||||||
|
"nolebase",
|
||||||
|
"rollup",
|
||||||
|
"vitest"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,35 +8,64 @@ import type { Component } from 'vue';
|
|||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import { notification } from 'ant-design-vue';
|
||||||
AutoComplete,
|
|
||||||
Button,
|
const AutoComplete = defineAsyncComponent(
|
||||||
Checkbox,
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
CheckboxGroup,
|
);
|
||||||
DatePicker,
|
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||||
Divider,
|
const Checkbox = defineAsyncComponent(
|
||||||
Input,
|
() => import('ant-design-vue/es/checkbox'),
|
||||||
InputNumber,
|
);
|
||||||
InputPassword,
|
const CheckboxGroup = defineAsyncComponent(() =>
|
||||||
Mentions,
|
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||||
notification,
|
);
|
||||||
Radio,
|
const DatePicker = defineAsyncComponent(
|
||||||
RadioGroup,
|
() => import('ant-design-vue/es/date-picker'),
|
||||||
RangePicker,
|
);
|
||||||
Rate,
|
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||||
Select,
|
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||||
Space,
|
const InputNumber = defineAsyncComponent(
|
||||||
Switch,
|
() => import('ant-design-vue/es/input-number'),
|
||||||
Textarea,
|
);
|
||||||
TimePicker,
|
const InputPassword = defineAsyncComponent(() =>
|
||||||
TreeSelect,
|
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||||
Upload,
|
);
|
||||||
} from 'ant-design-vue';
|
const Mentions = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/mentions'),
|
||||||
|
);
|
||||||
|
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||||
|
const RadioGroup = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||||
|
);
|
||||||
|
const RangePicker = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||||
|
);
|
||||||
|
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||||
|
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||||
|
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||||
|
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||||
|
const Textarea = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||||
|
);
|
||||||
|
const TimePicker = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/time-picker'),
|
||||||
|
);
|
||||||
|
const TreeSelect = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/tree-select'),
|
||||||
|
);
|
||||||
|
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
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';
|
||||||
@@ -47,12 +46,14 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
46
apps/web-antd/src/utils/TimeUtils.ts
Normal file
46
apps/web-antd/src/utils/TimeUtils.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
/** 时间段选择器拓展 */
|
||||||
|
export const rangePickerExtend = () => {
|
||||||
|
return {
|
||||||
|
showTime: {
|
||||||
|
format: 'HH:mm:ss',
|
||||||
|
defaultValue: [
|
||||||
|
dayjs('00:00:00', 'HH:mm:ss'),
|
||||||
|
dayjs('23:59:59', 'HH:mm:ss'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 如果需要10位时间戳(秒级)可以使用 valueFormat: 'X'
|
||||||
|
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss', // 显示格式
|
||||||
|
placeholder: ['开始时间', '结束时间'],
|
||||||
|
ranges: {
|
||||||
|
今天: [dayjs().startOf('day'), dayjs().endOf('day')],
|
||||||
|
|
||||||
|
昨天: [
|
||||||
|
dayjs().subtract(1, 'day').startOf('day'),
|
||||||
|
dayjs().subtract(1, 'day').endOf('day'),
|
||||||
|
],
|
||||||
|
|
||||||
|
本周: [dayjs().startOf('week'), dayjs().endOf('day')],
|
||||||
|
|
||||||
|
本月: [dayjs().startOf('month'), dayjs().endOf('day')],
|
||||||
|
|
||||||
|
最近7天: [
|
||||||
|
dayjs().subtract(7, 'day').startOf('day'),
|
||||||
|
dayjs().endOf('day'),
|
||||||
|
],
|
||||||
|
|
||||||
|
最近30天: [
|
||||||
|
dayjs().subtract(30, 'day').startOf('day'),
|
||||||
|
dayjs().endOf('day'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
transformDateFunc: (dates: any) => {
|
||||||
|
if (dates && dates.length === 2) {
|
||||||
|
return [dates.createTime[0], dates.createTime[1]].join(','); // 格式化为后台支持的时间格式
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,31 +8,121 @@ import type { Component } from 'vue';
|
|||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import { ElNotification } from 'element-plus';
|
||||||
ElButton,
|
|
||||||
ElCheckbox,
|
const ElButton = defineAsyncComponent(() =>
|
||||||
ElCheckboxButton,
|
Promise.all([
|
||||||
ElCheckboxGroup,
|
import('element-plus/es/components/button/index'),
|
||||||
ElDatePicker,
|
import('element-plus/es/components/button/style/css'),
|
||||||
ElDivider,
|
]).then(([res]) => res.ElButton),
|
||||||
ElInput,
|
);
|
||||||
ElInputNumber,
|
const ElCheckbox = defineAsyncComponent(() =>
|
||||||
ElNotification,
|
Promise.all([
|
||||||
ElRadio,
|
import('element-plus/es/components/checkbox/index'),
|
||||||
ElRadioButton,
|
import('element-plus/es/components/checkbox/style/css'),
|
||||||
ElRadioGroup,
|
]).then(([res]) => res.ElCheckbox),
|
||||||
ElSelectV2,
|
);
|
||||||
ElSpace,
|
const ElCheckboxButton = defineAsyncComponent(() =>
|
||||||
ElSwitch,
|
Promise.all([
|
||||||
ElTimePicker,
|
import('element-plus/es/components/checkbox/index'),
|
||||||
ElTreeSelect,
|
import('element-plus/es/components/checkbox-button/style/css'),
|
||||||
ElUpload,
|
]).then(([res]) => res.ElCheckboxButton),
|
||||||
} from 'element-plus';
|
);
|
||||||
|
const ElCheckboxGroup = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/checkbox/index'),
|
||||||
|
import('element-plus/es/components/checkbox-group/style/css'),
|
||||||
|
]).then(([res]) => res.ElCheckboxGroup),
|
||||||
|
);
|
||||||
|
const ElDatePicker = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/date-picker/index'),
|
||||||
|
import('element-plus/es/components/date-picker/style/css'),
|
||||||
|
]).then(([res]) => res.ElDatePicker),
|
||||||
|
);
|
||||||
|
const ElDivider = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/divider/index'),
|
||||||
|
import('element-plus/es/components/divider/style/css'),
|
||||||
|
]).then(([res]) => res.ElDivider),
|
||||||
|
);
|
||||||
|
const ElInput = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/input/index'),
|
||||||
|
import('element-plus/es/components/input/style/css'),
|
||||||
|
]).then(([res]) => res.ElInput),
|
||||||
|
);
|
||||||
|
const ElInputNumber = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/input-number/index'),
|
||||||
|
import('element-plus/es/components/input-number/style/css'),
|
||||||
|
]).then(([res]) => res.ElInputNumber),
|
||||||
|
);
|
||||||
|
const ElRadio = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/radio/index'),
|
||||||
|
import('element-plus/es/components/radio/style/css'),
|
||||||
|
]).then(([res]) => res.ElRadio),
|
||||||
|
);
|
||||||
|
const ElRadioButton = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/radio/index'),
|
||||||
|
import('element-plus/es/components/radio-button/style/css'),
|
||||||
|
]).then(([res]) => res.ElRadioButton),
|
||||||
|
);
|
||||||
|
const ElRadioGroup = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/radio/index'),
|
||||||
|
import('element-plus/es/components/radio-group/style/css'),
|
||||||
|
]).then(([res]) => res.ElRadioGroup),
|
||||||
|
);
|
||||||
|
const ElSelectV2 = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/select-v2/index'),
|
||||||
|
import('element-plus/es/components/select-v2/style/css'),
|
||||||
|
]).then(([res]) => res.ElSelectV2),
|
||||||
|
);
|
||||||
|
const ElSpace = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/space/index'),
|
||||||
|
import('element-plus/es/components/space/style/css'),
|
||||||
|
]).then(([res]) => res.ElSpace),
|
||||||
|
);
|
||||||
|
const ElSwitch = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/switch/index'),
|
||||||
|
import('element-plus/es/components/switch/style/css'),
|
||||||
|
]).then(([res]) => res.ElSwitch),
|
||||||
|
);
|
||||||
|
const ElTimePicker = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/time-picker/index'),
|
||||||
|
import('element-plus/es/components/time-picker/style/css'),
|
||||||
|
]).then(([res]) => res.ElTimePicker),
|
||||||
|
);
|
||||||
|
const ElTreeSelect = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/tree-select/index'),
|
||||||
|
import('element-plus/es/components/tree-select/style/css'),
|
||||||
|
]).then(([res]) => res.ElTreeSelect),
|
||||||
|
);
|
||||||
|
const ElUpload = defineAsyncComponent(() =>
|
||||||
|
Promise.all([
|
||||||
|
import('element-plus/es/components/upload/index'),
|
||||||
|
import('element-plus/es/components/upload/style/css'),
|
||||||
|
]).then(([res]) => res.ElUpload),
|
||||||
|
);
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
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';
|
||||||
@@ -49,12 +48,14 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||||
|
|
||||||
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
@@ -17,11 +17,16 @@ const [Form, formApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
layout: 'horizontal',
|
layout: 'horizontal',
|
||||||
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
// wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
handleSubmit: (values) => {
|
handleSubmit: (values) => {
|
||||||
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
|
{
|
||||||
|
component: 'IconPicker',
|
||||||
|
fieldName: 'icon',
|
||||||
|
label: 'IconPicker',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 组件需要在 #/adapter.ts内注册,并加上类型
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
component: 'ApiSelect',
|
component: 'ApiSelect',
|
||||||
@@ -149,6 +154,8 @@ const [Form, formApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer();
|
||||||
function setFormValues() {
|
function setFormValues() {
|
||||||
formApi.setValues({
|
formApi.setValues({
|
||||||
string: 'string',
|
string: 'string',
|
||||||
@@ -168,6 +175,9 @@ function setFormValues() {
|
|||||||
description="我们重新包装了CheckboxGroup、RadioGroup、Select,可以通过options属性传入选项属性数组以自动生成选项"
|
description="我们重新包装了CheckboxGroup、RadioGroup、Select,可以通过options属性传入选项属性数组以自动生成选项"
|
||||||
title="表单演示"
|
title="表单演示"
|
||||||
>
|
>
|
||||||
|
<Drawer class="w-[600px]" title="基础表单示例">
|
||||||
|
<Form />
|
||||||
|
</Drawer>
|
||||||
<ElCard>
|
<ElCard>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -175,7 +185,7 @@ function setFormValues() {
|
|||||||
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
|
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<Form />
|
<ElButton type="primary" @click="drawerApi.open"> 打开抽屉 </ElButton>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,32 +8,68 @@ import type { Component } from 'vue';
|
|||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
|
||||||
NButton,
|
|
||||||
NCheckbox,
|
|
||||||
NCheckboxGroup,
|
|
||||||
NDatePicker,
|
|
||||||
NDivider,
|
|
||||||
NInput,
|
|
||||||
NInputNumber,
|
|
||||||
NRadio,
|
|
||||||
NRadioButton,
|
|
||||||
NRadioGroup,
|
|
||||||
NSelect,
|
|
||||||
NSpace,
|
|
||||||
NSwitch,
|
|
||||||
NTimePicker,
|
|
||||||
NTreeSelect,
|
|
||||||
NUpload,
|
|
||||||
} from 'naive-ui';
|
|
||||||
|
|
||||||
import { message } from '#/adapter/naive';
|
import { message } from '#/adapter/naive';
|
||||||
|
|
||||||
|
const NButton = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/button').then((res) => res.NButton),
|
||||||
|
);
|
||||||
|
const NCheckbox = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/checkbox').then((res) => res.NCheckbox),
|
||||||
|
);
|
||||||
|
const NCheckboxGroup = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/checkbox').then((res) => res.NCheckboxGroup),
|
||||||
|
);
|
||||||
|
const NDatePicker = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/date-picker').then((res) => res.NDatePicker),
|
||||||
|
);
|
||||||
|
const NDivider = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/divider').then((res) => res.NDivider),
|
||||||
|
);
|
||||||
|
const NInput = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/input').then((res) => res.NInput),
|
||||||
|
);
|
||||||
|
const NInputNumber = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/input-number').then((res) => res.NInputNumber),
|
||||||
|
);
|
||||||
|
const NRadio = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/radio').then((res) => res.NRadio),
|
||||||
|
);
|
||||||
|
const NRadioButton = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/radio').then((res) => res.NRadioButton),
|
||||||
|
);
|
||||||
|
const NRadioGroup = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/radio').then((res) => res.NRadioGroup),
|
||||||
|
);
|
||||||
|
const NSelect = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/select').then((res) => res.NSelect),
|
||||||
|
);
|
||||||
|
const NSpace = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/space').then((res) => res.NSpace),
|
||||||
|
);
|
||||||
|
const NSwitch = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/switch').then((res) => res.NSwitch),
|
||||||
|
);
|
||||||
|
const NTimePicker = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/time-picker').then((res) => res.NTimePicker),
|
||||||
|
);
|
||||||
|
const NTreeSelect = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/tree-select').then((res) => res.NTreeSelect),
|
||||||
|
);
|
||||||
|
const NUpload = defineAsyncComponent(() =>
|
||||||
|
import('naive-ui/es/upload').then((res) => res.NUpload),
|
||||||
|
);
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
type: 'input' | 'select',
|
type: 'input' | 'select',
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
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';
|
||||||
@@ -47,12 +46,14 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -104,6 +104,11 @@
|
|||||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modal zIndex
|
||||||
|
*/
|
||||||
|
--popup-z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export type BeforeCloseScope = {
|
|||||||
isConfirm: boolean;
|
isConfirm: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alert 属性
|
||||||
|
*/
|
||||||
export type AlertProps = {
|
export type AlertProps = {
|
||||||
/** 关闭前的回调,如果返回false,则终止关闭 */
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
beforeClose?: (
|
beforeClose?: (
|
||||||
@@ -50,6 +53,8 @@ export type AlertProps = {
|
|||||||
) => boolean | Promise<boolean | undefined> | undefined;
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
/** 边框 */
|
/** 边框 */
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
/** 按钮对齐方式 */
|
||||||
|
buttonAlign?: 'center' | 'end' | 'start';
|
||||||
/** 取消按钮的标题 */
|
/** 取消按钮的标题 */
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
/** 是否居中显示 */
|
/** 是否居中显示 */
|
||||||
@@ -62,14 +67,41 @@ export type AlertProps = {
|
|||||||
content: Component | string;
|
content: Component | string;
|
||||||
/** 弹窗内容的额外样式 */
|
/** 弹窗内容的额外样式 */
|
||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
|
contentMasking?: boolean;
|
||||||
|
/** 弹窗底部内容(与按钮在同一个容器中) */
|
||||||
|
footer?: Component | string;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
|
/**
|
||||||
|
* 弹窗遮罩模糊效果
|
||||||
|
*/
|
||||||
|
overlayBlur?: number;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
showCancel?: boolean;
|
showCancel?: boolean;
|
||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** prompt 属性 */
|
||||||
|
export type PromptProps<T = any> = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (scope: {
|
||||||
|
isConfirm: boolean;
|
||||||
|
value: T | undefined;
|
||||||
|
}) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 用于接受用户输入的组件 */
|
||||||
|
component?: Component;
|
||||||
|
/** 输入组件的属性 */
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
/** 输入组件的插槽 */
|
||||||
|
componentSlots?: Recordable<Component>;
|
||||||
|
/** 默认值 */
|
||||||
|
defaultValue?: T;
|
||||||
|
/** 输入组件的值属性名 */
|
||||||
|
modelPropName?: string;
|
||||||
|
} & Omit<AlertProps, 'beforeClose'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 函数签名
|
* 函数签名
|
||||||
* alert和confirm的函数签名相同。
|
* alert和confirm的函数签名相同。
|
||||||
|
|||||||
@@ -131,26 +131,37 @@ function fetchApi(): Promise<Record<string, any>> {
|
|||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| modelValue(v-model) | 当前值 | `any` | - |
|
| modelValue(v-model) | 当前值 | `any` | - | - |
|
||||||
| component | 欲包装的组件(以下称为目标组件) | `Component` | - |
|
| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - |
|
||||||
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
|
| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |
|
||||||
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
|
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - | - |
|
||||||
| params | 传递给api的参数 | `Record<string, any>` | - |
|
| params | 传递给api的参数 | `Record<string, any>` | - | - |
|
||||||
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
|
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |
|
||||||
| labelField | label字段名 | `string` | `label` |
|
| labelField | label字段名 | `string` | `label` | - |
|
||||||
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
|
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - |
|
||||||
| valueField | value字段名 | `string` | `value` |
|
| valueField | value字段名 | `string` | `value` | - |
|
||||||
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` |
|
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |
|
||||||
| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` |
|
| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |
|
||||||
| immediate | 是否立即调用api | `boolean` | `true` |
|
| immediate | 是否立即调用api | `boolean` | `true` | - |
|
||||||
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
|
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |
|
||||||
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||||
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||||
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
|
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |
|
||||||
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
|
| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |
|
||||||
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
|
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - |
|
||||||
|
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| (item: OptionsItem[]) => OptionsItem \| false` | `false` | >5.5.4 |
|
||||||
|
|
||||||
|
#### autoSelect 自动设置选项
|
||||||
|
|
||||||
|
如果当前值为undefined,在选项数据成功加载之后,自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
|
||||||
|
|
||||||
|
- `first`:自动选择第一个选项
|
||||||
|
- `last`:自动选择最后一个选项
|
||||||
|
- `one`:有且仅有一个选项时,自动选择它
|
||||||
|
- `函数`:自定义选择逻辑,函数的参数为options,返回值为选择的选项
|
||||||
|
- false:不自动选择选项
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
|
|||||||
|
|
||||||
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||||
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
|
||||||
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@@ -84,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||||
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
|
|||||||
@@ -167,6 +167,23 @@ vxeUI.renderer.add('CellLink', {
|
|||||||
|
|
||||||
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
|
当启用了表单搜索时,可以在toolbarConfig中配置`search`为`true`来让表格在工具栏区域显示一个搜索表单控制按钮。表格的所有以`form-`开头的命名插槽都会被传递给搜索表单。
|
||||||
|
|
||||||
|
### 定制分隔条
|
||||||
|
|
||||||
|
当你启用表单搜索时,在表单和表格之间会显示一个分隔条。这个分隔条使用了默认的组件背景色,并且横向贯穿整个Vben Vxe Table在视觉上融入了页面的默认背景中。如果你在Vben Vxe Table的外层包裹了一个不同背景色的容器(如将其放在一个Card内),默认的表单和表格之间的分隔条可能就显得格格不入了,下面的代码演示了如何定制这个分隔条。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const [Grid] = useVbenVxeGrid({
|
||||||
|
formOptions: {},
|
||||||
|
gridOptions: {},
|
||||||
|
// 完全移除分隔条
|
||||||
|
separator: false,
|
||||||
|
// 你也可以使用下面的代码来移除分隔条
|
||||||
|
// separator: { show: false },
|
||||||
|
// 或者使用下面的代码来改变分隔条的颜色
|
||||||
|
// separator: { backgroundColor: 'rgba(100,100,0,0.5)' },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
<DemoPreview dir="demos/vben-vxe-table/form" />
|
<DemoPreview dir="demos/vben-vxe-table/form" />
|
||||||
|
|
||||||
## 单元格编辑
|
## 单元格编辑
|
||||||
@@ -231,15 +248,16 @@ useVbenVxeGrid 返回的第二个参数,是一个对象,包含了一些表
|
|||||||
|
|
||||||
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
|
所有属性都可以传入 `useVbenVxeGrid` 的第一个参数中。
|
||||||
|
|
||||||
| 属性名 | 描述 | 类型 |
|
| 属性名 | 描述 | 类型 | 版本要求 |
|
||||||
| -------------- | -------------------- | ------------------- |
|
| --- | --- | --- | --- |
|
||||||
| tableTitle | 表格标题 | `string` |
|
| tableTitle | 表格标题 | `string` | - |
|
||||||
| tableTitleHelp | 表格标题帮助信息 | `string` |
|
| tableTitleHelp | 表格标题帮助信息 | `string` | - |
|
||||||
| gridClass | grid组件的class | `string` |
|
| gridClass | grid组件的class | `string` | - |
|
||||||
| gridOptions | grid组件的参数 | `VxeTableGridProps` |
|
| gridOptions | grid组件的参数 | `VxeTableGridProps` | - |
|
||||||
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` |
|
| gridEvents | grid组件的触发的事件 | `VxeGridListeners` | - |
|
||||||
| formOptions | 表单参数 | `VbenFormProps` |
|
| formOptions | 表单参数 | `VbenFormProps` | - |
|
||||||
| showSearchForm | 是否显示搜索表单 | `boolean` |
|
| showSearchForm | 是否显示搜索表单 | `boolean` | - |
|
||||||
|
| separator | 搜索表单与表格主体之间的分隔条 | `boolean\|SeparatorOptions` | >5.5.4 |
|
||||||
|
|
||||||
## Slots
|
## Slots
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { h } from 'vue';
|
|||||||
|
|
||||||
import { alert, VbenButton } from '@vben/common-ui';
|
import { alert, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Empty } from 'ant-design-vue';
|
import { Result } from 'ant-design-vue';
|
||||||
|
|
||||||
function showAlert() {
|
function showAlert() {
|
||||||
alert('This is an alert message');
|
alert('This is an alert message');
|
||||||
@@ -18,7 +18,12 @@ function showIconAlert() {
|
|||||||
|
|
||||||
function showCustomAlert() {
|
function showCustomAlert() {
|
||||||
alert({
|
alert({
|
||||||
content: h(Empty, { description: '什么都没有' }),
|
buttonAlign: 'center',
|
||||||
|
content: h(Result, {
|
||||||
|
status: 'success',
|
||||||
|
subTitle: '已成功创建订单。订单ID:2017182818828182881',
|
||||||
|
title: '操作成功',
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Checkbox, message } from 'ant-design-vue';
|
||||||
|
|
||||||
function showConfirm() {
|
function showConfirm() {
|
||||||
confirm('This is an alert message')
|
confirm('This is an alert message')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -18,6 +22,34 @@ function showIconConfirm() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showfooterConfirm() {
|
||||||
|
const checked = ref(false);
|
||||||
|
confirm({
|
||||||
|
cancelText: '不要虾扯蛋',
|
||||||
|
confirmText: '是的,我们都是NPC',
|
||||||
|
content:
|
||||||
|
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的,你有过这种感觉吗?',
|
||||||
|
footer: () =>
|
||||||
|
h(
|
||||||
|
Checkbox,
|
||||||
|
{
|
||||||
|
checked: checked.value,
|
||||||
|
class: 'flex-1',
|
||||||
|
'onUpdate:checked': (v) => (checked.value = v),
|
||||||
|
},
|
||||||
|
'不再提示',
|
||||||
|
),
|
||||||
|
icon: 'question',
|
||||||
|
title: '未解之谜',
|
||||||
|
}).then(() => {
|
||||||
|
if (checked.value) {
|
||||||
|
message.success('我不会再拿这个问题烦你了');
|
||||||
|
} else {
|
||||||
|
message.info('下次还要继续问你哟');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showAsyncConfirm() {
|
function showAsyncConfirm() {
|
||||||
confirm({
|
confirm({
|
||||||
beforeClose({ isConfirm }) {
|
beforeClose({ isConfirm }) {
|
||||||
@@ -37,6 +69,7 @@ function showAsyncConfirm() {
|
|||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||||
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showfooterConfirm">Confirm With Footer</VbenButton>
|
||||||
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { VbenSelect } from '@vben-core/shadcn-ui';
|
import { Input, RadioGroup, Select } from 'ant-design-vue';
|
||||||
|
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
||||||
|
|
||||||
function showPrompt() {
|
function showPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
@@ -15,27 +18,87 @@ function showPrompt() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showSlotsPrompt() {
|
||||||
|
prompt({
|
||||||
|
component: Input,
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入',
|
||||||
|
prefix: '充值金额',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
componentSlots: {
|
||||||
|
addonAfter: () => h(BadgeJapaneseYen),
|
||||||
|
},
|
||||||
|
content: '此弹窗演示了如何使用componentSlots传递自定义插槽',
|
||||||
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
|
}).then((val) => {
|
||||||
|
if (val) alert(`你输入的是${val}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showSelectPrompt() {
|
function showSelectPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
component: VbenSelect,
|
component: Select,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'Option A', value: 'Option A' },
|
||||||
|
{ label: 'Option B', value: 'Option B' },
|
||||||
|
{ label: 'Option C', value: 'Option C' },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
// 弹窗会设置body的pointer-events为none,这回影响下拉框的点击事件
|
||||||
|
popupClassName: 'pointer-events-auto',
|
||||||
|
},
|
||||||
|
content: '此弹窗演示了如何使用component传递自定义组件',
|
||||||
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
|
}).then((val) => {
|
||||||
|
if (val) {
|
||||||
|
alert(`你选择了${val}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAsyncPrompt() {
|
||||||
|
prompt({
|
||||||
|
async beforeClose(scope) {
|
||||||
|
if (scope.isConfirm) {
|
||||||
|
if (scope.value) {
|
||||||
|
// 模拟异步操作,如果不成功,可以返回false
|
||||||
|
await sleep(2000);
|
||||||
|
} else {
|
||||||
|
alert('请选择一个选项');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: RadioGroup,
|
||||||
|
componentProps: {
|
||||||
|
class: 'flex flex-col',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Option 1', value: 'option1' },
|
{ label: 'Option 1', value: 'option1' },
|
||||||
{ label: 'Option 2', value: 'option2' },
|
{ label: 'Option 2', value: 'option2' },
|
||||||
{ label: 'Option 3', value: 'option3' },
|
{ label: 'Option 3', value: 'option3' },
|
||||||
],
|
],
|
||||||
placeholder: '请选择',
|
|
||||||
},
|
},
|
||||||
content: 'This is an alert message with icon',
|
content: '选择一个选项后再点击[确认]',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
}).then((val) => {
|
}).then((val) => {
|
||||||
alert(`你选择的是${val}`);
|
alert(`${val} 已设置。`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
<VbenButton @click="showSelectPrompt">Confirm With Select</VbenButton>
|
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton>
|
||||||
|
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
||||||
|
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -198,9 +198,16 @@ class PreferenceManager {
|
|||||||
window
|
window
|
||||||
.matchMedia('(prefers-color-scheme: dark)')
|
.matchMedia('(prefers-color-scheme: dark)')
|
||||||
.addEventListener('change', ({ matches: isDark }) => {
|
.addEventListener('change', ({ matches: isDark }) => {
|
||||||
this.updatePreferences({
|
// 如果偏好设置中主题模式为auto,则跟随系统更新
|
||||||
theme: { mode: isDark ? 'dark' : 'light' },
|
if (this.state.theme.mode === 'auto') {
|
||||||
});
|
this.updatePreferences({
|
||||||
|
theme: { mode: isDark ? 'dark' : 'light' },
|
||||||
|
});
|
||||||
|
// 恢复为auto模式
|
||||||
|
this.updatePreferences({
|
||||||
|
theme: { mode: 'auto' },
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
|
|
||||||
import { useNamespace } from '@vben-core/composables';
|
import { useNamespace } from '@vben-core/composables';
|
||||||
import { Ellipsis } from '@vben-core/icons';
|
import { Ellipsis } from '@vben-core/icons';
|
||||||
import { isHttpUrl } from '@vben-core/shared/utils';
|
|
||||||
|
|
||||||
import { useResizeObserver } from '@vueuse/core';
|
import { useResizeObserver } from '@vueuse/core';
|
||||||
|
|
||||||
@@ -248,9 +247,6 @@ function handleMenuItemClick(data: MenuItemClicked) {
|
|||||||
if (!path || !parentPaths) {
|
if (!path || !parentPaths) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isHttpUrl(path)) {
|
|
||||||
activePath.value = path;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('select', path, parentPaths);
|
emit('select', path, parentPaths);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,6 +208,8 @@ onBeforeUnmount(() => {
|
|||||||
nsMenu.e('popup-container'),
|
nsMenu.e('popup-container'),
|
||||||
is(rootMenu.theme, true),
|
is(rootMenu.theme, true),
|
||||||
opened ? '' : 'hidden',
|
opened ? '' : 'hidden',
|
||||||
|
'overflow-auto',
|
||||||
|
'max-h-[calc(var(--radix-hover-card-content-available-height)-20px)]',
|
||||||
]"
|
]"
|
||||||
:content-props="contentProps"
|
:content-props="contentProps"
|
||||||
:open="true"
|
:open="true"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Component } from 'vue';
|
import type { Component, VNode } from 'vue';
|
||||||
|
|
||||||
import type { Recordable } from '@vben-core/typings';
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { AlertProps, BeforeCloseScope } from './alert';
|
import type { AlertProps, BeforeCloseScope, PromptProps } from './alert';
|
||||||
|
|
||||||
import { h, ref, render } from 'vue';
|
import { h, nextTick, ref, render } from 'vue';
|
||||||
|
|
||||||
import { useSimpleLocale } from '@vben-core/composables';
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
import { Input } from '@vben-core/shadcn-ui';
|
import { Input } from '@vben-core/shadcn-ui';
|
||||||
@@ -130,40 +130,58 @@ export function vbenConfirm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function vbenPrompt<T = any>(
|
export async function vbenPrompt<T = any>(
|
||||||
options: Omit<AlertProps, 'beforeClose'> & {
|
options: PromptProps<T>,
|
||||||
beforeClose?: (scope: {
|
|
||||||
isConfirm: boolean;
|
|
||||||
value: T | undefined;
|
|
||||||
}) => boolean | Promise<boolean | undefined> | undefined;
|
|
||||||
component?: Component;
|
|
||||||
componentProps?: Recordable<any>;
|
|
||||||
defaultValue?: T;
|
|
||||||
modelPropName?: string;
|
|
||||||
},
|
|
||||||
): Promise<T | undefined> {
|
): Promise<T | undefined> {
|
||||||
const {
|
const {
|
||||||
component: _component,
|
component: _component,
|
||||||
componentProps: _componentProps,
|
componentProps: _componentProps,
|
||||||
|
componentSlots,
|
||||||
content,
|
content,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
modelPropName: _modelPropName,
|
modelPropName: _modelPropName,
|
||||||
...delegated
|
...delegated
|
||||||
} = options;
|
} = options;
|
||||||
const contents: Component[] = [];
|
|
||||||
const modelValue = ref<T | undefined>(defaultValue);
|
const modelValue = ref<T | undefined>(defaultValue);
|
||||||
|
const inputComponentRef = ref<null | VNode>(null);
|
||||||
|
const staticContents: Component[] = [];
|
||||||
|
|
||||||
if (isString(content)) {
|
if (isString(content)) {
|
||||||
contents.push(h('span', content));
|
staticContents.push(h('span', content));
|
||||||
} else {
|
} else if (content) {
|
||||||
contents.push(content);
|
staticContents.push(content as Component);
|
||||||
}
|
}
|
||||||
const componentProps = _componentProps || {};
|
|
||||||
const modelPropName = _modelPropName || 'modelValue';
|
const modelPropName = _modelPropName || 'modelValue';
|
||||||
componentProps[modelPropName] = modelValue.value;
|
const componentProps = { ..._componentProps };
|
||||||
componentProps[`onUpdate:${modelPropName}`] = (val: any) => {
|
|
||||||
modelValue.value = val;
|
// 每次渲染时都会重新计算的内容函数
|
||||||
|
const contentRenderer = () => {
|
||||||
|
const currentProps = { ...componentProps };
|
||||||
|
|
||||||
|
// 设置当前值
|
||||||
|
currentProps[modelPropName] = modelValue.value;
|
||||||
|
|
||||||
|
// 设置更新处理函数
|
||||||
|
currentProps[`onUpdate:${modelPropName}`] = (val: T) => {
|
||||||
|
modelValue.value = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建输入组件
|
||||||
|
inputComponentRef.value = h(
|
||||||
|
_component || Input,
|
||||||
|
currentProps,
|
||||||
|
componentSlots,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 返回包含静态内容和输入组件的数组
|
||||||
|
return h(
|
||||||
|
'div',
|
||||||
|
{ class: 'flex flex-col gap-2' },
|
||||||
|
{ default: () => [...staticContents, inputComponentRef.value] },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
const componentRef = h(_component || Input, componentProps);
|
|
||||||
contents.push(componentRef);
|
|
||||||
const props: AlertProps & Recordable<any> = {
|
const props: AlertProps & Recordable<any> = {
|
||||||
...delegated,
|
...delegated,
|
||||||
async beforeClose(scope: BeforeCloseScope) {
|
async beforeClose(scope: BeforeCloseScope) {
|
||||||
@@ -174,23 +192,46 @@ export async function vbenPrompt<T = any>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: h(
|
// 使用函数形式,每次渲染都会重新计算内容
|
||||||
'div',
|
content: contentRenderer,
|
||||||
{ class: 'flex flex-col gap-2' },
|
contentMasking: true,
|
||||||
{ default: () => contents },
|
async onOpened() {
|
||||||
),
|
await nextTick();
|
||||||
onOpened() {
|
const componentRef: null | VNode = inputComponentRef.value;
|
||||||
// 组件挂载完成后,自动聚焦到输入组件
|
if (componentRef) {
|
||||||
if (
|
if (
|
||||||
componentRef.component?.exposed &&
|
componentRef.component?.exposed &&
|
||||||
isFunction(componentRef.component.exposed.focus)
|
isFunction(componentRef.component.exposed.focus)
|
||||||
) {
|
) {
|
||||||
componentRef.component.exposed.focus();
|
componentRef.component.exposed.focus();
|
||||||
} else if (componentRef.el && isFunction(componentRef.el.focus)) {
|
} else {
|
||||||
componentRef.el.focus();
|
if (componentRef.el) {
|
||||||
|
if (
|
||||||
|
isFunction(componentRef.el.focus) &&
|
||||||
|
['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(
|
||||||
|
componentRef.el.tagName,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
componentRef.el.focus();
|
||||||
|
} else if (isFunction(componentRef.el.querySelector)) {
|
||||||
|
const focusableElement = componentRef.el.querySelector(
|
||||||
|
'input, select, textarea, button',
|
||||||
|
);
|
||||||
|
if (focusableElement && isFunction(focusableElement.focus)) {
|
||||||
|
focusableElement.focus();
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
componentRef.el.nextElementSibling &&
|
||||||
|
isFunction(componentRef.el.nextElementSibling.focus)
|
||||||
|
) {
|
||||||
|
componentRef.el.nextElementSibling.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await vbenConfirm(props);
|
await vbenConfirm(props);
|
||||||
return modelValue.value;
|
return modelValue.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { Component } from 'vue';
|
import type { Component, VNode, VNodeArrayChildren } from 'vue';
|
||||||
|
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
@@ -13,6 +15,11 @@ export type AlertProps = {
|
|||||||
) => boolean | Promise<boolean | undefined> | undefined;
|
) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
/** 边框 */
|
/** 边框 */
|
||||||
bordered?: boolean;
|
bordered?: boolean;
|
||||||
|
/**
|
||||||
|
* 按钮对齐方式
|
||||||
|
* @default 'end'
|
||||||
|
*/
|
||||||
|
buttonAlign?: 'center' | 'end' | 'start';
|
||||||
/** 取消按钮的标题 */
|
/** 取消按钮的标题 */
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
/** 是否居中显示 */
|
/** 是否居中显示 */
|
||||||
@@ -25,10 +32,41 @@ export type AlertProps = {
|
|||||||
content: Component | string;
|
content: Component | string;
|
||||||
/** 弹窗内容的额外样式 */
|
/** 弹窗内容的额外样式 */
|
||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
|
contentMasking?: boolean;
|
||||||
|
/** 弹窗底部内容(与按钮在同一个容器中) */
|
||||||
|
footer?: Component | string;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
|
/**
|
||||||
|
* 弹窗遮罩模糊效果
|
||||||
|
*/
|
||||||
|
overlayBlur?: number;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
showCancel?: boolean;
|
showCancel?: boolean;
|
||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Prompt属性 */
|
||||||
|
export type PromptProps<T = any> = {
|
||||||
|
/** 关闭前的回调,如果返回false,则终止关闭 */
|
||||||
|
beforeClose?: (scope: {
|
||||||
|
isConfirm: boolean;
|
||||||
|
value: T | undefined;
|
||||||
|
}) => boolean | Promise<boolean | undefined> | undefined;
|
||||||
|
/** 用于接受用户输入的组件 */
|
||||||
|
component?: Component;
|
||||||
|
/** 输入组件的属性 */
|
||||||
|
componentProps?: Recordable<any>;
|
||||||
|
/** 输入组件的插槽 */
|
||||||
|
componentSlots?:
|
||||||
|
| (() => any)
|
||||||
|
| Recordable<unknown>
|
||||||
|
| VNode
|
||||||
|
| VNodeArrayChildren;
|
||||||
|
/** 默认值 */
|
||||||
|
defaultValue?: T;
|
||||||
|
/** 输入组件的值属性名 */
|
||||||
|
modelPropName?: string;
|
||||||
|
} & Omit<AlertProps, 'beforeClose'>;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Component } from 'vue';
|
|||||||
|
|
||||||
import type { AlertProps } from './alert';
|
import type { AlertProps } from './alert';
|
||||||
|
|
||||||
import { computed, h, nextTick, ref, watch } from 'vue';
|
import { computed, h, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
import { useSimpleLocale } from '@vben-core/composables';
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
import {
|
import {
|
||||||
@@ -30,6 +30,7 @@ import { cn } from '@vben-core/shared/utils';
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<AlertProps>(), {
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
bordered: true,
|
bordered: true,
|
||||||
|
buttonAlign: 'end',
|
||||||
centered: true,
|
centered: true,
|
||||||
containerClass: 'w-[520px]',
|
containerClass: 'w-[520px]',
|
||||||
});
|
});
|
||||||
@@ -38,14 +39,12 @@ const open = defineModel<boolean>('open', { default: false });
|
|||||||
const { $t } = useSimpleLocale();
|
const { $t } = useSimpleLocale();
|
||||||
const components = globalShareState.getComponents();
|
const components = globalShareState.getComponents();
|
||||||
const isConfirm = ref(false);
|
const isConfirm = ref(false);
|
||||||
watch(open, async (val) => {
|
|
||||||
await nextTick();
|
function onAlertClosed() {
|
||||||
if (val) {
|
emits('closed', isConfirm.value);
|
||||||
isConfirm.value = false;
|
isConfirm.value = false;
|
||||||
} else {
|
}
|
||||||
emits('closed', isConfirm.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const getIconRender = computed(() => {
|
const getIconRender = computed(() => {
|
||||||
let iconRender: Component | null = null;
|
let iconRender: Component | null = null;
|
||||||
if (props.icon) {
|
if (props.icon) {
|
||||||
@@ -99,6 +98,7 @@ function handleCancel() {
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
async function handleOpenChange(val: boolean) {
|
async function handleOpenChange(val: boolean) {
|
||||||
|
await nextTick();
|
||||||
if (!val && props.beforeClose) {
|
if (!val && props.beforeClose) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -119,15 +119,16 @@ async function handleOpenChange(val: boolean) {
|
|||||||
<AlertDialogContent
|
<AlertDialogContent
|
||||||
:open="open"
|
:open="open"
|
||||||
:centered="centered"
|
:centered="centered"
|
||||||
|
:overlay-blur="overlayBlur"
|
||||||
@opened="emits('opened')"
|
@opened="emits('opened')"
|
||||||
|
@closed="onAlertClosed"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
containerClass,
|
containerClass,
|
||||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
||||||
{
|
{
|
||||||
'border-border border': bordered,
|
'border-border border': bordered,
|
||||||
'shadow-3xl': !bordered,
|
'shadow-3xl': !bordered,
|
||||||
'top-1/2 !-translate-y-1/2': centered,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -137,7 +138,7 @@ async function handleOpenChange(val: boolean) {
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<component :is="getIconRender" class="mr-2" />
|
<component :is="getIconRender" class="mr-2" />
|
||||||
<span class="flex-auto">{{ $t(title) }}</span>
|
<span class="flex-auto">{{ $t(title) }}</span>
|
||||||
<AlertDialogCancel v-if="showCancel">
|
<AlertDialogCancel v-if="showCancel" as-child>
|
||||||
<VbenButton
|
<VbenButton
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -154,19 +155,24 @@ async function handleOpenChange(val: boolean) {
|
|||||||
<div class="m-4 mb-6 min-h-[30px]">
|
<div class="m-4 mb-6 min-h-[30px]">
|
||||||
<VbenRenderContent :content="content" render-br />
|
<VbenRenderContent :content="content" render-br />
|
||||||
</div>
|
</div>
|
||||||
<VbenLoading v-if="loading" :spinning="loading" />
|
<VbenLoading v-if="loading && contentMasking" :spinning="loading" />
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<div class="flex justify-end gap-x-2">
|
<div
|
||||||
<AlertDialogCancel v-if="showCancel" :disabled="loading">
|
class="flex items-center justify-end gap-x-2"
|
||||||
|
:class="`justify-${buttonAlign}`"
|
||||||
|
>
|
||||||
|
<VbenRenderContent :content="footer" />
|
||||||
|
<AlertDialogCancel v-if="showCancel" as-child>
|
||||||
<component
|
<component
|
||||||
:is="components.DefaultButton || VbenButton"
|
:is="components.DefaultButton || VbenButton"
|
||||||
|
:disabled="loading"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@click="handleCancel"
|
@click="handleCancel"
|
||||||
>
|
>
|
||||||
{{ cancelText || $t('cancel') }}
|
{{ cancelText || $t('cancel') }}
|
||||||
</component>
|
</component>
|
||||||
</AlertDialogCancel>
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction>
|
<AlertDialogAction as-child>
|
||||||
<component
|
<component
|
||||||
:is="components.PrimaryButton || VbenButton"
|
:is="components.PrimaryButton || VbenButton"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export class ModalApi {
|
|||||||
confirmDisabled: false,
|
confirmDisabled: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
contentClass: '',
|
contentClass: '',
|
||||||
|
destroyOnClose: true,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
footer: true,
|
footer: true,
|
||||||
footerClass: '',
|
footerClass: '',
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ export interface ModalProps {
|
|||||||
* 弹窗描述
|
* 弹窗描述
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/**
|
||||||
|
* 在关闭时销毁弹窗
|
||||||
|
*/
|
||||||
|
destroyOnClose?: boolean;
|
||||||
/**
|
/**
|
||||||
* 是否可拖拽
|
* 是否可拖拽
|
||||||
* @default false
|
* @default false
|
||||||
@@ -153,10 +157,6 @@ export interface ModalApiOptions extends ModalState {
|
|||||||
* 独立的弹窗组件
|
* 独立的弹窗组件
|
||||||
*/
|
*/
|
||||||
connectedComponent?: Component;
|
connectedComponent?: Component;
|
||||||
/**
|
|
||||||
* 在关闭时销毁弹窗。仅在使用 connectedComponent 时有效
|
|
||||||
*/
|
|
||||||
destroyOnClose?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* 关闭前的回调,返回 false 可以阻止关闭
|
* 关闭前的回调,返回 false 可以阻止关闭
|
||||||
* @returns
|
* @returns
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExtendedModalApi, ModalProps } from './modal';
|
import type { ExtendedModalApi, ModalProps } from './modal';
|
||||||
|
|
||||||
import { computed, nextTick, provide, ref, useId, watch } from 'vue';
|
import { computed, nextTick, provide, ref, unref, useId, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useIsMobile,
|
useIsMobile,
|
||||||
@@ -34,6 +34,7 @@ interface Props extends ModalProps {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
appendToMain: false,
|
appendToMain: false,
|
||||||
|
destroyOnClose: true,
|
||||||
modalApi: undefined,
|
modalApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ const {
|
|||||||
confirmText,
|
confirmText,
|
||||||
contentClass,
|
contentClass,
|
||||||
description,
|
description,
|
||||||
|
destroyOnClose,
|
||||||
draggable,
|
draggable,
|
||||||
footer: showFooter,
|
footer: showFooter,
|
||||||
footerClass,
|
footerClass,
|
||||||
@@ -100,10 +102,15 @@ const { dragging, transform } = useModalDraggable(
|
|||||||
shouldDraggable,
|
shouldDraggable,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const firstOpened = ref(false);
|
||||||
|
const isClosed = ref(false);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => state?.value?.isOpen,
|
() => state?.value?.isOpen,
|
||||||
async (v) => {
|
async (v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
|
isClosed.value = false;
|
||||||
|
if (!firstOpened.value) firstOpened.value = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (!contentRef.value) return;
|
if (!contentRef.value) return;
|
||||||
const innerContentRef = contentRef.value.getContentRef();
|
const innerContentRef = contentRef.value.getContentRef();
|
||||||
@@ -113,6 +120,7 @@ watch(
|
|||||||
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -176,6 +184,15 @@ const getAppendTo = computed(() => {
|
|||||||
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
||||||
: undefined;
|
: undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getForceMount = computed(() => {
|
||||||
|
return !unref(destroyOnClose);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClosed() {
|
||||||
|
isClosed.value = true;
|
||||||
|
props.modalApi?.onClosed();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
@@ -197,9 +214,11 @@ const getAppendTo = computed(() => {
|
|||||||
shouldFullscreen,
|
shouldFullscreen,
|
||||||
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
||||||
'duration-300': !dragging,
|
'duration-300': !dragging,
|
||||||
|
hidden: isClosed,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
:force-mount="getForceMount"
|
||||||
:modal="modal"
|
:modal="modal"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
:show-close="closable"
|
:show-close="closable"
|
||||||
@@ -207,7 +226,7 @@ const getAppendTo = computed(() => {
|
|||||||
:overlay-blur="overlayBlur"
|
:overlay-blur="overlayBlur"
|
||||||
close-class="top-3"
|
close-class="top-3"
|
||||||
@close-auto-focus="handleFocusOutside"
|
@close-auto-focus="handleFocusOutside"
|
||||||
@closed="() => modalApi?.onClosed()"
|
@closed="handleClosed"
|
||||||
:close-disabled="submitting"
|
:close-disabled="submitting"
|
||||||
@escape-key-down="escapeKeyDown"
|
@escape-key-down="escapeKeyDown"
|
||||||
@focus-outside="handleFocusOutside"
|
@focus-outside="handleFocusOutside"
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
|
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
|
||||||
|
|
||||||
import {
|
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
|
||||||
defineComponent,
|
|
||||||
h,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
provide,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import { useStore } from '@vben-core/shared/store';
|
import { useStore } from '@vben-core/shared/store';
|
||||||
|
|
||||||
@@ -32,7 +24,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
const { connectedComponent } = options;
|
const { connectedComponent } = options;
|
||||||
if (connectedComponent) {
|
if (connectedComponent) {
|
||||||
const extendedApi = reactive({});
|
const extendedApi = reactive({});
|
||||||
const isModalReady = ref(true);
|
|
||||||
const Modal = defineComponent(
|
const Modal = defineComponent(
|
||||||
(props: TParentModalProps, { attrs, slots }) => {
|
(props: TParentModalProps, { attrs, slots }) => {
|
||||||
provide(USER_MODAL_INJECT_KEY, {
|
provide(USER_MODAL_INJECT_KEY, {
|
||||||
@@ -42,11 +33,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
Object.setPrototypeOf(extendedApi, api);
|
Object.setPrototypeOf(extendedApi, api);
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
async reCreateModal() {
|
|
||||||
isModalReady.value = false;
|
|
||||||
await nextTick();
|
|
||||||
isModalReady.value = true;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
checkProps(extendedApi as ExtendedModalApi, {
|
checkProps(extendedApi as ExtendedModalApi, {
|
||||||
...props,
|
...props,
|
||||||
@@ -55,7 +41,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
});
|
});
|
||||||
return () =>
|
return () =>
|
||||||
h(
|
h(
|
||||||
isModalReady.value ? connectedComponent : 'div',
|
connectedComponent,
|
||||||
{
|
{
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
@@ -84,14 +70,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
injectData.options?.onOpenChange?.(isOpen);
|
injectData.options?.onOpenChange?.(isOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClosed = mergedOptions.onClosed;
|
|
||||||
|
|
||||||
mergedOptions.onClosed = () => {
|
|
||||||
onClosed?.();
|
|
||||||
if (mergedOptions.destroyOnClose) {
|
|
||||||
injectData.reCreateModal?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const api = new ModalApi(mergedOptions);
|
const api = new ModalApi(mergedOptions);
|
||||||
|
|
||||||
const extendedApi: ExtendedModalApi = api as never;
|
const extendedApi: ExtendedModalApi = api as never;
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import type { ValueType, VbenButtonGroupProps } from './button';
|
|||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { Circle, CircleCheckBig, LoaderCircle } from '@vben-core/icons';
|
import { Circle, CircleCheckBig, LoaderCircle } from '@vben-core/icons';
|
||||||
import { VbenRenderContent } from '@vben-core/shadcn-ui';
|
|
||||||
import { cn, isFunction } from '@vben-core/shared/utils';
|
import { cn, isFunction } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { objectOmit } from '@vueuse/core';
|
import { objectOmit } from '@vueuse/core';
|
||||||
|
|
||||||
|
import { VbenRenderContent } from '../render-content';
|
||||||
import VbenButtonGroup from './button-group.vue';
|
import VbenButtonGroup from './button-group.vue';
|
||||||
import Button from './button.vue';
|
import Button from './button.vue';
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,13 @@ withDefaults(defineProps<Props>(), {
|
|||||||
:size="logoSize"
|
:size="logoSize"
|
||||||
class="relative rounded-none bg-transparent"
|
class="relative rounded-none bg-transparent"
|
||||||
/>
|
/>
|
||||||
<span
|
<template v-if="!collapsed">
|
||||||
v-if="!collapsed"
|
<slot name="text">
|
||||||
class="text-foreground truncate text-nowrap font-semibold"
|
<span class="text-foreground truncate text-nowrap font-semibold">
|
||||||
>
|
{{ text }}
|
||||||
{{ text }}
|
</span>
|
||||||
</span>
|
</slot>
|
||||||
|
</template>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<Transition name="fade">
|
<Transition name="fade" appear>
|
||||||
<AlertDialogOverlay
|
<AlertDialogOverlay
|
||||||
v-if="open && modal"
|
v-if="open && modal"
|
||||||
:style="{
|
:style="{
|
||||||
@@ -80,7 +80,17 @@ defineExpose({
|
|||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
'z-popup bg-background w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||||
|
'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=open]:slide-in-from-top-[48%] data-[state=closed]:slide-out-to-top-[48%]':
|
||||||
|
!centered,
|
||||||
|
'data-[state=open]:slide-in-from-top-[98%] data-[state=closed]:slide-out-to-top-[148%]':
|
||||||
|
centered,
|
||||||
|
'top-[10vh]': !centered,
|
||||||
|
'top-1/2 -translate-y-1/2': centered,
|
||||||
|
},
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
".": {
|
".": {
|
||||||
"types": "./src/index.ts",
|
"types": "./src/index.ts",
|
||||||
"default": "./src/index.ts"
|
"default": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./es/tippy": {
|
||||||
|
"types": "./src/components/tippy/index.ts",
|
||||||
|
"default": "./src/components/tippy/index.ts"
|
||||||
|
},
|
||||||
|
"./es/loading": {
|
||||||
|
"types": "./src/components/loading/index.ts",
|
||||||
|
"default": "./src/components/loading/index.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -54,6 +54,20 @@ interface Props {
|
|||||||
visibleEvent?: string;
|
visibleEvent?: string;
|
||||||
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
||||||
modelPropName?: string;
|
modelPropName?: string;
|
||||||
|
/**
|
||||||
|
* 自动选择
|
||||||
|
* - `first`:自动选择第一个选项
|
||||||
|
* - `last`:自动选择最后一个选项
|
||||||
|
* - `one`: 当请求的结果只有一个选项时,自动选择该选项
|
||||||
|
* - 函数:自定义选择逻辑,函数的参数为请求的结果数组,返回值为选择的选项
|
||||||
|
* - false:不自动选择(默认)
|
||||||
|
*/
|
||||||
|
autoSelect?:
|
||||||
|
| 'first'
|
||||||
|
| 'last'
|
||||||
|
| 'one'
|
||||||
|
| ((item: OptionsItem[]) => OptionsItem)
|
||||||
|
| false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
||||||
@@ -74,6 +88,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
modelPropName: 'modelValue',
|
modelPropName: 'modelValue',
|
||||||
api: undefined,
|
api: undefined,
|
||||||
|
autoSelect: false,
|
||||||
options: () => [],
|
options: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,7 +96,7 @@ const emit = defineEmits<{
|
|||||||
optionsChange: [OptionsItem[]];
|
optionsChange: [OptionsItem[]];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modelValue = defineModel({ default: '' });
|
const modelValue = defineModel<any>({ default: undefined });
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
const innerParams = ref({});
|
const innerParams = ref({});
|
||||||
@@ -194,6 +209,35 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function emitChange() {
|
function emitChange() {
|
||||||
|
if (
|
||||||
|
modelValue.value === undefined &&
|
||||||
|
props.autoSelect &&
|
||||||
|
unref(getOptions).length > 0
|
||||||
|
) {
|
||||||
|
let firstOption;
|
||||||
|
if (isFunction(props.autoSelect)) {
|
||||||
|
firstOption = props.autoSelect(unref(getOptions));
|
||||||
|
} else {
|
||||||
|
switch (props.autoSelect) {
|
||||||
|
case 'first': {
|
||||||
|
firstOption = unref(getOptions)[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'last': {
|
||||||
|
firstOption = unref(getOptions)[unref(getOptions).length - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'one': {
|
||||||
|
if (unref(getOptions).length === 1) {
|
||||||
|
firstOption = unref(getOptions)[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstOption) modelValue.value = firstOption.value;
|
||||||
|
}
|
||||||
emit('optionsChange', unref(getOptions));
|
emit('optionsChange', unref(getOptions));
|
||||||
}
|
}
|
||||||
const componentRef = ref();
|
const componentRef = ref();
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ComponentInternalInstance } from 'vue';
|
|
||||||
|
|
||||||
import type { VerificationProps } from '../types';
|
import type { VerificationProps } from '../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
type ComponentInternalInstance,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -21,6 +20,44 @@ import { resetSize } from '../utils/util';
|
|||||||
* VerifyPoints
|
* VerifyPoints
|
||||||
* @description 点选
|
* @description 点选
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// const props = defineProps({
|
||||||
|
// barSize: {
|
||||||
|
// default() {
|
||||||
|
// return {
|
||||||
|
// height: '40px',
|
||||||
|
// width: '310px',
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
// type: Object,
|
||||||
|
// },
|
||||||
|
// captchaType: {
|
||||||
|
// default() {
|
||||||
|
// return 'VerifyPoints';
|
||||||
|
// },
|
||||||
|
// type: String,
|
||||||
|
// },
|
||||||
|
// imgSize: {
|
||||||
|
// default() {
|
||||||
|
// return {
|
||||||
|
// height: '155px',
|
||||||
|
// width: '310px',
|
||||||
|
// };
|
||||||
|
// },
|
||||||
|
// type: Object,
|
||||||
|
// },
|
||||||
|
// // 弹出式pop,固定fixed
|
||||||
|
// mode: {
|
||||||
|
// default: 'fixed',
|
||||||
|
// type: String,
|
||||||
|
// },
|
||||||
|
// // 间隔
|
||||||
|
// vSpace: {
|
||||||
|
// default: 5,
|
||||||
|
// type: Number,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'VerifyPoints',
|
name: 'VerifyPoints',
|
||||||
});
|
});
|
||||||
@@ -90,7 +127,7 @@ onMounted(() => {
|
|||||||
const canvas = ref(null);
|
const canvas = ref(null);
|
||||||
|
|
||||||
// 获取坐标
|
// 获取坐标
|
||||||
const getMousePos = function (_obj: any, e: any) {
|
const getMousePos = function (obj: any, e: any) {
|
||||||
const x = e.offsetX;
|
const x = e.offsetX;
|
||||||
const y = e.offsetY;
|
const y = e.offsetY;
|
||||||
return { x, y };
|
return { x, y };
|
||||||
@@ -153,7 +190,7 @@ function canvasClick(e: any) {
|
|||||||
if (res.repCode === '0000') {
|
if (res.repCode === '0000') {
|
||||||
barAreaColor.value = '#4cae4c';
|
barAreaColor.value = '#4cae4c';
|
||||||
barAreaBorderColor.value = '#5cb85c';
|
barAreaBorderColor.value = '#5cb85c';
|
||||||
text.value = $t('ui.captcha.sliderSuccessText');
|
text.value = $t('ui.captcha.success');
|
||||||
bindingClick.value = false;
|
bindingClick.value = false;
|
||||||
if (mode.value === 'pop') {
|
if (mode.value === 'pop') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -190,7 +227,7 @@ async function getPictrue() {
|
|||||||
backToken.value = res.data.repData.token;
|
backToken.value = res.data.repData.token;
|
||||||
secretKey.value = res.data.repData.secretKey;
|
secretKey.value = res.data.repData.secretKey;
|
||||||
poinTextList.value = res.data.repData.wordList;
|
poinTextList.value = res.data.repData.wordList;
|
||||||
text.value = `${$t('ui.captcha.clickInOrder')}【${poinTextList.value.join(',')}】`;
|
text.value = `${$t('ui.captcha.point')}【${poinTextList.value.join(',')}】`;
|
||||||
} else {
|
} else {
|
||||||
text.value = res?.data?.repMsg;
|
text.value = res?.data?.repMsg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ export * from '@vben-core/popup-ui';
|
|||||||
|
|
||||||
// 给文档用
|
// 给文档用
|
||||||
export {
|
export {
|
||||||
|
VbenAvatar,
|
||||||
VbenButton,
|
VbenButton,
|
||||||
VbenButtonGroup,
|
VbenButtonGroup,
|
||||||
VbenCheckButtonGroup,
|
VbenCheckButtonGroup,
|
||||||
VbenCountToAnimator,
|
VbenCountToAnimator,
|
||||||
|
VbenFullScreen,
|
||||||
VbenInputPassword,
|
VbenInputPassword,
|
||||||
VbenLoading,
|
VbenLoading,
|
||||||
|
VbenLogo,
|
||||||
VbenPinInput,
|
VbenPinInput,
|
||||||
VbenSpinner,
|
VbenSpinner,
|
||||||
VbenTree,
|
VbenTree,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type {
|
|||||||
RouteLocationNormalizedLoadedGeneric,
|
RouteLocationNormalizedLoadedGeneric,
|
||||||
} from 'vue-router';
|
} from 'vue-router';
|
||||||
|
|
||||||
|
import { computed } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import { preferences, usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
@@ -20,6 +21,15 @@ const { keepAlive } = usePreferences();
|
|||||||
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
const { getCachedTabs, getExcludeCachedTabs, renderRouteView } =
|
||||||
storeToRefs(tabbarStore);
|
storeToRefs(tabbarStore);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否使用动画
|
||||||
|
*/
|
||||||
|
const getEnabledTransition = computed(() => {
|
||||||
|
const { transition } = preferences;
|
||||||
|
const transitionName = transition.name;
|
||||||
|
return transitionName && transition.enable;
|
||||||
|
});
|
||||||
|
|
||||||
// 页面切换动画
|
// 页面切换动画
|
||||||
function getTransitionName(_route: RouteLocationNormalizedLoaded) {
|
function getTransitionName(_route: RouteLocationNormalizedLoaded) {
|
||||||
// 如果偏好设置未设置,则不使用动画
|
// 如果偏好设置未设置,则不使用动画
|
||||||
@@ -90,7 +100,12 @@ function transformComponent(
|
|||||||
<div class="relative h-full">
|
<div class="relative h-full">
|
||||||
<IFrameRouterView />
|
<IFrameRouterView />
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<Transition :name="getTransitionName(route)" appear mode="out-in">
|
<Transition
|
||||||
|
v-if="getEnabledTransition"
|
||||||
|
:name="getTransitionName(route)"
|
||||||
|
appear
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
<KeepAlive
|
<KeepAlive
|
||||||
v-if="keepAlive"
|
v-if="keepAlive"
|
||||||
:exclude="getExcludeCachedTabs"
|
:exclude="getExcludeCachedTabs"
|
||||||
@@ -109,6 +124,25 @@ function transformComponent(
|
|||||||
:key="route.fullPath"
|
:key="route.fullPath"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<template v-else>
|
||||||
|
<KeepAlive
|
||||||
|
v-if="keepAlive"
|
||||||
|
:exclude="getExcludeCachedTabs"
|
||||||
|
:include="getCachedTabs"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="transformComponent(Component, route)"
|
||||||
|
v-if="renderRouteView"
|
||||||
|
v-show="!route.meta.iframeSrc"
|
||||||
|
:key="route.fullPath"
|
||||||
|
/>
|
||||||
|
</KeepAlive>
|
||||||
|
<component
|
||||||
|
:is="Component"
|
||||||
|
v-else-if="renderRouteView"
|
||||||
|
:key="route.fullPath"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -228,7 +228,11 @@ const headerSlots = computed(() => {
|
|||||||
:text="preferences.app.name"
|
:text="preferences.app.name"
|
||||||
:theme="showHeaderNav ? headerTheme : theme"
|
:theme="showHeaderNav ? headerTheme : theme"
|
||||||
@click="clickLogo"
|
@click="clickLogo"
|
||||||
/>
|
>
|
||||||
|
<template v-if="$slots['logo-text']" #text>
|
||||||
|
<slot name="logo-text"></slot>
|
||||||
|
</template>
|
||||||
|
</VbenLogo>
|
||||||
</template>
|
</template>
|
||||||
<!-- 头部区域 -->
|
<!-- 头部区域 -->
|
||||||
<template #header>
|
<template #header>
|
||||||
@@ -310,7 +314,11 @@ const headerSlots = computed(() => {
|
|||||||
v-if="preferences.logo.enable"
|
v-if="preferences.logo.enable"
|
||||||
:text="preferences.app.name"
|
:text="preferences.app.name"
|
||||||
:theme="theme"
|
:theme="theme"
|
||||||
/>
|
>
|
||||||
|
<template v-if="$slots['logo-text']" #text>
|
||||||
|
<slot name="logo-text"></slot>
|
||||||
|
</template>
|
||||||
|
</VbenLogo>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #tabbar>
|
<template #tabbar>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useNavigation } from './use-navigation';
|
|||||||
|
|
||||||
function useExtraMenu(useRootMenus?: ComputedRef<MenuRecordRaw[]>) {
|
function useExtraMenu(useRootMenus?: ComputedRef<MenuRecordRaw[]>) {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const { navigation } = useNavigation();
|
const { navigation, willOpenedByWindow } = useNavigation();
|
||||||
|
|
||||||
const menus = computed(() => useRootMenus?.value ?? accessStore.accessMenus);
|
const menus = computed(() => useRootMenus?.value ?? accessStore.accessMenus);
|
||||||
|
|
||||||
@@ -33,11 +33,15 @@ function useExtraMenu(useRootMenus?: ComputedRef<MenuRecordRaw[]>) {
|
|||||||
* @param menu
|
* @param menu
|
||||||
*/
|
*/
|
||||||
const handleMixedMenuSelect = async (menu: MenuRecordRaw) => {
|
const handleMixedMenuSelect = async (menu: MenuRecordRaw) => {
|
||||||
extraMenus.value = menu?.children ?? [];
|
const _extraMenus = menu?.children ?? [];
|
||||||
extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;
|
const hasChildren = _extraMenus.length > 0;
|
||||||
const hasChildren = extraMenus.value.length > 0;
|
|
||||||
|
if (!willOpenedByWindow(menu.path)) {
|
||||||
|
extraMenus.value = _extraMenus ?? [];
|
||||||
|
extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path;
|
||||||
|
sidebarExtraVisible.value = hasChildren;
|
||||||
|
}
|
||||||
|
|
||||||
sidebarExtraVisible.value = hasChildren;
|
|
||||||
if (!hasChildren) {
|
if (!hasChildren) {
|
||||||
await navigation(menu.path);
|
await navigation(menu.path);
|
||||||
} else if (preferences.sidebar.autoActivateChild) {
|
} else if (preferences.sidebar.autoActivateChild) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { findRootMenuByPath } from '@vben/utils';
|
|||||||
import { useNavigation } from './use-navigation';
|
import { useNavigation } from './use-navigation';
|
||||||
|
|
||||||
function useMixedMenu() {
|
function useMixedMenu() {
|
||||||
const { navigation } = useNavigation();
|
const { navigation, willOpenedByWindow } = useNavigation();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const splitSideMenus = ref<MenuRecordRaw[]>([]);
|
const splitSideMenus = ref<MenuRecordRaw[]>([]);
|
||||||
@@ -89,11 +89,15 @@ function useMixedMenu() {
|
|||||||
navigation(key);
|
navigation(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootMenu = menus.value.find((item) => item.path === key);
|
const rootMenu = menus.value.find((item) => item.path === key);
|
||||||
rootMenuPath.value = rootMenu?.path ?? '';
|
const _splitSideMenus = rootMenu?.children ?? [];
|
||||||
splitSideMenus.value = rootMenu?.children ?? [];
|
|
||||||
if (splitSideMenus.value.length === 0) {
|
if (!willOpenedByWindow(key)) {
|
||||||
|
rootMenuPath.value = rootMenu?.path ?? '';
|
||||||
|
splitSideMenus.value = _splitSideMenus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_splitSideMenus.length === 0) {
|
||||||
navigation(key);
|
navigation(key);
|
||||||
} else if (rootMenu && preferences.sidebar.autoActivateChild) {
|
} else if (rootMenu && preferences.sidebar.autoActivateChild) {
|
||||||
navigation(
|
navigation(
|
||||||
|
|||||||
@@ -29,7 +29,19 @@ function useNavigation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { navigation };
|
const willOpenedByWindow = (path: string) => {
|
||||||
|
const route = routeMetaMap.get(path);
|
||||||
|
const { openInNewWindow = false } = route?.meta ?? {};
|
||||||
|
if (isHttpUrl(path)) {
|
||||||
|
return true;
|
||||||
|
} else if (openInNewWindow) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { navigation, willOpenedByWindow };
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useNavigation };
|
export { useNavigation };
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { $t } from '@vben/locales';
|
|||||||
import { BUILT_IN_THEME_PRESETS } from '@vben/preferences';
|
import { BUILT_IN_THEME_PRESETS } from '@vben/preferences';
|
||||||
import { convertToHsl, TinyColor } from '@vben/utils';
|
import { convertToHsl, TinyColor } from '@vben/utils';
|
||||||
|
|
||||||
|
import { useThrottleFn } from '@vueuse/core';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'PreferenceBuiltinTheme',
|
name: 'PreferenceBuiltinTheme',
|
||||||
});
|
});
|
||||||
@@ -19,6 +21,15 @@ const colorInput = ref();
|
|||||||
const modelValue = defineModel<BuiltinThemeType>({ default: 'default' });
|
const modelValue = defineModel<BuiltinThemeType>({ default: 'default' });
|
||||||
const themeColorPrimary = defineModel<string>('themeColorPrimary');
|
const themeColorPrimary = defineModel<string>('themeColorPrimary');
|
||||||
|
|
||||||
|
const updateThemeColorPrimary = useThrottleFn(
|
||||||
|
(value: string) => {
|
||||||
|
themeColorPrimary.value = value;
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
const inputValue = computed(() => {
|
const inputValue = computed(() => {
|
||||||
return new TinyColor(themeColorPrimary.value || '').toHexString();
|
return new TinyColor(themeColorPrimary.value || '').toHexString();
|
||||||
});
|
});
|
||||||
@@ -84,7 +95,7 @@ function handleSelect(theme: BuiltinThemePreset) {
|
|||||||
|
|
||||||
function handleInputChange(e: Event) {
|
function handleInputChange(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
themeColorPrimary.value = convertToHsl(target.value);
|
updateThemeColorPrimary(convertToHsl(target.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectColor() {
|
function selectColor() {
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
|
|||||||
initVxeTable();
|
initVxeTable();
|
||||||
useTableForm = useVbenForm;
|
useTableForm = useVbenForm;
|
||||||
|
|
||||||
const preference = usePreferences();
|
const { isDark, locale } = usePreferences();
|
||||||
|
|
||||||
const localMap = {
|
const localMap = {
|
||||||
'zh-CN': zhCN,
|
'zh-CN': zhCN,
|
||||||
@@ -114,11 +114,11 @@ export function setupVbenVxeTable(setupOptions: SetupVxeTable) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => preference.theme.value, () => preference.locale.value],
|
[() => isDark.value, () => locale.value],
|
||||||
([theme, locale]) => {
|
([isDarkValue, localeValue]) => {
|
||||||
VxeUI.setTheme(theme === 'dark' ? 'dark' : 'light');
|
VxeUI.setTheme(isDarkValue ? 'dark' : 'light');
|
||||||
VxeUI.setI18n(locale, localMap[locale]);
|
VxeUI.setI18n(localeValue, localMap[localeValue]);
|
||||||
VxeUI.setLanguage(locale);
|
VxeUI.setLanguage(localeValue);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ export interface VxeTableGridOptions<T = any> extends VxeTableGridProps<T> {
|
|||||||
toolbarConfig?: ToolbarConfigOptions;
|
toolbarConfig?: ToolbarConfigOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SeparatorOptions {
|
||||||
|
show?: boolean;
|
||||||
|
backgroundColor?: string;
|
||||||
|
}
|
||||||
export interface VxeGridProps {
|
export interface VxeGridProps {
|
||||||
/**
|
/**
|
||||||
* 标题
|
* 标题
|
||||||
@@ -64,6 +68,10 @@ export interface VxeGridProps {
|
|||||||
* 显示搜索表单
|
* 显示搜索表单
|
||||||
*/
|
*/
|
||||||
showSearchForm?: boolean;
|
showSearchForm?: boolean;
|
||||||
|
/**
|
||||||
|
* 搜索表单与表格主体之间的分隔条
|
||||||
|
*/
|
||||||
|
separator?: boolean | SeparatorOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ExtendedVxeGridApi = VxeGridApi & {
|
export type ExtendedVxeGridApi = VxeGridApi & {
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ import { usePriorityValues } from '@vben/hooks';
|
|||||||
import { EmptyIcon } from '@vben/icons';
|
import { EmptyIcon } from '@vben/icons';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
import { usePreferences } from '@vben/preferences';
|
import { usePreferences } from '@vben/preferences';
|
||||||
import { cloneDeep, cn, isEqual, mergeWithArrayOverride } from '@vben/utils';
|
import {
|
||||||
|
cloneDeep,
|
||||||
|
cn,
|
||||||
|
isBoolean,
|
||||||
|
isEqual,
|
||||||
|
mergeWithArrayOverride,
|
||||||
|
} from '@vben/utils';
|
||||||
|
|
||||||
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
|
import { VbenHelpTooltip, VbenLoading } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
@@ -67,10 +73,30 @@ const {
|
|||||||
tableTitle,
|
tableTitle,
|
||||||
tableTitleHelp,
|
tableTitleHelp,
|
||||||
showSearchForm,
|
showSearchForm,
|
||||||
|
separator,
|
||||||
} = usePriorityValues(props, state);
|
} = usePriorityValues(props, state);
|
||||||
|
|
||||||
const { isMobile } = usePreferences();
|
const { isMobile } = usePreferences();
|
||||||
|
const isSeparator = computed(() => {
|
||||||
|
if (
|
||||||
|
!formOptions.value ||
|
||||||
|
showSearchForm.value === false ||
|
||||||
|
separator.value === false
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (separator.value === true || separator.value === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return separator.value.show !== false;
|
||||||
|
});
|
||||||
|
const separatorBg = computed(() => {
|
||||||
|
return !separator.value ||
|
||||||
|
isBoolean(separator.value) ||
|
||||||
|
!separator.value.backgroundColor
|
||||||
|
? undefined
|
||||||
|
: separator.value.backgroundColor;
|
||||||
|
});
|
||||||
const slots: SetupContext['slots'] = useSlots();
|
const slots: SetupContext['slots'] = useSlots();
|
||||||
|
|
||||||
const [Form, formApi] = useTableForm({
|
const [Form, formApi] = useTableForm({
|
||||||
@@ -375,7 +401,18 @@ onUnmounted(() => {
|
|||||||
<div
|
<div
|
||||||
v-if="formOptions"
|
v-if="formOptions"
|
||||||
v-show="showSearchForm !== false"
|
v-show="showSearchForm !== false"
|
||||||
:class="cn('relative rounded py-3', isCompactForm ? 'pb-8' : 'pb-4')"
|
:class="
|
||||||
|
cn(
|
||||||
|
'relative rounded py-3',
|
||||||
|
isCompactForm
|
||||||
|
? isSeparator
|
||||||
|
? 'pb-8'
|
||||||
|
: 'pb-4'
|
||||||
|
: isSeparator
|
||||||
|
? 'pb-4'
|
||||||
|
: 'pb-0',
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<slot name="form">
|
<slot name="form">
|
||||||
<Form>
|
<Form>
|
||||||
@@ -404,6 +441,10 @@ onUnmounted(() => {
|
|||||||
</Form>
|
</Form>
|
||||||
</slot>
|
</slot>
|
||||||
<div
|
<div
|
||||||
|
v-if="isSeparator"
|
||||||
|
:style="{
|
||||||
|
...(separatorBg ? { backgroundColor: separatorBg } : undefined),
|
||||||
|
}"
|
||||||
class="bg-background-deep z-100 absolute -left-2 bottom-1 h-2 w-[calc(100%+1rem)] overflow-hidden md:bottom-2 md:h-3"
|
class="bg-background-deep z-100 absolute -left-2 bottom-1 h-2 w-[calc(100%+1rem)] overflow-hidden md:bottom-2 md:h-3"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,35 +8,64 @@ import type { Component } from 'vue';
|
|||||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { defineComponent, getCurrentInstance, h, ref } from 'vue';
|
import {
|
||||||
|
defineAsyncComponent,
|
||||||
|
defineComponent,
|
||||||
|
getCurrentInstance,
|
||||||
|
h,
|
||||||
|
ref,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import {
|
import { notification } from 'ant-design-vue';
|
||||||
AutoComplete,
|
|
||||||
Button,
|
const AutoComplete = defineAsyncComponent(
|
||||||
Checkbox,
|
() => import('ant-design-vue/es/auto-complete'),
|
||||||
CheckboxGroup,
|
);
|
||||||
DatePicker,
|
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||||
Divider,
|
const Checkbox = defineAsyncComponent(
|
||||||
Input,
|
() => import('ant-design-vue/es/checkbox'),
|
||||||
InputNumber,
|
);
|
||||||
InputPassword,
|
const CheckboxGroup = defineAsyncComponent(() =>
|
||||||
Mentions,
|
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||||
notification,
|
);
|
||||||
Radio,
|
const DatePicker = defineAsyncComponent(
|
||||||
RadioGroup,
|
() => import('ant-design-vue/es/date-picker'),
|
||||||
RangePicker,
|
);
|
||||||
Rate,
|
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||||
Select,
|
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||||
Space,
|
const InputNumber = defineAsyncComponent(
|
||||||
Switch,
|
() => import('ant-design-vue/es/input-number'),
|
||||||
Textarea,
|
);
|
||||||
TimePicker,
|
const InputPassword = defineAsyncComponent(() =>
|
||||||
TreeSelect,
|
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||||
Upload,
|
);
|
||||||
} from 'ant-design-vue';
|
const Mentions = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/mentions'),
|
||||||
|
);
|
||||||
|
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||||
|
const RadioGroup = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||||
|
);
|
||||||
|
const RangePicker = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||||
|
);
|
||||||
|
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||||
|
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||||
|
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||||
|
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||||
|
const Textarea = defineAsyncComponent(() =>
|
||||||
|
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||||
|
);
|
||||||
|
const TimePicker = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/time-picker'),
|
||||||
|
);
|
||||||
|
const TreeSelect = defineAsyncComponent(
|
||||||
|
() => import('ant-design-vue/es/tree-select'),
|
||||||
|
);
|
||||||
|
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||||
|
|
||||||
const withDefaultPlaceholder = <T extends Component>(
|
const withDefaultPlaceholder = <T extends Component>(
|
||||||
component: T,
|
component: T,
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { createApp, watchEffect } from 'vue';
|
import { createApp, watchEffect } from 'vue';
|
||||||
|
|
||||||
import { registerAccessDirective } from '@vben/access';
|
import { registerAccessDirective } from '@vben/access';
|
||||||
import { initTippy, registerLoadingDirective } from '@vben/common-ui';
|
import { registerLoadingDirective } from '@vben/common-ui';
|
||||||
import { MotionPlugin } from '@vben/plugins/motion';
|
|
||||||
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 '@vben/styles/antd';
|
||||||
|
|
||||||
import { VueQueryPlugin } from '@tanstack/vue-query';
|
|
||||||
import { useTitle } from '@vueuse/core';
|
import { useTitle } from '@vueuse/core';
|
||||||
|
|
||||||
import { $t, setupI18n } from '#/locales';
|
import { $t, setupI18n } from '#/locales';
|
||||||
@@ -21,13 +19,13 @@ async function bootstrap(namespace: string) {
|
|||||||
// 初始化组件适配器
|
// 初始化组件适配器
|
||||||
await initComponentAdapter();
|
await initComponentAdapter();
|
||||||
|
|
||||||
// // 设置弹窗的默认配置
|
// 设置弹窗的默认配置
|
||||||
// setDefaultModalProps({
|
// setDefaultModalProps({
|
||||||
// fullscreenButton: false,
|
// fullscreenButton: false,
|
||||||
// });
|
// });
|
||||||
// // 设置抽屉的默认配置
|
// 设置抽屉的默认配置
|
||||||
// setDefaultDrawerProps({
|
// setDefaultDrawerProps({
|
||||||
// // zIndex: 1020,
|
// zIndex: 1020,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
@@ -48,15 +46,18 @@ async function bootstrap(namespace: string) {
|
|||||||
registerAccessDirective(app);
|
registerAccessDirective(app);
|
||||||
|
|
||||||
// 初始化 tippy
|
// 初始化 tippy
|
||||||
|
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||||
initTippy(app);
|
initTippy(app);
|
||||||
|
|
||||||
// 配置路由及路由守卫
|
// 配置路由及路由守卫
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// 配置@tanstack/vue-query
|
// 配置@tanstack/vue-query
|
||||||
|
const { VueQueryPlugin } = await import('@tanstack/vue-query');
|
||||||
app.use(VueQueryPlugin);
|
app.use(VueQueryPlugin);
|
||||||
|
|
||||||
// 配置Motion插件
|
// 配置Motion插件
|
||||||
|
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||||
app.use(MotionPlugin);
|
app.use(MotionPlugin);
|
||||||
|
|
||||||
// 动态更新标题
|
// 动态更新标题
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||||
|
|
||||||
import { AuthPageLayout, BasicLayout } from '#/layouts';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import Login from '#/views/_core/authentication/login.vue';
|
|
||||||
|
|
||||||
|
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||||
|
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||||
/** 全局404页面 */
|
/** 全局404页面 */
|
||||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||||
@@ -50,7 +50,7 @@ const coreRoutes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
path: 'login',
|
path: 'login',
|
||||||
component: Login,
|
component: () => import('#/views/_core/authentication/login.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: $t('page.auth.login'),
|
title: $t('page.auth.login'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
// 菜单接口
|
// 菜单接口
|
||||||
api: getAllMenusApi,
|
api: getAllMenusApi,
|
||||||
|
autoSelect: 'first',
|
||||||
},
|
},
|
||||||
// 字段名
|
// 字段名
|
||||||
fieldName: 'api',
|
fieldName: 'api',
|
||||||
|
|||||||
@@ -16,15 +16,18 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
},
|
},
|
||||||
onOpenChange(isOpen) {
|
onOpenChange(isOpen) {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
handleUpdate(10);
|
handleUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleUpdate(len: number) {
|
function handleUpdate(len?: number) {
|
||||||
modalApi.setState({ confirmDisabled: true, loading: true });
|
modalApi.setState({ confirmDisabled: true, loading: true });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
list.value = Array.from({ length: len }, (_v, k) => k + 1);
|
list.value = Array.from(
|
||||||
|
{ length: len ?? Math.floor(Math.random() * 10) + 1 },
|
||||||
|
(_v, k) => k + 1,
|
||||||
|
);
|
||||||
modalApi.setState({ confirmDisabled: false, loading: false });
|
modalApi.setState({ confirmDisabled: false, loading: false });
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
@@ -40,7 +43,7 @@ function handleUpdate(len: number) {
|
|||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
<template #prepend-footer>
|
<template #prepend-footer>
|
||||||
<Button type="link" @click="handleUpdate(6)">点击更新数据</Button>
|
<Button type="link" @click="handleUpdate()">点击更新数据</Button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const value = ref();
|
|||||||
title="基础弹窗示例"
|
title="基础弹窗示例"
|
||||||
title-tooltip="标题提示内容"
|
title-tooltip="标题提示内容"
|
||||||
>
|
>
|
||||||
此弹窗指定在内容区域打开
|
此弹窗指定在内容区域打开,并且在关闭之后弹窗内容不会被销毁
|
||||||
<Input v-model="value" placeholder="KeepAlive测试" />
|
<Input v-model:value="value" placeholder="KeepAlive测试" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ function openConfirm() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
centered: false,
|
||||||
content: '这是一个确认弹窗',
|
content: '这是一个确认弹窗',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
})
|
})
|
||||||
@@ -160,6 +161,7 @@ async function openPrompt() {
|
|||||||
componentProps: { placeholder: '不能吃芝士...' },
|
componentProps: { placeholder: '不能吃芝士...' },
|
||||||
content: '中午吃了什么?',
|
content: '中午吃了什么?',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
|
overlayBlur: 3,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
message.success(`用户输入了:${res}`);
|
message.success(`用户输入了:${res}`);
|
||||||
@@ -196,7 +198,7 @@ async function openPrompt() {
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card class="w-[300px]" title="指定容器">
|
<Card class="w-[300px]" title="指定容器+关闭后不销毁">
|
||||||
<p>在内容区域打开弹窗的示例</p>
|
<p>在内容区域打开弹窗的示例</p>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<Button type="primary" @click="openInContentModal">打开弹窗</Button>
|
<Button type="primary" @click="openInContentModal">打开弹窗</Button>
|
||||||
@@ -261,6 +263,9 @@ async function openPrompt() {
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="w-[300px]" title="轻量提示弹窗">
|
<Card class="w-[300px]" title="轻量提示弹窗">
|
||||||
|
<template #extra>
|
||||||
|
<DocButton path="/components/common-ui/vben-alert" />
|
||||||
|
</template>
|
||||||
<p>通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等</p>
|
<p>通过快捷方法创建动态提示弹窗,适合一些轻量的提示和确认、输入等</p>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<Button type="primary" @click="openAlert">Alert</Button>
|
<Button type="primary" @click="openAlert">Alert</Button>
|
||||||
|
|||||||
2437
pnpm-lock.yaml
generated
2437
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -21,22 +21,22 @@ catalog:
|
|||||||
'@commitlint/cli': ^19.8.0
|
'@commitlint/cli': ^19.8.0
|
||||||
'@commitlint/config-conventional': ^19.8.0
|
'@commitlint/config-conventional': ^19.8.0
|
||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.23.0
|
'@eslint/js': ^9.24.0
|
||||||
'@faker-js/faker': ^9.6.0
|
'@faker-js/faker': ^9.6.0
|
||||||
'@iconify/json': ^2.2.323
|
'@iconify/json': ^2.2.324
|
||||||
'@iconify/tailwind': ^1.2.0
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.3.0
|
'@iconify/vue': ^4.3.0
|
||||||
'@intlify/core-base': ^11.1.2
|
'@intlify/core-base': ^11.1.3
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.5
|
'@intlify/unplugin-vue-i18n': ^6.0.5
|
||||||
'@jspm/generator': ^2.5.1
|
'@jspm/generator': ^2.5.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.15.1
|
'@nolebase/vitepress-plugin-git-changelog': ^2.16.0
|
||||||
'@playwright/test': ^1.51.1
|
'@playwright/test': ^1.51.1
|
||||||
'@pnpm/workspace.read-manifest': ^1000.1.2
|
'@pnpm/workspace.read-manifest': ^1000.1.3
|
||||||
'@stylistic/stylelint-plugin': ^3.1.2
|
'@stylistic/stylelint-plugin': ^3.1.2
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.16
|
'@tailwindcss/typography': ^0.5.16
|
||||||
'@tanstack/vue-query': ^5.71.1
|
'@tanstack/vue-query': ^5.72.0
|
||||||
'@tanstack/vue-store': ^0.7.0
|
'@tanstack/vue-store': ^0.7.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
@@ -46,15 +46,15 @@ catalog:
|
|||||||
'@types/lodash.get': ^4.4.9
|
'@types/lodash.get': ^4.4.9
|
||||||
'@types/lodash.isequal': ^4.5.8
|
'@types/lodash.isequal': ^4.5.8
|
||||||
'@types/lodash.set': ^4.3.9
|
'@types/lodash.set': ^4.3.9
|
||||||
'@types/node': ^22.13.17
|
'@types/node': ^22.14.0
|
||||||
'@types/nprogress': ^0.2.3
|
'@types/nprogress': ^0.2.3
|
||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/qs': ^6.9.18
|
'@types/qs': ^6.9.18
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@types/crypto-js': ^4.2.2
|
'@types/crypto-js': ^4.2.2
|
||||||
'@typescript-eslint/eslint-plugin': ^8.29.0
|
'@typescript-eslint/eslint-plugin': ^8.29.1
|
||||||
'@typescript-eslint/parser': ^8.29.0
|
'@typescript-eslint/parser': ^8.29.1
|
||||||
'@vee-validate/zod': ^4.15.0
|
'@vee-validate/zod': ^4.15.0
|
||||||
'@vite-pwa/vitepress': ^0.5.4
|
'@vite-pwa/vitepress': ^0.5.4
|
||||||
'@vitejs/plugin-vue': ^5.2.3
|
'@vitejs/plugin-vue': ^5.2.3
|
||||||
@@ -90,17 +90,17 @@ catalog:
|
|||||||
dotenv: ^16.4.7
|
dotenv: ^16.4.7
|
||||||
echarts: ^5.6.0
|
echarts: ^5.6.0
|
||||||
element-plus: ^2.9.7
|
element-plus: ^2.9.7
|
||||||
eslint: ^9.23.0
|
eslint: ^9.24.0
|
||||||
eslint-config-turbo: ^2.4.4
|
eslint-config-turbo: ^2.5.0
|
||||||
eslint-plugin-command: ^0.2.7
|
eslint-plugin-command: ^0.2.7
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.10.0
|
eslint-plugin-import-x: ^4.10.2
|
||||||
eslint-plugin-jsdoc: ^50.6.9
|
eslint-plugin-jsdoc: ^50.6.9
|
||||||
eslint-plugin-jsonc: ^2.20.0
|
eslint-plugin-jsonc: ^2.20.0
|
||||||
eslint-plugin-n: ^17.17.0
|
eslint-plugin-n: ^17.17.0
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
eslint-plugin-no-only-tests: ^3.3.0
|
||||||
eslint-plugin-perfectionist: ^4.11.0
|
eslint-plugin-perfectionist: ^4.11.0
|
||||||
eslint-plugin-prettier: ^5.2.5
|
eslint-plugin-prettier: ^5.2.6
|
||||||
eslint-plugin-regexp: ^2.7.0
|
eslint-plugin-regexp: ^2.7.0
|
||||||
eslint-plugin-unicorn: ^56.0.1
|
eslint-plugin-unicorn: ^56.0.1
|
||||||
eslint-plugin-unused-imports: ^4.1.4
|
eslint-plugin-unused-imports: ^4.1.4
|
||||||
@@ -148,9 +148,9 @@ catalog:
|
|||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.39.0
|
rollup: ^4.39.0
|
||||||
rollup-plugin-visualizer: ^5.14.0
|
rollup-plugin-visualizer: ^5.14.0
|
||||||
sass: ^1.86.1
|
sass: ^1.86.3
|
||||||
sortablejs: ^1.15.6
|
sortablejs: ^1.15.6
|
||||||
stylelint: ^16.17.0
|
stylelint: ^16.18.0
|
||||||
stylelint-config-recess-order: ^5.1.1
|
stylelint-config-recess-order: ^5.1.1
|
||||||
stylelint-config-recommended: ^14.0.1
|
stylelint-config-recommended: ^14.0.1
|
||||||
stylelint-config-recommended-scss: ^14.1.0
|
stylelint-config-recommended-scss: ^14.1.0
|
||||||
@@ -164,12 +164,12 @@ catalog:
|
|||||||
tailwindcss-animate: ^1.0.7
|
tailwindcss-animate: ^1.0.7
|
||||||
theme-colors: ^0.1.0
|
theme-colors: ^0.1.0
|
||||||
tippy.js: ^6.2.5
|
tippy.js: ^6.2.5
|
||||||
turbo: ^2.4.4
|
turbo: ^2.5.0
|
||||||
typescript: ^5.8.2
|
typescript: ^5.8.3
|
||||||
unbuild: ^3.5.0
|
unbuild: ^3.5.0
|
||||||
unplugin-element-plus: ^0.9.1
|
unplugin-element-plus: ^0.9.1
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.2.4
|
vite: ^6.2.5
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.3
|
vite-plugin-dts: ^4.5.3
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
@@ -181,12 +181,12 @@ catalog:
|
|||||||
vitest: ^2.1.9
|
vitest: ^2.1.9
|
||||||
vue: ^3.5.13
|
vue: ^3.5.13
|
||||||
vue-eslint-parser: ^9.4.3
|
vue-eslint-parser: ^9.4.3
|
||||||
vue-i18n: ^11.1.2
|
vue-i18n: ^11.1.3
|
||||||
vue-json-viewer: ^3.0.4
|
vue-json-viewer: ^3.0.4
|
||||||
vue-router: ^4.5.0
|
vue-router: ^4.5.0
|
||||||
vue-tippy: ^6.7.0
|
vue-tippy: ^6.7.0
|
||||||
vue-tsc: 2.1.10
|
vue-tsc: 2.1.10
|
||||||
vxe-pc-ui: ^4.5.11
|
vxe-pc-ui: ^4.5.14
|
||||||
vxe-table: ^4.12.5
|
vxe-table: ^4.12.5
|
||||||
watermark-js-plus: ^1.5.8
|
watermark-js-plus: ^1.5.8
|
||||||
zod: ^3.24.2
|
zod: ^3.24.2
|
||||||
|
|||||||
Reference in New Issue
Block a user