Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
commit
e085083e42
@ -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) {
|
||||||
|
@ -131,26 +131,36 @@ 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' \| 'none'\| false` | `false` | >5.5.4 |
|
||||||
|
|
||||||
|
#### autoSelect 自动设置选项
|
||||||
|
|
||||||
|
如果当前值为undefined,在选项数据成功加载之后,自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
|
||||||
|
|
||||||
|
- `first`:自动选择第一个选项
|
||||||
|
- `last`:自动选择最后一个选项
|
||||||
|
- `one`:有且仅有一个选项时,自动选择它
|
||||||
|
- false:不自动选择选项
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { h } from 'vue';
|
|||||||
|
|
||||||
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Input, RadioGroup } from 'ant-design-vue';
|
import { Input, RadioGroup, Select } from 'ant-design-vue';
|
||||||
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
||||||
|
|
||||||
function showPrompt() {
|
function showPrompt() {
|
||||||
@ -18,7 +18,7 @@ function showPrompt() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSelectPrompt() {
|
function showSlotsPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
component: Input,
|
component: Input,
|
||||||
componentProps: {
|
componentProps: {
|
||||||
@ -37,6 +37,29 @@ function showSelectPrompt() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showSelectPrompt() {
|
||||||
|
prompt({
|
||||||
|
component: Select,
|
||||||
|
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) {
|
function sleep(ms: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@ -44,7 +67,6 @@ function sleep(ms: number) {
|
|||||||
function showAsyncPrompt() {
|
function showAsyncPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
async beforeClose(scope) {
|
async beforeClose(scope) {
|
||||||
console.log(scope);
|
|
||||||
if (scope.isConfirm) {
|
if (scope.isConfirm) {
|
||||||
if (scope.value) {
|
if (scope.value) {
|
||||||
// 模拟异步操作,如果不成功,可以返回false
|
// 模拟异步操作,如果不成功,可以返回false
|
||||||
@ -75,6 +97,7 @@ function showAsyncPrompt() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
|
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton>
|
||||||
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
||||||
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,10 @@ export type AlertProps = {
|
|||||||
contentMasking?: boolean;
|
contentMasking?: boolean;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
|
/**
|
||||||
|
* 弹窗遮罩模糊效果
|
||||||
|
*/
|
||||||
|
overlayBlur?: number;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
showCancel?: boolean;
|
showCancel?: boolean;
|
||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
|
@ -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 {
|
||||||
@ -39,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) {
|
|
||||||
isConfirm.value = false;
|
|
||||||
} else {
|
|
||||||
emits('closed', isConfirm.value);
|
emits('closed', isConfirm.value);
|
||||||
}
|
isConfirm.value = false;
|
||||||
});
|
}
|
||||||
|
|
||||||
const getIconRender = computed(() => {
|
const getIconRender = computed(() => {
|
||||||
let iconRender: Component | null = null;
|
let iconRender: Component | null = null;
|
||||||
if (props.icon) {
|
if (props.icon) {
|
||||||
@ -100,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 {
|
||||||
@ -120,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,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
@ -54,6 +54,14 @@ 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' | false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
||||||
@ -74,6 +82,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
modelPropName: 'modelValue',
|
modelPropName: 'modelValue',
|
||||||
api: undefined,
|
api: undefined,
|
||||||
|
autoSelect: false,
|
||||||
options: () => [],
|
options: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,7 +90,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 +203,31 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function emitChange() {
|
function emitChange() {
|
||||||
|
if (
|
||||||
|
modelValue.value === undefined &&
|
||||||
|
props.autoSelect &&
|
||||||
|
unref(getOptions).length > 0
|
||||||
|
) {
|
||||||
|
let firstOption;
|
||||||
|
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[props.valueField];
|
||||||
|
}
|
||||||
emit('optionsChange', unref(getOptions));
|
emit('optionsChange', unref(getOptions));
|
||||||
}
|
}
|
||||||
const componentRef = ref();
|
const componentRef = ref();
|
||||||
|
@ -74,6 +74,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
// 菜单接口
|
// 菜单接口
|
||||||
api: getAllMenusApi,
|
api: getAllMenusApi,
|
||||||
|
autoSelect: 'first',
|
||||||
},
|
},
|
||||||
// 字段名
|
// 字段名
|
||||||
fieldName: 'api',
|
fieldName: 'api',
|
||||||
|
@ -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}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user