Merge branch 'main' of https://gitee.com/dapppp/ruoyi-plus-vben5 into dev
This commit is contained in:
commit
b0d44b07b4
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,3 +1,13 @@
|
|||||||
|
# 1.1.4
|
||||||
|
|
||||||
|
**Features**
|
||||||
|
|
||||||
|
- 通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)
|
||||||
|
|
||||||
|
**BUG FIXES**
|
||||||
|
|
||||||
|
- 字典项为空时getDict方法无限调用接口((无奈兼容 不给字典item本来就是错误用法))
|
||||||
|
|
||||||
# 1.1.3
|
# 1.1.3
|
||||||
|
|
||||||
**REFACTOR**
|
**REFACTOR**
|
||||||
|
@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
|
|||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { ApiSelect, 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 {
|
||||||
@ -52,6 +52,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
@ -87,14 +88,32 @@ async function initComponentAdapter() {
|
|||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
return h(
|
return h(
|
||||||
ApiSelect,
|
ApiComponent,
|
||||||
{
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
component: Select,
|
component: Select,
|
||||||
loadingSlot: 'suffixIcon',
|
loadingSlot: 'suffixIcon',
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
visibleEvent: 'onDropdownVisibleChange',
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
|
@ -130,3 +130,24 @@ export function vxeCheckboxChecked(
|
|||||||
) {
|
) {
|
||||||
return tableApi?.grid?.getCheckboxRecords?.()?.length > 0;
|
return tableApi?.grid?.getCheckboxRecords?.()?.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用的vxe-table排序事件 支持单/多字段排序
|
||||||
|
* @param tableApi api
|
||||||
|
* @param sortParams 排序参数
|
||||||
|
*/
|
||||||
|
export function vxeSortEvent(
|
||||||
|
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
|
||||||
|
sortParams: VxeGridDefines.SortChangeEventParams,
|
||||||
|
) {
|
||||||
|
const { sortList } = sortParams;
|
||||||
|
// 这里是排序取消 length为0 就不传参数了
|
||||||
|
if (sortList.length === 0) {
|
||||||
|
tableApi.query();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 支持单/多字段排序
|
||||||
|
const orderByColumn = sortList.map((item) => item.field).join(',');
|
||||||
|
const isAsc = sortList.map((item) => item.order).join(',');
|
||||||
|
tableApi.query({ orderByColumn, isAsc });
|
||||||
|
}
|
||||||
|
9
apps/web-antd/src/api/common.d.ts
vendored
9
apps/web-antd/src/api/common.d.ts
vendored
@ -21,13 +21,20 @@ export interface PageResult<T = any> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询参数
|
* 分页查询参数
|
||||||
|
*
|
||||||
|
* 排序支持的用法如下:
|
||||||
|
* {isAsc:"asc",orderByColumn:"id"} order by id asc
|
||||||
|
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
|
||||||
|
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
|
||||||
|
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
|
||||||
|
*
|
||||||
* @param pageNum 当前页
|
* @param pageNum 当前页
|
||||||
* @param pageSize 每页大小
|
* @param pageSize 每页大小
|
||||||
* @param orderByColumn 排序字段
|
* @param orderByColumn 排序字段
|
||||||
* @param isAsc 是否升序
|
* @param isAsc 是否升序
|
||||||
*/
|
*/
|
||||||
export interface PageQuery {
|
export interface PageQuery {
|
||||||
isAsc?: boolean;
|
isAsc?: string;
|
||||||
orderByColumn?: string;
|
orderByColumn?: string;
|
||||||
pageNum?: number;
|
pageNum?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
|
@ -20,7 +20,14 @@ export function getDict(dictName: string): DictData[] {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// 移除请求状态缓存
|
// 移除请求状态缓存
|
||||||
dictRequestCache.delete(dictName);
|
/**
|
||||||
|
* 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法)
|
||||||
|
* 会导致if一直进入逻辑导致接口无限刷新
|
||||||
|
* 在这里dictList为空时 不删除缓存
|
||||||
|
*/
|
||||||
|
if (dictList.length > 0) {
|
||||||
|
dictRequestCache.delete(dictName);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -42,7 +49,14 @@ export function getDictOptions(dictName: string): Option[] {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
// 移除请求状态缓存
|
// 移除请求状态缓存
|
||||||
dictRequestCache.delete(dictName);
|
/**
|
||||||
|
* 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法)
|
||||||
|
* 会导致if一直进入逻辑导致接口五线刷新
|
||||||
|
* 在这里dictList为空时 不删除缓存
|
||||||
|
*/
|
||||||
|
if (dictOptionList.length > 0) {
|
||||||
|
dictRequestCache.delete(dictName);
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ import { Page, useVbenDrawer, type VbenFormProps } from '@vben/common-ui';
|
|||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
import { Modal, Space } from 'ant-design-vue';
|
import { Modal, Space } from 'ant-design-vue';
|
||||||
import { isEmpty } from 'lodash-es';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useVbenVxeGrid,
|
useVbenVxeGrid,
|
||||||
vxeCheckboxChecked,
|
vxeCheckboxChecked,
|
||||||
type VxeGridProps,
|
type VxeGridProps,
|
||||||
|
vxeSortEvent,
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
operLogClean,
|
operLogClean,
|
||||||
@ -60,18 +60,12 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
|||||||
pagerConfig: {},
|
pagerConfig: {},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page, sort }, formValues = {}) => {
|
query: async ({ page }, formValues = {}) => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
pageNum: page.currentPage,
|
pageNum: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...formValues,
|
...formValues,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isEmpty(sort)) {
|
|
||||||
params.orderByColumn = sort.field;
|
|
||||||
params.isAsc = sort.order;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await operLogList(params);
|
return await operLogList(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -81,7 +75,10 @@ const gridOptions: VxeGridProps<OperationLog> = {
|
|||||||
keyField: 'operId',
|
keyField: 'operId',
|
||||||
},
|
},
|
||||||
sortConfig: {
|
sortConfig: {
|
||||||
|
// 远程排序
|
||||||
remote: true,
|
remote: true,
|
||||||
|
// 支持多字段排序 默认关闭
|
||||||
|
multiple: true,
|
||||||
},
|
},
|
||||||
id: 'monitor-operlog-index',
|
id: 'monitor-operlog-index',
|
||||||
};
|
};
|
||||||
@ -90,9 +87,7 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
|||||||
formOptions,
|
formOptions,
|
||||||
gridOptions,
|
gridOptions,
|
||||||
gridEvents: {
|
gridEvents: {
|
||||||
sortChange: () => {
|
sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
|
||||||
tableApi.query();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -17,12 +17,12 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
import { isEmpty } from 'lodash-es';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useVbenVxeGrid,
|
useVbenVxeGrid,
|
||||||
vxeCheckboxChecked,
|
vxeCheckboxChecked,
|
||||||
type VxeGridProps,
|
type VxeGridProps,
|
||||||
|
vxeSortEvent,
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import { configInfoByKey } from '#/api/system/config';
|
import { configInfoByKey } from '#/api/system/config';
|
||||||
import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
|
import { ossDownload, ossList, ossRemove } from '#/api/system/oss';
|
||||||
@ -66,16 +66,12 @@ const gridOptions: VxeGridProps = {
|
|||||||
pagerConfig: {},
|
pagerConfig: {},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page, sort }, formValues = {}) => {
|
query: async ({ page }, formValues = {}) => {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
pageNum: page.currentPage,
|
pageNum: page.currentPage,
|
||||||
pageSize: page.pageSize,
|
pageSize: page.pageSize,
|
||||||
...formValues,
|
...formValues,
|
||||||
};
|
};
|
||||||
if (!isEmpty(sort)) {
|
|
||||||
params.orderByColumn = sort.field;
|
|
||||||
params.isAsc = sort.order;
|
|
||||||
}
|
|
||||||
return await ossList(params);
|
return await ossList(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -86,7 +82,10 @@ const gridOptions: VxeGridProps = {
|
|||||||
height: 65,
|
height: 65,
|
||||||
},
|
},
|
||||||
sortConfig: {
|
sortConfig: {
|
||||||
|
// 远程排序
|
||||||
remote: true,
|
remote: true,
|
||||||
|
// 支持多字段排序 默认关闭
|
||||||
|
multiple: false,
|
||||||
},
|
},
|
||||||
id: 'system-oss-index',
|
id: 'system-oss-index',
|
||||||
};
|
};
|
||||||
@ -95,9 +94,7 @@ const [BasicTable, tableApi] = useVbenVxeGrid({
|
|||||||
formOptions,
|
formOptions,
|
||||||
gridOptions,
|
gridOptions,
|
||||||
gridEvents: {
|
gridEvents: {
|
||||||
sortChange: () => {
|
sortChange: (sortParams) => vxeSortEvent(tableApi, sortParams),
|
||||||
tableApi.query();
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ const { hasAccessByCodes } = useAccess();
|
|||||||
<div class="flex h-full gap-[8px]">
|
<div class="flex h-full gap-[8px]">
|
||||||
<DeptTree
|
<DeptTree
|
||||||
v-model:select-dept-id="selectDeptId"
|
v-model:select-dept-id="selectDeptId"
|
||||||
:width="260"
|
class="w-[260px]"
|
||||||
@reload="() => tableApi.reload()"
|
@reload="() => tableApi.reload()"
|
||||||
@select="() => tableApi.reload()"
|
@select="() => tableApi.reload()"
|
||||||
/>
|
/>
|
||||||
|
@ -9,20 +9,22 @@ import type { Recordable } from '@vben/types';
|
|||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { ApiSelect, 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 {
|
||||||
ElButton,
|
ElButton,
|
||||||
ElCheckbox,
|
ElCheckbox,
|
||||||
|
ElCheckboxButton,
|
||||||
ElCheckboxGroup,
|
ElCheckboxGroup,
|
||||||
ElDatePicker,
|
ElDatePicker,
|
||||||
ElDivider,
|
ElDivider,
|
||||||
ElInput,
|
ElInput,
|
||||||
ElInputNumber,
|
ElInputNumber,
|
||||||
ElNotification,
|
ElNotification,
|
||||||
|
ElRadio,
|
||||||
|
ElRadioButton,
|
||||||
ElRadioGroup,
|
ElRadioGroup,
|
||||||
ElSelect,
|
|
||||||
ElSelectV2,
|
ElSelectV2,
|
||||||
ElSpace,
|
ElSpace,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
@ -44,6 +46,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
@ -67,19 +70,55 @@ async function initComponentAdapter() {
|
|||||||
// import('xxx').then((res) => res.Button),
|
// import('xxx').then((res) => res.Button),
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
return h(
|
return h(
|
||||||
ApiSelect,
|
ApiComponent,
|
||||||
{
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
component: ElSelectV2,
|
component: ElSelectV2,
|
||||||
loadingSlot: 'loading',
|
loadingSlot: 'loading',
|
||||||
visibleEvent: 'onDropdownVisibleChange',
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: ElTreeSelect,
|
||||||
|
props: { label: 'label', children: 'children' },
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'loading',
|
||||||
|
optionsPropName: 'data',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Checkbox: ElCheckbox,
|
Checkbox: ElCheckbox,
|
||||||
CheckboxGroup: ElCheckboxGroup,
|
CheckboxGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options, isButton } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(isButton ? ElCheckboxButton : ElCheckbox, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElCheckboxGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ ...slots, default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
// 自定义默认按钮
|
// 自定义默认按钮
|
||||||
DefaultButton: (props, { attrs, slots }) => {
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
return h(ElButton, { ...props, attrs, type: 'info' }, slots);
|
||||||
@ -104,8 +143,28 @@ async function initComponentAdapter() {
|
|||||||
},
|
},
|
||||||
Input: withDefaultPlaceholder(ElInput, 'input'),
|
Input: withDefaultPlaceholder(ElInput, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'),
|
||||||
RadioGroup: ElRadioGroup,
|
RadioGroup: (props, { attrs, slots }) => {
|
||||||
Select: withDefaultPlaceholder(ElSelect, 'select'),
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(attrs.isButton ? ElRadioButton : ElRadio, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
ElRadioGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ ...slots, default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Select: (props, { attrs, slots }) => {
|
||||||
|
return h(ElSelectV2, { ...props, attrs }, slots);
|
||||||
|
},
|
||||||
Space: ElSpace,
|
Space: ElSpace,
|
||||||
Switch: ElSwitch,
|
Switch: ElSwitch,
|
||||||
TimePicker: (props, { attrs, slots }) => {
|
TimePicker: (props, { attrs, slots }) => {
|
||||||
|
@ -12,6 +12,7 @@ setupVbenForm<ComponentType>({
|
|||||||
config: {
|
config: {
|
||||||
modelPropNameMap: {
|
modelPropNameMap: {
|
||||||
Upload: 'fileList',
|
Upload: 'fileList',
|
||||||
|
CheckboxGroup: 'model-value',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defineRules: {
|
defineRules: {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Demos",
|
"title": "Demos",
|
||||||
"elementPlus": "Element Plus",
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "Form",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "Project",
|
"title": "Project",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "演示",
|
"title": "演示",
|
||||||
"elementPlus": "Element Plus",
|
"elementPlus": "Element Plus",
|
||||||
|
"form": "表单演示",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "项目",
|
"title": "项目",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
|
@ -23,6 +23,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/demos/element',
|
path: '/demos/element',
|
||||||
component: () => import('#/views/demos/element/index.vue'),
|
component: () => import('#/views/demos/element/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: $t('demos.form'),
|
||||||
|
},
|
||||||
|
name: 'BasicForm',
|
||||||
|
path: '/demos/form',
|
||||||
|
component: () => import('#/views/demos/form/basic.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
180
apps/web-ele/src/views/demos/form/basic.vue
Normal file
180
apps/web-ele/src/views/demos/form/basic.vue
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
handleSubmit: (values) => {
|
||||||
|
ElMessage.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'string',
|
||||||
|
label: 'String',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'number',
|
||||||
|
label: 'Number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radio',
|
||||||
|
label: 'Radio',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: 'A' },
|
||||||
|
{ value: 'B', label: 'B' },
|
||||||
|
{ value: 'C', label: 'C' },
|
||||||
|
{ value: 'D', label: 'D' },
|
||||||
|
{ value: 'E', label: 'E' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radioButton',
|
||||||
|
label: 'RadioButton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
options: ['A', 'B', 'C', 'D', 'E', 'F'].map((v) => ({
|
||||||
|
value: v,
|
||||||
|
label: `选项${v}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox',
|
||||||
|
label: 'Checkbox',
|
||||||
|
componentProps: {
|
||||||
|
options: ['A', 'B', 'C'].map((v) => ({ value: v, label: `选项${v}` })),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox1',
|
||||||
|
label: 'Checkbox1',
|
||||||
|
renderComponentContent: () => {
|
||||||
|
return {
|
||||||
|
default: () => {
|
||||||
|
return ['A', 'B', 'C', 'D'].map((v) =>
|
||||||
|
h(ElCheckbox, { label: v, value: v }),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbotton',
|
||||||
|
label: 'CheckBotton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
fieldName: 'date',
|
||||||
|
label: 'Date',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Select',
|
||||||
|
fieldName: 'select',
|
||||||
|
label: 'Select',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function setFormValues() {
|
||||||
|
formApi.setValues({
|
||||||
|
string: 'string',
|
||||||
|
number: 123,
|
||||||
|
radio: 'B',
|
||||||
|
radioButton: 'C',
|
||||||
|
checkbox: ['A', 'C'],
|
||||||
|
checkbotton: ['B', 'C'],
|
||||||
|
checkbox1: ['A', 'B'],
|
||||||
|
date: new Date(),
|
||||||
|
select: 'B',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
description="我们重新包装了CheckboxGroup、RadioGroup、Select,可以通过options属性传入选项属性数组以自动生成选项"
|
||||||
|
title="表单演示"
|
||||||
|
>
|
||||||
|
<ElCard>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="flex-auto">基础表单演示</span>
|
||||||
|
<ElButton type="primary" @click="setFormValues">设置表单值</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<Form />
|
||||||
|
</ElCard>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
|
|||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { ApiSelect, 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 {
|
||||||
@ -19,6 +19,8 @@ import {
|
|||||||
NDivider,
|
NDivider,
|
||||||
NInput,
|
NInput,
|
||||||
NInputNumber,
|
NInputNumber,
|
||||||
|
NRadio,
|
||||||
|
NRadioButton,
|
||||||
NRadioGroup,
|
NRadioGroup,
|
||||||
NSelect,
|
NSelect,
|
||||||
NSpace,
|
NSpace,
|
||||||
@ -43,6 +45,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
| 'DatePicker'
|
| 'DatePicker'
|
||||||
@ -67,18 +70,52 @@ async function initComponentAdapter() {
|
|||||||
|
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
return h(
|
return h(
|
||||||
ApiSelect,
|
ApiComponent,
|
||||||
{
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
component: NSelect,
|
component: NSelect,
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: NTreeSelect,
|
||||||
|
nodeKey: 'value',
|
||||||
|
loadingSlot: 'arrow',
|
||||||
|
keyField: 'value',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'options',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Checkbox: NCheckbox,
|
Checkbox: NCheckbox,
|
||||||
CheckboxGroup: NCheckboxGroup,
|
CheckboxGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () => options.map((option) => h(NCheckbox, option));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return h(
|
||||||
|
NCheckboxGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ default: defaultSlot },
|
||||||
|
);
|
||||||
|
},
|
||||||
DatePicker: NDatePicker,
|
DatePicker: NDatePicker,
|
||||||
// 自定义默认按钮
|
// 自定义默认按钮
|
||||||
DefaultButton: (props, { attrs, slots }) => {
|
DefaultButton: (props, { attrs, slots }) => {
|
||||||
@ -98,7 +135,28 @@ async function initComponentAdapter() {
|
|||||||
},
|
},
|
||||||
Input: withDefaultPlaceholder(NInput, 'input'),
|
Input: withDefaultPlaceholder(NInput, 'input'),
|
||||||
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
InputNumber: withDefaultPlaceholder(NInputNumber, 'input'),
|
||||||
RadioGroup: NRadioGroup,
|
RadioGroup: (props, { attrs, slots }) => {
|
||||||
|
let defaultSlot;
|
||||||
|
if (Reflect.has(slots, 'default')) {
|
||||||
|
defaultSlot = slots.default;
|
||||||
|
} else {
|
||||||
|
const { options } = attrs;
|
||||||
|
if (Array.isArray(options)) {
|
||||||
|
defaultSlot = () =>
|
||||||
|
options.map((option) =>
|
||||||
|
h(attrs.isButton ? NRadioButton : NRadio, option),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const groupRender = h(
|
||||||
|
NRadioGroup,
|
||||||
|
{ ...props, ...attrs },
|
||||||
|
{ default: defaultSlot },
|
||||||
|
);
|
||||||
|
return attrs.isButton
|
||||||
|
? h(NSpace, { vertical: true }, () => groupRender)
|
||||||
|
: groupRender;
|
||||||
|
},
|
||||||
Select: withDefaultPlaceholder(NSelect, 'select'),
|
Select: withDefaultPlaceholder(NSelect, 'select'),
|
||||||
Space: NSpace,
|
Space: NSpace,
|
||||||
Switch: NSwitch,
|
Switch: NSwitch,
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"title": "Demos",
|
"title": "Demos",
|
||||||
"naive": "Naive UI",
|
"naive": "Naive UI",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
|
"form": "Form",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "Project",
|
"title": "Project",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"title": "演示",
|
"title": "演示",
|
||||||
"naive": "Naive UI",
|
"naive": "Naive UI",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
|
"form": "表单",
|
||||||
"vben": {
|
"vben": {
|
||||||
"title": "项目",
|
"title": "项目",
|
||||||
"about": "关于",
|
"about": "关于",
|
||||||
|
@ -31,6 +31,14 @@ const routes: RouteRecordRaw[] = [
|
|||||||
path: '/demos/table',
|
path: '/demos/table',
|
||||||
component: () => import('#/views/demos/table/index.vue'),
|
component: () => import('#/views/demos/table/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
title: $t('demos.form'),
|
||||||
|
},
|
||||||
|
name: 'Form',
|
||||||
|
path: '/demos/form',
|
||||||
|
component: () => import('#/views/demos/form/basic.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
143
apps/web-naive/src/views/demos/form/basic.vue
Normal file
143
apps/web-naive/src/views/demos/form/basic.vue
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { NButton, NCard, useMessage } from 'naive-ui';
|
||||||
|
|
||||||
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
import { getAllMenusApi } from '#/api';
|
||||||
|
|
||||||
|
const message = useMessage();
|
||||||
|
const [Form, formApi] = useVbenForm({
|
||||||
|
commonConfig: {
|
||||||
|
// 所有表单项
|
||||||
|
componentProps: {
|
||||||
|
class: 'w-full',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
layout: 'horizontal',
|
||||||
|
// 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
|
||||||
|
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
|
||||||
|
handleSubmit: (values) => {
|
||||||
|
message.success(`表单数据:${JSON.stringify(values)}`);
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
// 组件需要在 #/adapter.ts内注册,并加上类型
|
||||||
|
component: 'ApiSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口转options格式
|
||||||
|
afterFetch: (data: { name: string; path: string }[]) => {
|
||||||
|
return data.map((item: any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'api',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'Input',
|
||||||
|
fieldName: 'string',
|
||||||
|
label: 'String',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'InputNumber',
|
||||||
|
fieldName: 'number',
|
||||||
|
label: 'Number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radio',
|
||||||
|
label: 'Radio',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: 'A' },
|
||||||
|
{ value: 'B', label: 'B' },
|
||||||
|
{ value: 'C', label: 'C' },
|
||||||
|
{ value: 'D', label: 'D' },
|
||||||
|
{ value: 'E', label: 'E' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'RadioGroup',
|
||||||
|
fieldName: 'radioButton',
|
||||||
|
label: 'RadioButton',
|
||||||
|
componentProps: {
|
||||||
|
isButton: true,
|
||||||
|
class: 'flex flex-wrap', // 如果选项过多,可以添加class来自动折叠
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
{ value: 'D', label: '选项D' },
|
||||||
|
{ value: 'E', label: '选项E' },
|
||||||
|
{ value: 'F', label: '选项F' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'CheckboxGroup',
|
||||||
|
fieldName: 'checkbox',
|
||||||
|
label: 'Checkbox',
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ value: 'A', label: '选项A' },
|
||||||
|
{ value: 'B', label: '选项B' },
|
||||||
|
{ value: 'C', label: '选项C' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
fieldName: 'date',
|
||||||
|
label: 'Date',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
function setFormValues() {
|
||||||
|
formApi.setValues({
|
||||||
|
string: 'string',
|
||||||
|
number: 123,
|
||||||
|
radio: 'B',
|
||||||
|
radioButton: 'C',
|
||||||
|
checkbox: ['A', 'C'],
|
||||||
|
date: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Page
|
||||||
|
description="表单适配器重新包装了CheckboxGroup和RadioGroup,可以通过options属性传递选项数据(选项数据将作为子组件的属性)"
|
||||||
|
title="表单演示"
|
||||||
|
>
|
||||||
|
<NCard title="基础表单">
|
||||||
|
<template #header-extra>
|
||||||
|
<NButton type="primary" @click="setFormValues">设置表单值</NButton>
|
||||||
|
</template>
|
||||||
|
<Form />
|
||||||
|
</NCard>
|
||||||
|
</Page>
|
||||||
|
</template>
|
@ -162,6 +162,10 @@ function sidebarComponents(): DefaultTheme.SidebarItem[] {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
text: '通用组件',
|
text: '通用组件',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
link: 'common-ui/vben-api-component',
|
||||||
|
text: 'ApiComponent Api组件包装器',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
link: 'common-ui/vben-modal',
|
link: 'common-ui/vben-modal',
|
||||||
text: 'Modal 模态框',
|
text: 'Modal 模态框',
|
||||||
|
150
docs/src/components/common-ui/vben-api-component.md
Normal file
150
docs/src/components/common-ui/vben-api-component.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
outline: deep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vben ApiComponent Api组件包装器
|
||||||
|
|
||||||
|
框架提供的API“包装器”,它一般不独立使用,主要用于包装其它组件,为目标组件提供自动获取远程数据的能力,但仍然保持了目标组件的原始用法。
|
||||||
|
|
||||||
|
::: info 写在前面
|
||||||
|
|
||||||
|
我们在各个应用的组件适配器中,使用ApiComponent包装了Select、TreeSelect组件,使得这些组件可以自动获取远程数据并生成选项。其它类似的组件(比如Cascader)如有需要也可以参考示例代码自行进行包装。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
通过 `component` 传入其它组件的定义,并配置相关的其它属性(主要是一些名称映射)。包装组件将通过`api`获取数据(`beforerFetch`、`afterFetch`将分别在`api`运行前、运行后被调用),使用`resultField`从中提取数组,使用`valueField`、`labelField`等来从数据中提取value和label(如果提供了`childrenField`,会将其作为树形结构递归处理每一级数据),之后将处理好的数据通过`optionsPropName`指定的属性传递给目标组件。
|
||||||
|
|
||||||
|
::: details 包装级联选择器,点击下拉时开始加载远程数据
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ApiComponent } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Cascader } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const treeData: Record<string, any> = [
|
||||||
|
{
|
||||||
|
label: '浙江',
|
||||||
|
value: 'zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: '杭州',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: '西湖',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'sudi',
|
||||||
|
label: '苏堤',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiaxing',
|
||||||
|
label: '嘉兴',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'wuzhen',
|
||||||
|
label: '乌镇',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'meihuazhou',
|
||||||
|
label: '梅花洲',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'zhoushan',
|
||||||
|
label: '舟山',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'putuoshan',
|
||||||
|
label: '普陀山',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'taohuadao',
|
||||||
|
label: '桃花岛',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '江苏',
|
||||||
|
value: 'jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: '南京',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: '中华门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'zijinshan',
|
||||||
|
label: '紫金山',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'yuhuatai',
|
||||||
|
label: '雨花台',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 模拟请求接口
|
||||||
|
*/
|
||||||
|
function fetchApi(): Promise<Record<string, any>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(treeData);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
:api="fetchApi"
|
||||||
|
:component="Cascader"
|
||||||
|
:immediate="false"
|
||||||
|
children-field="children"
|
||||||
|
loading-slot="suffixIcon"
|
||||||
|
visible-event="onDropdownVisibleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| component | 欲包装的组件 | `Component` | - |
|
||||||
|
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
|
||||||
|
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
|
||||||
|
| params | 传递给api的参数 | `Record<string, any>` | - |
|
||||||
|
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
|
||||||
|
| labelField | label字段名 | `string` | `label` |
|
||||||
|
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
|
||||||
|
| valueField | value字段名 | `string` | `value` |
|
||||||
|
| optionsPropName | 组件接收options数据的属性名称 | `string` | `options` |
|
||||||
|
| modelPropName | 组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` |
|
||||||
|
| immediate | 是否立即调用api | `boolean` | `true` |
|
||||||
|
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
|
||||||
|
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
||||||
|
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
||||||
|
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
|
||||||
|
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
|
||||||
|
| loadingSlot | 组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
@ -74,6 +74,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
|
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
@ -95,6 +96,13 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
| contentClass | modal内容区域的class | `string` | - |
|
| contentClass | modal内容区域的class | `string` | - |
|
||||||
| footerClass | modal底部区域的class | `string` | - |
|
| footerClass | modal底部区域的class | `string` | - |
|
||||||
| headerClass | modal顶部区域的class | `string` | - |
|
| headerClass | modal顶部区域的class | `string` | - |
|
||||||
|
| zIndex | 抽屉的ZIndex层级 | `number` | `1000` |
|
||||||
|
|
||||||
|
::: info appendToMain
|
||||||
|
|
||||||
|
`appendToMain`可以指定将抽屉挂载到内容区域,打开抽屉时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,抽屉会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便抽屉能够正确计算高度。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### Event
|
### Event
|
||||||
|
|
||||||
|
@ -306,6 +306,8 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| actionWrapperClass | 表单操作区域class | `any` | - |
|
| actionWrapperClass | 表单操作区域class | `any` | - |
|
||||||
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleReset | 表单重置回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
| handleSubmit | 表单提交回调 | `(values: Record<string, any>,) => Promise<void> \| void` | - |
|
||||||
|
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>,) => void` | - |
|
||||||
|
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
||||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
|
| showDefaultActions | 是否显示默认操作按钮 | `boolean` | `true` |
|
||||||
|
@ -80,6 +80,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
|
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
@ -106,6 +107,13 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
| footerClass | modal底部区域的class | `string` | - |
|
| footerClass | modal底部区域的class | `string` | - |
|
||||||
| headerClass | modal顶部区域的class | `string` | - |
|
| headerClass | modal顶部区域的class | `string` | - |
|
||||||
| bordered | 是否显示border | `boolean` | `false` |
|
| bordered | 是否显示border | `boolean` | `false` |
|
||||||
|
| zIndex | 弹窗的ZIndex层级 | `number` | `1000` |
|
||||||
|
|
||||||
|
::: info appendToMain
|
||||||
|
|
||||||
|
`appendToMain`可以指定将弹窗挂载到内容区域,打开这种弹窗时,内容区域以外的部分(标签栏、导航菜单等等)不会被遮挡。默认情况下,弹窗会挂载到body上。但是:挂载到内容区域时,作为页面根容器的`Page`组件,需要设置`auto-content-height`属性,以便弹窗能够正确计算高度。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### Event
|
### Event
|
||||||
|
|
||||||
|
100
docs/src/demos/vben-api-component/cascader/index.vue
Normal file
100
docs/src/demos/vben-api-component/cascader/index.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { ApiComponent } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Cascader } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const treeData: Record<string, any> = [
|
||||||
|
{
|
||||||
|
label: '浙江',
|
||||||
|
value: 'zhejiang',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'hangzhou',
|
||||||
|
label: '杭州',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'xihu',
|
||||||
|
label: '西湖',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'sudi',
|
||||||
|
label: '苏堤',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'jiaxing',
|
||||||
|
label: '嘉兴',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'wuzhen',
|
||||||
|
label: '乌镇',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'meihuazhou',
|
||||||
|
label: '梅花洲',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'zhoushan',
|
||||||
|
label: '舟山',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'putuoshan',
|
||||||
|
label: '普陀山',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'taohuadao',
|
||||||
|
label: '桃花岛',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '江苏',
|
||||||
|
value: 'jiangsu',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'nanjing',
|
||||||
|
label: '南京',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
value: 'zhonghuamen',
|
||||||
|
label: '中华门',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'zijinshan',
|
||||||
|
label: '紫金山',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'yuhuatai',
|
||||||
|
label: '雨花台',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* 模拟请求接口
|
||||||
|
*/
|
||||||
|
function fetchApi(): Promise<Record<string, any>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(treeData);
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<ApiComponent
|
||||||
|
:api="fetchApi"
|
||||||
|
:component="Cascader"
|
||||||
|
:immediate="false"
|
||||||
|
children-field="children"
|
||||||
|
loading-slot="suffixIcon"
|
||||||
|
visible-event="onDropdownVisibleChange"
|
||||||
|
/>
|
||||||
|
</template>
|
@ -99,7 +99,7 @@
|
|||||||
"node": ">=20.10.0",
|
"node": ">=20.10.0",
|
||||||
"pnpm": ">=9.12.0"
|
"pnpm": ">=9.12.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.14.4",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
|
@ -7,6 +7,9 @@ export const CSS_VARIABLE_LAYOUT_HEADER_HEIGHT = `--vben-header-height`;
|
|||||||
/** layout footer 组件的高度 */
|
/** layout footer 组件的高度 */
|
||||||
export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;
|
export const CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT = `--vben-footer-height`;
|
||||||
|
|
||||||
|
/** 内容区域的组件ID */
|
||||||
|
export const ELEMENT_ID_MAIN_CONTENT = `__vben_main_content`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 默认命名空间
|
* @zh_CN 默认命名空间
|
||||||
*/
|
*/
|
||||||
|
@ -142,13 +142,29 @@ defineExpose({
|
|||||||
"
|
"
|
||||||
:style="queryFormStyle"
|
:style="queryFormStyle"
|
||||||
>
|
>
|
||||||
|
<template v-if="rootProps.actionButtonsReverse">
|
||||||
|
<!-- 提交按钮前 -->
|
||||||
|
<slot name="submit-before"></slot>
|
||||||
|
|
||||||
|
<component
|
||||||
|
:is="COMPONENT_MAP.PrimaryButton"
|
||||||
|
v-if="submitButtonOptions.show"
|
||||||
|
class="ml-3"
|
||||||
|
type="button"
|
||||||
|
@click="handleSubmit"
|
||||||
|
v-bind="submitButtonOptions"
|
||||||
|
>
|
||||||
|
{{ submitButtonOptions.content }}
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 重置按钮前 -->
|
<!-- 重置按钮前 -->
|
||||||
<slot name="reset-before"></slot>
|
<slot name="reset-before"></slot>
|
||||||
|
|
||||||
<component
|
<component
|
||||||
:is="COMPONENT_MAP.DefaultButton"
|
:is="COMPONENT_MAP.DefaultButton"
|
||||||
v-if="resetButtonOptions.show"
|
v-if="resetButtonOptions.show"
|
||||||
class="mr-3"
|
class="ml-3"
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleReset"
|
@click="handleReset"
|
||||||
v-bind="resetButtonOptions"
|
v-bind="resetButtonOptions"
|
||||||
@ -156,18 +172,21 @@ defineExpose({
|
|||||||
{{ resetButtonOptions.content }}
|
{{ resetButtonOptions.content }}
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
<!-- 提交按钮前 -->
|
<template v-if="!rootProps.actionButtonsReverse">
|
||||||
<slot name="submit-before"></slot>
|
<!-- 提交按钮前 -->
|
||||||
|
<slot name="submit-before"></slot>
|
||||||
|
|
||||||
<component
|
<component
|
||||||
:is="COMPONENT_MAP.PrimaryButton"
|
:is="COMPONENT_MAP.PrimaryButton"
|
||||||
v-if="submitButtonOptions.show"
|
v-if="submitButtonOptions.show"
|
||||||
type="button"
|
class="ml-3"
|
||||||
@click="handleSubmit"
|
type="button"
|
||||||
v-bind="submitButtonOptions"
|
@click="handleSubmit"
|
||||||
>
|
v-bind="submitButtonOptions"
|
||||||
{{ submitButtonOptions.content }}
|
>
|
||||||
</component>
|
{{ submitButtonOptions.content }}
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 展开按钮前 -->
|
<!-- 展开按钮前 -->
|
||||||
<slot name="expand-before"></slot>
|
<slot name="expand-before"></slot>
|
||||||
|
@ -307,6 +307,10 @@ export interface VbenFormProps<
|
|||||||
FormRenderProps<T>,
|
FormRenderProps<T>,
|
||||||
'componentBindEventMap' | 'componentMap' | 'form'
|
'componentBindEventMap' | 'componentMap' | 'form'
|
||||||
> {
|
> {
|
||||||
|
/**
|
||||||
|
* 操作按钮是否反转(提交按钮前置)
|
||||||
|
*/
|
||||||
|
actionButtonsReverse?: boolean;
|
||||||
/**
|
/**
|
||||||
* 表单操作区域class
|
* 表单操作区域class
|
||||||
*/
|
*/
|
||||||
|
@ -62,9 +62,7 @@ function handleKeyDownEnter(event: KeyboardEvent) {
|
|||||||
watch(
|
watch(
|
||||||
() => form.values,
|
() => form.values,
|
||||||
useDebounceFn(() => {
|
useDebounceFn(() => {
|
||||||
(props.handleValuesChange ?? state.value.handleValuesChange)?.(
|
forward.value.handleValuesChange?.(toRaw(form.values));
|
||||||
toRaw(form.values),
|
|
||||||
);
|
|
||||||
state.value.submitOnChange && props.formApi?.submitForm();
|
state.value.submitOnChange && props.formApi?.submitForm();
|
||||||
}, 300),
|
}, 300),
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
"@vben-core/composables": "workspace:*",
|
"@vben-core/composables": "workspace:*",
|
||||||
"@vben-core/icons": "workspace:*",
|
"@vben-core/icons": "workspace:*",
|
||||||
"@vben-core/shadcn-ui": "workspace:*",
|
"@vben-core/shadcn-ui": "workspace:*",
|
||||||
|
"@vben-core/shared": "workspace:*",
|
||||||
"@vben-core/typings": "workspace:*",
|
"@vben-core/typings": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"vue": "catalog:"
|
"vue": "catalog:"
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
} from '@vben-core/composables';
|
} from '@vben-core/composables';
|
||||||
import { Menu } from '@vben-core/icons';
|
import { Menu } from '@vben-core/icons';
|
||||||
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
||||||
|
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
|
||||||
|
|
||||||
import { useMouse, useScroll, useThrottleFn } from '@vueuse/core';
|
import { useMouse, useScroll, useThrottleFn } from '@vueuse/core';
|
||||||
|
|
||||||
@ -457,6 +458,8 @@ function handleHeaderToggle() {
|
|||||||
emit('toggleSidebar');
|
emit('toggleSidebar');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const idMainContent = ELEMENT_ID_MAIN_CONTENT;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -553,6 +556,7 @@ function handleHeaderToggle() {
|
|||||||
|
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
<LayoutContent
|
<LayoutContent
|
||||||
|
:id="idMainContent"
|
||||||
:content-compact="contentCompact"
|
:content-compact="contentCompact"
|
||||||
:content-compact-width="contentCompactWidth"
|
:content-compact-width="contentCompactWidth"
|
||||||
:padding="contentPadding"
|
:padding="contentPadding"
|
||||||
|
@ -7,6 +7,11 @@ import type { Component, Ref } from 'vue';
|
|||||||
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
|
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
|
||||||
|
|
||||||
export interface DrawerProps {
|
export interface DrawerProps {
|
||||||
|
/**
|
||||||
|
* 是否挂载到内容区域
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
appendToMain?: boolean;
|
||||||
/**
|
/**
|
||||||
* 取消按钮文字
|
* 取消按钮文字
|
||||||
*/
|
*/
|
||||||
@ -59,12 +64,12 @@ export interface DrawerProps {
|
|||||||
* 弹窗头部样式
|
* 弹窗头部样式
|
||||||
*/
|
*/
|
||||||
headerClass?: ClassType;
|
headerClass?: ClassType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹窗是否显示
|
* 弹窗是否显示
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示遮罩
|
* 是否显示遮罩
|
||||||
* @default true
|
* @default true
|
||||||
@ -74,12 +79,12 @@ export interface DrawerProps {
|
|||||||
* 是否自动聚焦
|
* 是否自动聚焦
|
||||||
*/
|
*/
|
||||||
openAutoFocus?: boolean;
|
openAutoFocus?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 抽屉位置
|
* 抽屉位置
|
||||||
* @default right
|
* @default right
|
||||||
*/
|
*/
|
||||||
placement?: DrawerPlacement;
|
placement?: DrawerPlacement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示取消按钮
|
* 是否显示取消按钮
|
||||||
* @default true
|
* @default true
|
||||||
@ -98,6 +103,10 @@ export interface DrawerProps {
|
|||||||
* 弹窗标题提示
|
* 弹窗标题提示
|
||||||
*/
|
*/
|
||||||
titleTooltip?: string;
|
titleTooltip?: string;
|
||||||
|
/**
|
||||||
|
* 抽屉层级
|
||||||
|
*/
|
||||||
|
zIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DrawerState extends DrawerProps {
|
export interface DrawerState extends DrawerProps {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
|
import type { DrawerProps, ExtendedDrawerApi } from './drawer';
|
||||||
|
|
||||||
import { provide, ref, useId, watch } from 'vue';
|
import { computed, provide, ref, useId, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useIsMobile,
|
useIsMobile,
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
VbenLoading,
|
VbenLoading,
|
||||||
VisuallyHidden,
|
VisuallyHidden,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
|
||||||
import { globalShareState } from '@vben-core/shared/global-state';
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
import { cn } from '@vben-core/shared/utils';
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
@ -31,7 +32,9 @@ interface Props extends DrawerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
appendToMain: false,
|
||||||
drawerApi: undefined,
|
drawerApi: undefined,
|
||||||
|
zIndex: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const components = globalShareState.getComponents();
|
const components = globalShareState.getComponents();
|
||||||
@ -46,6 +49,7 @@ const { isMobile } = useIsMobile();
|
|||||||
const state = props.drawerApi?.useStore?.();
|
const state = props.drawerApi?.useStore?.();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
appendToMain,
|
||||||
cancelText,
|
cancelText,
|
||||||
class: drawerClass,
|
class: drawerClass,
|
||||||
closable,
|
closable,
|
||||||
@ -67,6 +71,7 @@ const {
|
|||||||
showConfirmButton,
|
showConfirmButton,
|
||||||
title,
|
title,
|
||||||
titleTooltip,
|
titleTooltip,
|
||||||
|
zIndex,
|
||||||
} = usePriorityValues(props, state);
|
} = usePriorityValues(props, state);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -110,6 +115,10 @@ function handleFocusOutside(e: Event) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAppendTo = computed(() => {
|
||||||
|
return appendToMain.value ? `#${ELEMENT_ID_MAIN_CONTENT}` : undefined;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Sheet
|
<Sheet
|
||||||
@ -118,6 +127,7 @@ function handleFocusOutside(e: Event) {
|
|||||||
@update:open="() => drawerApi?.close()"
|
@update:open="() => drawerApi?.close()"
|
||||||
>
|
>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
|
:append-to="getAppendTo"
|
||||||
:class="
|
:class="
|
||||||
cn('flex w-[520px] flex-col', drawerClass, {
|
cn('flex w-[520px] flex-col', drawerClass, {
|
||||||
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
|
'!w-full': isMobile || placement === 'bottom' || placement === 'top',
|
||||||
@ -127,6 +137,7 @@ function handleFocusOutside(e: Event) {
|
|||||||
:modal="modal"
|
:modal="modal"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
:side="placement"
|
:side="placement"
|
||||||
|
:z-index="zIndex"
|
||||||
@close-auto-focus="handleFocusOutside"
|
@close-auto-focus="handleFocusOutside"
|
||||||
@escape-key-down="escapeKeyDown"
|
@escape-key-down="escapeKeyDown"
|
||||||
@focus-outside="handleFocusOutside"
|
@focus-outside="handleFocusOutside"
|
||||||
|
@ -3,6 +3,11 @@ import type { ModalApi } from './modal-api';
|
|||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
|
/**
|
||||||
|
* 是否要挂载到内容区域
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
appendToMain?: boolean;
|
||||||
/**
|
/**
|
||||||
* 是否显示边框
|
* 是否显示边框
|
||||||
* @default false
|
* @default false
|
||||||
@ -12,7 +17,6 @@ export interface ModalProps {
|
|||||||
* 取消按钮文字
|
* 取消按钮文字
|
||||||
*/
|
*/
|
||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否居中
|
* 是否居中
|
||||||
* @default false
|
* @default false
|
||||||
@ -20,6 +24,7 @@ export interface ModalProps {
|
|||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
|
|
||||||
class?: string;
|
class?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示右上角的关闭按钮
|
* 是否显示右上角的关闭按钮
|
||||||
* @default true
|
* @default true
|
||||||
@ -112,6 +117,10 @@ export interface ModalProps {
|
|||||||
* 弹窗标题提示
|
* 弹窗标题提示
|
||||||
*/
|
*/
|
||||||
titleTooltip?: string;
|
titleTooltip?: string;
|
||||||
|
/**
|
||||||
|
* 弹窗层级
|
||||||
|
*/
|
||||||
|
zIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModalState extends ModalProps {
|
export interface ModalState extends ModalProps {
|
||||||
|
@ -22,6 +22,7 @@ import {
|
|||||||
VbenLoading,
|
VbenLoading,
|
||||||
VisuallyHidden,
|
VisuallyHidden,
|
||||||
} from '@vben-core/shadcn-ui';
|
} from '@vben-core/shadcn-ui';
|
||||||
|
import { ELEMENT_ID_MAIN_CONTENT } from '@vben-core/shared/constants';
|
||||||
import { globalShareState } from '@vben-core/shared/global-state';
|
import { globalShareState } from '@vben-core/shared/global-state';
|
||||||
import { cn } from '@vben-core/shared/utils';
|
import { cn } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ interface Props extends ModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
appendToMain: false,
|
||||||
modalApi: undefined,
|
modalApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ const { isMobile } = useIsMobile();
|
|||||||
const state = props.modalApi?.useStore?.();
|
const state = props.modalApi?.useStore?.();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
appendToMain,
|
||||||
bordered,
|
bordered,
|
||||||
cancelText,
|
cancelText,
|
||||||
centered,
|
centered,
|
||||||
@ -78,6 +81,7 @@ const {
|
|||||||
showConfirmButton,
|
showConfirmButton,
|
||||||
title,
|
title,
|
||||||
titleTooltip,
|
titleTooltip,
|
||||||
|
zIndex,
|
||||||
} = usePriorityValues(props, state);
|
} = usePriorityValues(props, state);
|
||||||
|
|
||||||
const shouldFullscreen = computed(
|
const shouldFullscreen = computed(
|
||||||
@ -161,6 +165,9 @@ function handleFocusOutside(e: Event) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
const getAppendTo = computed(() => {
|
||||||
|
return appendToMain.value ? `#${ELEMENT_ID_MAIN_CONTENT}` : undefined;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -170,6 +177,7 @@ function handleFocusOutside(e: Event) {
|
|||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
ref="contentRef"
|
ref="contentRef"
|
||||||
|
:append-to="getAppendTo"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-[var(--radius)]',
|
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] w-[520px] flex-col p-0 sm:rounded-[var(--radius)]',
|
||||||
@ -187,6 +195,7 @@ function handleFocusOutside(e: Event) {
|
|||||||
:modal="modal"
|
:modal="modal"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
:show-close="closable"
|
:show-close="closable"
|
||||||
|
:z-index="zIndex"
|
||||||
close-class="top-3"
|
close-class="top-3"
|
||||||
@close-auto-focus="handleFocusOutside"
|
@close-auto-focus="handleFocusOutside"
|
||||||
@closed="() => modalApi?.onClosed()"
|
@closed="() => modalApi?.onClosed()"
|
||||||
|
@ -20,14 +20,16 @@ import DialogOverlay from './DialogOverlay.vue';
|
|||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<
|
defineProps<
|
||||||
{
|
{
|
||||||
|
appendTo?: HTMLElement | string;
|
||||||
class?: ClassType;
|
class?: ClassType;
|
||||||
closeClass?: ClassType;
|
closeClass?: ClassType;
|
||||||
modal?: boolean;
|
modal?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
showClose?: boolean;
|
showClose?: boolean;
|
||||||
|
zIndex?: number;
|
||||||
} & DialogContentProps
|
} & DialogContentProps
|
||||||
>(),
|
>(),
|
||||||
{ showClose: true },
|
{ appendTo: 'body', showClose: true, zIndex: 1000 },
|
||||||
);
|
);
|
||||||
const emits = defineEmits<
|
const emits = defineEmits<
|
||||||
{ close: []; closed: []; opened: [] } & DialogContentEmits
|
{ close: []; closed: []; opened: [] } & DialogContentEmits
|
||||||
@ -45,6 +47,18 @@ const delegatedProps = computed(() => {
|
|||||||
return delegated;
|
return delegated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function isAppendToBody() {
|
||||||
|
return (
|
||||||
|
props.appendTo === 'body' ||
|
||||||
|
props.appendTo === document.body ||
|
||||||
|
!props.appendTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = computed(() => {
|
||||||
|
return isAppendToBody() ? 'fixed' : 'absolute';
|
||||||
|
});
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
|
||||||
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
||||||
@ -64,17 +78,22 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal :to="appendTo">
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<DialogOverlay v-if="open && modal" @click="() => emits('close')" />
|
<DialogOverlay
|
||||||
|
v-if="open && modal"
|
||||||
|
:style="{ zIndex, position }"
|
||||||
|
@click="() => emits('close')"
|
||||||
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
ref="contentRef"
|
ref="contentRef"
|
||||||
|
:style="{ zIndex, position }"
|
||||||
@animationend="onAnimationEnd"
|
@animationend="onAnimationEnd"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'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%] fixed z-[1000] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
'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',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
@ -7,8 +7,5 @@ useScrollLock();
|
|||||||
const id = inject('DISMISSABLE_MODAL_ID');
|
const id = inject('DISMISSABLE_MODAL_ID');
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :data-dismissable-modal="id" class="bg-overlay inset-0"></div>
|
||||||
:data-dismissable-modal="id"
|
|
||||||
class="bg-overlay fixed inset-0 z-[1000]"
|
|
||||||
></div>
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -14,7 +14,10 @@ import {
|
|||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from 'radix-vue';
|
} from 'radix-vue';
|
||||||
|
|
||||||
const props = defineProps<{ class?: any } & DialogContentProps>();
|
const props = withDefaults(
|
||||||
|
defineProps<{ class?: any; zIndex?: number } & DialogContentProps>(),
|
||||||
|
{ zIndex: 1000 },
|
||||||
|
);
|
||||||
const emits = defineEmits<DialogContentEmits>();
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = computed(() => {
|
const delegatedProps = computed(() => {
|
||||||
@ -29,7 +32,8 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 border-border fixed inset-0 z-[1000] grid place-items-center overflow-y-auto border bg-black/80"
|
:style="{ zIndex }"
|
||||||
|
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 border-border absolute inset-0 grid place-items-center overflow-y-auto border bg-black/80"
|
||||||
>
|
>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
:class="
|
:class="
|
||||||
@ -38,6 +42,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
:style="{ zIndex }"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
@pointer-down-outside="
|
@pointer-down-outside="
|
||||||
(event) => {
|
(event) => {
|
||||||
|
@ -15,17 +15,22 @@ import { type SheetVariants, sheetVariants } from './sheet';
|
|||||||
import SheetOverlay from './SheetOverlay.vue';
|
import SheetOverlay from './SheetOverlay.vue';
|
||||||
|
|
||||||
interface SheetContentProps extends DialogContentProps {
|
interface SheetContentProps extends DialogContentProps {
|
||||||
|
appendTo?: HTMLElement | string;
|
||||||
class?: any;
|
class?: any;
|
||||||
modal?: boolean;
|
modal?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
side?: SheetVariants['side'];
|
side?: SheetVariants['side'];
|
||||||
|
zIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<SheetContentProps>();
|
const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||||
|
appendTo: 'body',
|
||||||
|
zIndex: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
const emits = defineEmits<DialogContentEmits>();
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
@ -41,16 +46,29 @@ const delegatedProps = computed(() => {
|
|||||||
return delegated;
|
return delegated;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function isAppendToBody() {
|
||||||
|
return (
|
||||||
|
props.appendTo === 'body' ||
|
||||||
|
props.appendTo === document.body ||
|
||||||
|
!props.appendTo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = computed(() => {
|
||||||
|
return isAppendToBody() ? 'fixed' : 'absolute';
|
||||||
|
});
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal :to="appendTo">
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<SheetOverlay v-if="open && modal" />
|
<SheetOverlay v-if="open && modal" :style="{ zIndex, position }" />
|
||||||
</Transition>
|
</Transition>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
:class="cn(sheetVariants({ side }), 'z-[1000]', props.class)"
|
:class="cn(sheetVariants({ side }), props.class)"
|
||||||
|
:style="{ zIndex, position }"
|
||||||
v-bind="{ ...forwarded, ...$attrs }"
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -7,8 +7,5 @@ useScrollLock();
|
|||||||
const id = inject('DISMISSABLE_DRAWER_ID');
|
const id = inject('DISMISSABLE_DRAWER_ID');
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div :data-dismissable-drawer="id" class="bg-overlay inset-0"></div>
|
||||||
:data-dismissable-drawer="id"
|
|
||||||
class="bg-overlay fixed inset-0 z-[1000]"
|
|
||||||
></div>
|
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { cva, type VariantProps } from 'class-variance-authority';
|
import { cva, type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
export const sheetVariants = cva(
|
export const sheetVariants = cva(
|
||||||
'fixed z-[1000] bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
'bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
||||||
{
|
{
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
side: 'right',
|
side: 'right',
|
||||||
@ -12,7 +12,7 @@ export const sheetVariants = cva(
|
|||||||
'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||||
right:
|
right:
|
||||||
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
'inset-y-0 right-0 w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { AnyPromiseFunction } from '@vben/types';
|
import type { AnyPromiseFunction } from '@vben/types';
|
||||||
|
|
||||||
import { computed, ref, unref, useAttrs, type VNode, watch } from 'vue';
|
import { type Component, computed, ref, unref, useAttrs, watch } from 'vue';
|
||||||
|
|
||||||
import { LoaderCircle } from '@vben/icons';
|
import { LoaderCircle } from '@vben/icons';
|
||||||
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
|
import { get, isEqual, isFunction } from '@vben-core/shared/utils';
|
||||||
@ -10,37 +10,56 @@ import { objectOmit } from '@vueuse/core';
|
|||||||
|
|
||||||
type OptionsItem = {
|
type OptionsItem = {
|
||||||
[name: string]: any;
|
[name: string]: any;
|
||||||
|
children?: OptionsItem[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// 组件
|
/** 组件 */
|
||||||
component: VNode;
|
component: Component;
|
||||||
|
/** 是否将value从数字转为string */
|
||||||
numberToString?: boolean;
|
numberToString?: boolean;
|
||||||
|
/** 获取options数据的函数 */
|
||||||
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
|
api?: (arg?: any) => Promise<OptionsItem[] | Record<string, any>>;
|
||||||
|
/** 传递给api的参数 */
|
||||||
params?: Record<string, any>;
|
params?: Record<string, any>;
|
||||||
|
/** 从api返回的结果中提取options数组的字段名 */
|
||||||
resultField?: string;
|
resultField?: string;
|
||||||
|
/** label字段名 */
|
||||||
labelField?: string;
|
labelField?: string;
|
||||||
|
/** children字段名,需要层级数据的组件可用 */
|
||||||
|
childrenField?: string;
|
||||||
|
/** value字段名 */
|
||||||
valueField?: string;
|
valueField?: string;
|
||||||
|
/** 组件接收options数据的属性名 */
|
||||||
|
optionsPropName?: string;
|
||||||
|
/** 是否立即调用api */
|
||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
|
/** 每次`visibleEvent`事件发生时都重新请求数据 */
|
||||||
alwaysLoad?: boolean;
|
alwaysLoad?: boolean;
|
||||||
|
/** 在api请求之前的回调函数 */
|
||||||
beforeFetch?: AnyPromiseFunction<any, any>;
|
beforeFetch?: AnyPromiseFunction<any, any>;
|
||||||
|
/** 在api请求之后的回调函数 */
|
||||||
afterFetch?: AnyPromiseFunction<any, any>;
|
afterFetch?: AnyPromiseFunction<any, any>;
|
||||||
|
/** 直接传入选项数据,也作为api返回空数据时的后备数据 */
|
||||||
options?: OptionsItem[];
|
options?: OptionsItem[];
|
||||||
// 尾部插槽
|
/** 组件的插槽名称,用来显示一个"加载中"的图标 */
|
||||||
loadingSlot?: string;
|
loadingSlot?: string;
|
||||||
// 可见时触发的事件名
|
/** 触发api请求的事件名 */
|
||||||
visibleEvent?: string;
|
visibleEvent?: string;
|
||||||
modelField?: string;
|
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
||||||
|
modelPropName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'ApiSelect', inheritAttrs: false });
|
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
labelField: 'label',
|
labelField: 'label',
|
||||||
valueField: 'value',
|
valueField: 'value',
|
||||||
|
childrenField: '',
|
||||||
|
optionsPropName: 'options',
|
||||||
resultField: '',
|
resultField: '',
|
||||||
visibleEvent: '',
|
visibleEvent: '',
|
||||||
numberToString: false,
|
numberToString: false,
|
||||||
@ -50,7 +69,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
loadingSlot: '',
|
loadingSlot: '',
|
||||||
beforeFetch: undefined,
|
beforeFetch: undefined,
|
||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
modelField: 'modelValue',
|
modelPropName: 'modelValue',
|
||||||
api: undefined,
|
api: undefined,
|
||||||
options: () => [],
|
options: () => [],
|
||||||
});
|
});
|
||||||
@ -69,29 +88,34 @@ const loading = ref(false);
|
|||||||
const isFirstLoaded = ref(false);
|
const isFirstLoaded = ref(false);
|
||||||
|
|
||||||
const getOptions = computed(() => {
|
const getOptions = computed(() => {
|
||||||
const { labelField, valueField, numberToString } = props;
|
const { labelField, valueField, childrenField, numberToString } = props;
|
||||||
|
|
||||||
const data: OptionsItem[] = [];
|
|
||||||
const refOptionsData = unref(refOptions);
|
const refOptionsData = unref(refOptions);
|
||||||
|
|
||||||
for (const next of refOptionsData) {
|
function transformData(data: OptionsItem[]): OptionsItem[] {
|
||||||
if (next) {
|
return data.map((item) => {
|
||||||
const value = get(next, valueField);
|
const value = get(item, valueField);
|
||||||
data.push({
|
return {
|
||||||
...objectOmit(next, [labelField, valueField]),
|
...objectOmit(item, [labelField, valueField, childrenField]),
|
||||||
label: get(next, labelField),
|
label: get(item, labelField),
|
||||||
value: numberToString ? `${value}` : value,
|
value: numberToString ? `${value}` : value,
|
||||||
});
|
...(childrenField && item[childrenField]
|
||||||
}
|
? { children: transformData(item[childrenField]) }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data: OptionsItem[] = transformData(refOptionsData);
|
||||||
|
|
||||||
return data.length > 0 ? data : props.options;
|
return data.length > 0 ? data : props.options;
|
||||||
});
|
});
|
||||||
|
|
||||||
const bindProps = computed(() => {
|
const bindProps = computed(() => {
|
||||||
return {
|
return {
|
||||||
[props.modelField]: unref(modelValue),
|
[props.modelPropName]: unref(modelValue),
|
||||||
[`onUpdate:${props.modelField}`]: (val: string) => {
|
[props.optionsPropName]: unref(getOptions),
|
||||||
|
[`onUpdate:${props.modelPropName}`]: (val: string) => {
|
||||||
modelValue.value = val;
|
modelValue.value = val;
|
||||||
},
|
},
|
||||||
...objectOmit(attrs, ['onUpdate:value']),
|
...objectOmit(attrs, ['onUpdate:value']),
|
||||||
@ -168,7 +192,6 @@ function emitChange() {
|
|||||||
<component
|
<component
|
||||||
:is="component"
|
:is="component"
|
||||||
v-bind="bindProps"
|
v-bind="bindProps"
|
||||||
:options="getOptions"
|
|
||||||
:placeholder="$attrs.placeholder"
|
:placeholder="$attrs.placeholder"
|
||||||
>
|
>
|
||||||
<template v-for="item in Object.keys($slots)" #[item]="data">
|
<template v-for="item in Object.keys($slots)" #[item]="data">
|
@ -0,0 +1 @@
|
|||||||
|
export { default as ApiComponent } from './api-component.vue';
|
@ -1 +0,0 @@
|
|||||||
export { default as ApiSelect } from './api-select.vue';
|
|
@ -1,4 +1,4 @@
|
|||||||
export * from './api-select';
|
export * from './api-component';
|
||||||
export * from './captcha';
|
export * from './captcha';
|
||||||
export * from './code-mirror';
|
export * from './code-mirror';
|
||||||
export * from './ellipsis-text';
|
export * from './ellipsis-text';
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
import {
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
type StyleValue,
|
||||||
|
useTemplateRef,
|
||||||
|
} from 'vue';
|
||||||
|
|
||||||
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';
|
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';
|
||||||
import { cn } from '@vben-core/shared/utils';
|
import { cn } from '@vben-core/shared/utils';
|
||||||
@ -29,13 +36,11 @@ const shouldAutoHeight = ref(false);
|
|||||||
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
|
const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
|
||||||
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
|
const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
|
||||||
|
|
||||||
const contentStyle = computed(() => {
|
const contentStyle = computed<StyleValue>(() => {
|
||||||
if (autoContentHeight) {
|
if (autoContentHeight) {
|
||||||
return {
|
return {
|
||||||
height: shouldAutoHeight.value
|
height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px)`,
|
||||||
? `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px)`
|
overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
|
||||||
: '0',
|
|
||||||
// 'overflow-y': shouldAutoHeight.value?'auto':'unset',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
VxeInput,
|
VxeInput,
|
||||||
VxeLoading,
|
VxeLoading,
|
||||||
VxeModal,
|
VxeModal,
|
||||||
|
VxeNumberInput,
|
||||||
VxePager,
|
VxePager,
|
||||||
// VxeList,
|
// VxeList,
|
||||||
// VxeModal,
|
// VxeModal,
|
||||||
@ -82,6 +83,7 @@ export function initVxeTable() {
|
|||||||
// VxeUI.component(VxeList);
|
// VxeUI.component(VxeList);
|
||||||
VxeUI.component(VxeLoading);
|
VxeUI.component(VxeLoading);
|
||||||
VxeUI.component(VxeModal);
|
VxeUI.component(VxeModal);
|
||||||
|
VxeUI.component(VxeNumberInput);
|
||||||
// VxeUI.component(VxeOptgroup);
|
// VxeUI.component(VxeOptgroup);
|
||||||
// VxeUI.component(VxeOption);
|
// VxeUI.component(VxeOption);
|
||||||
VxeUI.component(VxePager);
|
VxeUI.component(VxePager);
|
||||||
|
@ -42,7 +42,7 @@ export interface VxeGridProps {
|
|||||||
/**
|
/**
|
||||||
* vxe-grid 事件
|
* vxe-grid 事件
|
||||||
*/
|
*/
|
||||||
gridEvents?: DeepPartial<VxeGridListeners>;
|
gridEvents?: Partial<VxeGridListeners>;
|
||||||
/**
|
/**
|
||||||
* 表单配置
|
* 表单配置
|
||||||
*/
|
*/
|
||||||
|
@ -8,7 +8,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
|
|||||||
import type { Component, SetupContext } from 'vue';
|
import type { Component, SetupContext } from 'vue';
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { ApiSelect, 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 {
|
||||||
@ -49,6 +49,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||||
export type ComponentType =
|
export type ComponentType =
|
||||||
| 'ApiSelect'
|
| 'ApiSelect'
|
||||||
|
| 'ApiTreeSelect'
|
||||||
| 'AutoComplete'
|
| 'AutoComplete'
|
||||||
| 'Checkbox'
|
| 'Checkbox'
|
||||||
| 'CheckboxGroup'
|
| 'CheckboxGroup'
|
||||||
@ -82,13 +83,31 @@ async function initComponentAdapter() {
|
|||||||
|
|
||||||
ApiSelect: (props, { attrs, slots }) => {
|
ApiSelect: (props, { attrs, slots }) => {
|
||||||
return h(
|
return h(
|
||||||
ApiSelect,
|
ApiComponent,
|
||||||
{
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
component: Select,
|
component: Select,
|
||||||
loadingSlot: 'suffixIcon',
|
loadingSlot: 'suffixIcon',
|
||||||
modelField: 'value',
|
modelPropName: 'value',
|
||||||
|
visibleEvent: 'onVisibleChange',
|
||||||
|
},
|
||||||
|
slots,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ApiTreeSelect: (props, { attrs, slots }) => {
|
||||||
|
return h(
|
||||||
|
ApiComponent,
|
||||||
|
{
|
||||||
|
placeholder: $t('ui.placeholder.select'),
|
||||||
|
...props,
|
||||||
|
...attrs,
|
||||||
|
component: TreeSelect,
|
||||||
|
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||||
|
loadingSlot: 'suffixIcon',
|
||||||
|
modelPropName: 'value',
|
||||||
|
optionsPropName: 'treeData',
|
||||||
visibleEvent: 'onVisibleChange',
|
visibleEvent: 'onVisibleChange',
|
||||||
},
|
},
|
||||||
slots,
|
slots,
|
||||||
|
21
playground/src/views/examples/drawer/in-content-demo.vue
Normal file
21
playground/src/views/examples/drawer/in-content-demo.vue
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVbenDrawer } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const [Drawer, drawerApi] = useVbenDrawer({
|
||||||
|
onCancel() {
|
||||||
|
drawerApi.close();
|
||||||
|
},
|
||||||
|
onConfirm() {
|
||||||
|
message.info('onConfirm');
|
||||||
|
// drawerApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Drawer append-to-main title="基础抽屉示例" title-tooltip="标题提示内容">
|
||||||
|
<template #extra> extra </template>
|
||||||
|
本抽屉指定在内容区域打开
|
||||||
|
</Drawer>
|
||||||
|
</template>
|
@ -8,6 +8,7 @@ import AutoHeightDemo from './auto-height-demo.vue';
|
|||||||
import BaseDemo from './base-demo.vue';
|
import BaseDemo from './base-demo.vue';
|
||||||
import DynamicDemo from './dynamic-demo.vue';
|
import DynamicDemo from './dynamic-demo.vue';
|
||||||
import FormDrawerDemo from './form-drawer-demo.vue';
|
import FormDrawerDemo from './form-drawer-demo.vue';
|
||||||
|
import inContentDemo from './in-content-demo.vue';
|
||||||
import SharedDataDemo from './shared-data-demo.vue';
|
import SharedDataDemo from './shared-data-demo.vue';
|
||||||
|
|
||||||
const [BaseDrawer, baseDrawerApi] = useVbenDrawer({
|
const [BaseDrawer, baseDrawerApi] = useVbenDrawer({
|
||||||
@ -16,6 +17,12 @@ const [BaseDrawer, baseDrawerApi] = useVbenDrawer({
|
|||||||
// placement: 'left',
|
// placement: 'left',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [InContentDrawer, inContentDrawerApi] = useVbenDrawer({
|
||||||
|
// 连接抽离的组件
|
||||||
|
connectedComponent: inContentDemo,
|
||||||
|
// placement: 'left',
|
||||||
|
});
|
||||||
|
|
||||||
const [AutoHeightDrawer, autoHeightDrawerApi] = useVbenDrawer({
|
const [AutoHeightDrawer, autoHeightDrawerApi] = useVbenDrawer({
|
||||||
connectedComponent: AutoHeightDemo,
|
connectedComponent: AutoHeightDemo,
|
||||||
});
|
});
|
||||||
@ -37,6 +44,23 @@ function openBaseDrawer(placement: DrawerPlacement = 'right') {
|
|||||||
baseDrawerApi.open();
|
baseDrawerApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openInContentDrawer(placement: DrawerPlacement = 'right') {
|
||||||
|
inContentDrawerApi.setState({ class: '', placement });
|
||||||
|
if (placement === 'top') {
|
||||||
|
// 页面顶部区域的层级只有200,所以设置一个低于200的值,抽屉从顶部滑出来的时候才比较合适
|
||||||
|
inContentDrawerApi.setState({ zIndex: 199 });
|
||||||
|
} else {
|
||||||
|
inContentDrawerApi.setState({ zIndex: undefined });
|
||||||
|
}
|
||||||
|
inContentDrawerApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMaxContentDrawer() {
|
||||||
|
// 这里只是用来演示方便。实际上自己使用的时候可以直接将这些配置卸载Drawer的属性里
|
||||||
|
inContentDrawerApi.setState({ class: 'w-full', placement: 'right' });
|
||||||
|
inContentDrawerApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
function openAutoHeightDrawer() {
|
function openAutoHeightDrawer() {
|
||||||
autoHeightDrawerApi.open();
|
autoHeightDrawerApi.open();
|
||||||
}
|
}
|
||||||
@ -69,6 +93,7 @@ function openFormDrawer() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page
|
<Page
|
||||||
|
auto-content-height
|
||||||
description="抽屉组件通常用于在当前页面上显示一个覆盖层,用以展示重要信息或提供用户交互界面。"
|
description="抽屉组件通常用于在当前页面上显示一个覆盖层,用以展示重要信息或提供用户交互界面。"
|
||||||
title="抽屉组件示例"
|
title="抽屉组件示例"
|
||||||
>
|
>
|
||||||
@ -76,6 +101,7 @@ function openFormDrawer() {
|
|||||||
<DocButton path="/components/common-ui/vben-drawer" />
|
<DocButton path="/components/common-ui/vben-drawer" />
|
||||||
</template>
|
</template>
|
||||||
<BaseDrawer />
|
<BaseDrawer />
|
||||||
|
<InContentDrawer />
|
||||||
<AutoHeightDrawer />
|
<AutoHeightDrawer />
|
||||||
<DynamicDrawer />
|
<DynamicDrawer />
|
||||||
<SharedDataDrawer />
|
<SharedDataDrawer />
|
||||||
@ -83,18 +109,55 @@ function openFormDrawer() {
|
|||||||
|
|
||||||
<Card class="mb-4" title="基本使用">
|
<Card class="mb-4" title="基本使用">
|
||||||
<p class="mb-3">一个基础的抽屉示例</p>
|
<p class="mb-3">一个基础的抽屉示例</p>
|
||||||
<Button type="primary" @click="openBaseDrawer('right')">右侧打开</Button>
|
<Button class="mb-2" type="primary" @click="openBaseDrawer('right')">
|
||||||
<Button class="ml-2" type="primary" @click="openBaseDrawer('bottom')">
|
右侧打开
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="mb-2 ml-2"
|
||||||
|
type="primary"
|
||||||
|
@click="openBaseDrawer('bottom')"
|
||||||
|
>
|
||||||
底部打开
|
底部打开
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="ml-2" type="primary" @click="openBaseDrawer('left')">
|
<Button class="mb-2 ml-2" type="primary" @click="openBaseDrawer('left')">
|
||||||
左侧打开
|
左侧打开
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="ml-2" type="primary" @click="openBaseDrawer('top')">
|
<Button class="mb-2 ml-2" type="primary" @click="openBaseDrawer('top')">
|
||||||
顶部打开
|
顶部打开
|
||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card class="mb-4" title="在内容区域打开">
|
||||||
|
<p class="mb-3">指定抽屉在内容区域打开,不会覆盖顶部和左侧菜单等区域</p>
|
||||||
|
<Button class="mb-2" type="primary" @click="openInContentDrawer('right')">
|
||||||
|
右侧打开
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="mb-2 ml-2"
|
||||||
|
type="primary"
|
||||||
|
@click="openInContentDrawer('bottom')"
|
||||||
|
>
|
||||||
|
底部打开
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="mb-2 ml-2"
|
||||||
|
type="primary"
|
||||||
|
@click="openInContentDrawer('left')"
|
||||||
|
>
|
||||||
|
左侧打开
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="mb-2 ml-2"
|
||||||
|
type="primary"
|
||||||
|
@click="openInContentDrawer('top')"
|
||||||
|
>
|
||||||
|
顶部打开
|
||||||
|
</Button>
|
||||||
|
<Button class="mb-2 ml-2" type="primary" @click="openMaxContentDrawer">
|
||||||
|
内容区域全屏打开
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card class="mb-4" title="内容高度自适应滚动">
|
<Card class="mb-4" title="内容高度自适应滚动">
|
||||||
<p class="mb-3">可根据内容自动计算滚动高度</p>
|
<p class="mb-3">可根据内容自动计算滚动高度</p>
|
||||||
<Button type="primary" @click="openAutoHeightDrawer">打开抽屉</Button>
|
<Button type="primary" @click="openAutoHeightDrawer">打开抽屉</Button>
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Button, Card, message, Space } from 'ant-design-vue';
|
import { Button, Card, message, Space } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
import { useVbenForm } from '#/adapter/form';
|
||||||
|
|
||||||
|
const isReverseActionButtons = ref(false);
|
||||||
|
|
||||||
const [BaseForm, formApi] = useVbenForm({
|
const [BaseForm, formApi] = useVbenForm({
|
||||||
|
// 翻转操作按钮的位置
|
||||||
|
actionButtonsReverse: isReverseActionButtons.value,
|
||||||
// 所有表单项共用,可单独在表单内覆盖
|
// 所有表单项共用,可单独在表单内覆盖
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
// 所有表单项
|
// 所有表单项
|
||||||
@ -83,6 +89,7 @@ function handleClick(
|
|||||||
| 'labelWidth'
|
| 'labelWidth'
|
||||||
| 'resetDisabled'
|
| 'resetDisabled'
|
||||||
| 'resetLabelWidth'
|
| 'resetLabelWidth'
|
||||||
|
| 'reverseActionButtons'
|
||||||
| 'showAction'
|
| 'showAction'
|
||||||
| 'showResetButton'
|
| 'showResetButton'
|
||||||
| 'showSubmitButton'
|
| 'showSubmitButton'
|
||||||
@ -158,6 +165,11 @@ function handleClick(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'reverseActionButtons': {
|
||||||
|
isReverseActionButtons.value = !isReverseActionButtons.value;
|
||||||
|
formApi.setState({ actionButtonsReverse: isReverseActionButtons.value });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'showAction': {
|
case 'showAction': {
|
||||||
formApi.setState({ showDefaultActions: true });
|
formApi.setState({ showDefaultActions: true });
|
||||||
break;
|
break;
|
||||||
@ -177,6 +189,7 @@ function handleClick(
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateResetButton': {
|
case 'updateResetButton': {
|
||||||
formApi.setState({
|
formApi.setState({
|
||||||
resetButtonOptions: { disabled: true },
|
resetButtonOptions: { disabled: true },
|
||||||
@ -226,6 +239,9 @@ function handleClick(
|
|||||||
<Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
|
<Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
|
||||||
<Button @click="handleClick('disabled')">禁用表单</Button>
|
<Button @click="handleClick('disabled')">禁用表单</Button>
|
||||||
<Button @click="handleClick('resetDisabled')">解除禁用</Button>
|
<Button @click="handleClick('resetDisabled')">解除禁用</Button>
|
||||||
|
<Button @click="handleClick('reverseActionButtons')">
|
||||||
|
翻转操作按钮位置
|
||||||
|
</Button>
|
||||||
<Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
|
<Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
|
||||||
<Button @click="handleClick('showAction')">显示操作按钮</Button>
|
<Button @click="handleClick('showAction')">显示操作按钮</Button>
|
||||||
<Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
|
<Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
|
||||||
|
@ -55,13 +55,28 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
// 菜单接口
|
// 菜单接口
|
||||||
api: getAllMenusApi,
|
api: getAllMenusApi,
|
||||||
placeholder: '请选择',
|
|
||||||
},
|
},
|
||||||
// 字段名
|
// 字段名
|
||||||
fieldName: 'api',
|
fieldName: 'api',
|
||||||
// 界面显示的label
|
// 界面显示的label
|
||||||
label: 'ApiSelect',
|
label: 'ApiSelect',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: 'ApiTreeSelect',
|
||||||
|
// 对应组件的参数
|
||||||
|
componentProps: {
|
||||||
|
// 菜单接口
|
||||||
|
api: getAllMenusApi,
|
||||||
|
childrenField: 'children',
|
||||||
|
// 菜单接口转options格式
|
||||||
|
labelField: 'name',
|
||||||
|
valueField: 'path',
|
||||||
|
},
|
||||||
|
// 字段名
|
||||||
|
fieldName: 'apiTree',
|
||||||
|
// 界面显示的label
|
||||||
|
label: 'ApiTreeSelect',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
component: 'InputPassword',
|
component: 'InputPassword',
|
||||||
componentProps: {
|
componentProps: {
|
||||||
|
25
playground/src/views/examples/modal/in-content-demo.vue
Normal file
25
playground/src/views/examples/modal/in-content-demo.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
onConfirm() {
|
||||||
|
message.info('onConfirm');
|
||||||
|
// modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
append-to-main
|
||||||
|
class="w-[600px]"
|
||||||
|
title="基础弹窗示例"
|
||||||
|
title-tooltip="标题提示内容"
|
||||||
|
>
|
||||||
|
此弹窗指定在内容区域打开
|
||||||
|
</Modal>
|
||||||
|
</template>
|
@ -9,6 +9,7 @@ import BaseDemo from './base-demo.vue';
|
|||||||
import DragDemo from './drag-demo.vue';
|
import DragDemo from './drag-demo.vue';
|
||||||
import DynamicDemo from './dynamic-demo.vue';
|
import DynamicDemo from './dynamic-demo.vue';
|
||||||
import FormModalDemo from './form-modal-demo.vue';
|
import FormModalDemo from './form-modal-demo.vue';
|
||||||
|
import InContentModalDemo from './in-content-demo.vue';
|
||||||
import SharedDataDemo from './shared-data-demo.vue';
|
import SharedDataDemo from './shared-data-demo.vue';
|
||||||
|
|
||||||
const [BaseModal, baseModalApi] = useVbenModal({
|
const [BaseModal, baseModalApi] = useVbenModal({
|
||||||
@ -16,6 +17,11 @@ const [BaseModal, baseModalApi] = useVbenModal({
|
|||||||
connectedComponent: BaseDemo,
|
connectedComponent: BaseDemo,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [InContentModal, inContentModalApi] = useVbenModal({
|
||||||
|
// 连接抽离的组件
|
||||||
|
connectedComponent: InContentModalDemo,
|
||||||
|
});
|
||||||
|
|
||||||
const [AutoHeightModal, autoHeightModalApi] = useVbenModal({
|
const [AutoHeightModal, autoHeightModalApi] = useVbenModal({
|
||||||
connectedComponent: AutoHeightDemo,
|
connectedComponent: AutoHeightDemo,
|
||||||
});
|
});
|
||||||
@ -40,6 +46,10 @@ function openBaseModal() {
|
|||||||
baseModalApi.open();
|
baseModalApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openInContentModal() {
|
||||||
|
inContentModalApi.open();
|
||||||
|
}
|
||||||
|
|
||||||
function openAutoHeightModal() {
|
function openAutoHeightModal() {
|
||||||
autoHeightModalApi.open();
|
autoHeightModalApi.open();
|
||||||
}
|
}
|
||||||
@ -76,6 +86,7 @@ function openFormModal() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page
|
<Page
|
||||||
|
auto-content-height
|
||||||
description="弹窗组件常用于在不离开当前页面的情况下,显示额外的信息、表单或操作提示,更多api请查看组件文档。"
|
description="弹窗组件常用于在不离开当前页面的情况下,显示额外的信息、表单或操作提示,更多api请查看组件文档。"
|
||||||
title="弹窗组件示例"
|
title="弹窗组件示例"
|
||||||
>
|
>
|
||||||
@ -83,6 +94,7 @@ function openFormModal() {
|
|||||||
<DocButton path="/components/common-ui/vben-modal" />
|
<DocButton path="/components/common-ui/vben-modal" />
|
||||||
</template>
|
</template>
|
||||||
<BaseModal />
|
<BaseModal />
|
||||||
|
<InContentModal />
|
||||||
<AutoHeightModal />
|
<AutoHeightModal />
|
||||||
<DragModal />
|
<DragModal />
|
||||||
<DynamicModal />
|
<DynamicModal />
|
||||||
@ -93,6 +105,11 @@ function openFormModal() {
|
|||||||
<Button type="primary" @click="openBaseModal">打开弹窗</Button>
|
<Button type="primary" @click="openBaseModal">打开弹窗</Button>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card class="mb-4" title="指定容器">
|
||||||
|
<p class="mb-3">在内容区域打开弹窗的示例</p>
|
||||||
|
<Button type="primary" @click="openInContentModal">打开弹窗</Button>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card class="mb-4" title="内容高度自适应">
|
<Card class="mb-4" title="内容高度自适应">
|
||||||
<p class="mb-3">可根据内容并自动调整高度</p>
|
<p class="mb-3">可根据内容并自动调整高度</p>
|
||||||
<Button type="primary" @click="openAutoHeightModal">打开弹窗</Button>
|
<Button type="primary" @click="openAutoHeightModal">打开弹窗</Button>
|
||||||
|
@ -13,7 +13,7 @@ packages:
|
|||||||
- docs
|
- docs
|
||||||
- playground
|
- playground
|
||||||
catalog:
|
catalog:
|
||||||
'@ast-grep/napi': ^0.31.0
|
'@ast-grep/napi': ^0.31.1
|
||||||
'@changesets/changelog-github': ^0.5.0
|
'@changesets/changelog-github': ^0.5.0
|
||||||
'@changesets/cli': ^2.27.10
|
'@changesets/cli': ^2.27.10
|
||||||
'@changesets/git': ^3.0.2
|
'@changesets/git': ^3.0.2
|
||||||
@ -23,20 +23,20 @@ catalog:
|
|||||||
'@ctrl/tinycolor': ^4.1.0
|
'@ctrl/tinycolor': ^4.1.0
|
||||||
'@eslint/js': ^9.16.0
|
'@eslint/js': ^9.16.0
|
||||||
'@faker-js/faker': ^9.3.0
|
'@faker-js/faker': ^9.3.0
|
||||||
'@iconify/json': ^2.2.279
|
'@iconify/json': ^2.2.281
|
||||||
'@iconify/tailwind': ^1.1.3
|
'@iconify/tailwind': ^1.2.0
|
||||||
'@iconify/vue': ^4.1.2
|
'@iconify/vue': ^4.2.0
|
||||||
'@intlify/core-base': ^10.0.5
|
'@intlify/core-base': ^10.0.5
|
||||||
'@intlify/unplugin-vue-i18n': ^6.0.0
|
'@intlify/unplugin-vue-i18n': ^6.0.1
|
||||||
'@jspm/generator': ^2.4.1
|
'@jspm/generator': ^2.4.1
|
||||||
'@manypkg/get-packages': ^2.2.2
|
'@manypkg/get-packages': ^2.2.2
|
||||||
'@nolebase/vitepress-plugin-git-changelog': ^2.11.1
|
'@nolebase/vitepress-plugin-git-changelog': ^2.11.1
|
||||||
'@playwright/test': ^1.49.0
|
'@playwright/test': ^1.49.1
|
||||||
'@pnpm/workspace.read-manifest': ^1000.0.0
|
'@pnpm/workspace.read-manifest': ^1000.0.0
|
||||||
'@stylistic/stylelint-plugin': ^3.1.1
|
'@stylistic/stylelint-plugin': ^3.1.1
|
||||||
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
'@tailwindcss/nesting': 0.0.0-insiders.565cd3e
|
||||||
'@tailwindcss/typography': ^0.5.15
|
'@tailwindcss/typography': ^0.5.15
|
||||||
'@tanstack/vue-query': ^5.62.2
|
'@tanstack/vue-query': ^5.62.7
|
||||||
'@tanstack/vue-store': ^0.6.0
|
'@tanstack/vue-store': ^0.6.0
|
||||||
'@types/archiver': ^6.0.3
|
'@types/archiver': ^6.0.3
|
||||||
'@types/eslint': ^9.6.1
|
'@types/eslint': ^9.6.1
|
||||||
@ -50,8 +50,8 @@ catalog:
|
|||||||
'@types/postcss-import': ^14.0.3
|
'@types/postcss-import': ^14.0.3
|
||||||
'@types/qrcode': ^1.5.5
|
'@types/qrcode': ^1.5.5
|
||||||
'@types/sortablejs': ^1.15.8
|
'@types/sortablejs': ^1.15.8
|
||||||
'@typescript-eslint/eslint-plugin': ^8.17.0
|
'@typescript-eslint/eslint-plugin': ^8.18.0
|
||||||
'@typescript-eslint/parser': ^8.17.0
|
'@typescript-eslint/parser': ^8.18.0
|
||||||
'@vee-validate/zod': ^4.14.7
|
'@vee-validate/zod': ^4.14.7
|
||||||
'@vite-pwa/vitepress': ^0.5.3
|
'@vite-pwa/vitepress': ^0.5.3
|
||||||
'@vitejs/plugin-vue': ^5.2.1
|
'@vitejs/plugin-vue': ^5.2.1
|
||||||
@ -90,9 +90,9 @@ catalog:
|
|||||||
eslint-plugin-command: ^0.2.6
|
eslint-plugin-command: ^0.2.6
|
||||||
eslint-plugin-eslint-comments: ^3.2.0
|
eslint-plugin-eslint-comments: ^3.2.0
|
||||||
eslint-plugin-import-x: ^4.5.0
|
eslint-plugin-import-x: ^4.5.0
|
||||||
eslint-plugin-jsdoc: ^50.6.0
|
eslint-plugin-jsdoc: ^50.6.1
|
||||||
eslint-plugin-jsonc: ^2.18.2
|
eslint-plugin-jsonc: ^2.18.2
|
||||||
eslint-plugin-n: ^17.14.0
|
eslint-plugin-n: ^17.15.0
|
||||||
eslint-plugin-no-only-tests: ^3.3.0
|
eslint-plugin-no-only-tests: ^3.3.0
|
||||||
eslint-plugin-perfectionist: ^3.9.1
|
eslint-plugin-perfectionist: ^3.9.1
|
||||||
eslint-plugin-prettier: ^5.2.1
|
eslint-plugin-prettier: ^5.2.1
|
||||||
@ -101,7 +101,7 @@ catalog:
|
|||||||
eslint-plugin-unused-imports: ^4.1.4
|
eslint-plugin-unused-imports: ^4.1.4
|
||||||
eslint-plugin-vitest: ^0.5.4
|
eslint-plugin-vitest: ^0.5.4
|
||||||
eslint-plugin-vue: ^9.32.0
|
eslint-plugin-vue: ^9.32.0
|
||||||
execa: ^9.5.1
|
execa: ^9.5.2
|
||||||
find-up: ^7.0.0
|
find-up: ^7.0.0
|
||||||
get-port: ^7.1.0
|
get-port: ^7.1.0
|
||||||
globals: ^15.13.0
|
globals: ^15.13.0
|
||||||
@ -112,7 +112,7 @@ catalog:
|
|||||||
is-ci: ^3.0.1
|
is-ci: ^3.0.1
|
||||||
jsonc-eslint-parser: ^2.4.0
|
jsonc-eslint-parser: ^2.4.0
|
||||||
jsonwebtoken: ^9.0.2
|
jsonwebtoken: ^9.0.2
|
||||||
lint-staged: ^15.2.10
|
lint-staged: ^15.2.11
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
lodash.get: ^4.4.2
|
lodash.get: ^4.4.2
|
||||||
lodash.isequal: ^4.5.0
|
lodash.isequal: ^4.5.0
|
||||||
@ -125,7 +125,7 @@ catalog:
|
|||||||
pinia: 2.2.2
|
pinia: 2.2.2
|
||||||
pinia-plugin-persistedstate: ^4.1.3
|
pinia-plugin-persistedstate: ^4.1.3
|
||||||
pkg-types: ^1.2.1
|
pkg-types: ^1.2.1
|
||||||
playwright: ^1.49.0
|
playwright: ^1.49.1
|
||||||
postcss: ^8.4.49
|
postcss: ^8.4.49
|
||||||
postcss-antd-fixes: ^0.2.0
|
postcss-antd-fixes: ^0.2.0
|
||||||
postcss-html: ^1.7.0
|
postcss-html: ^1.7.0
|
||||||
@ -136,10 +136,10 @@ catalog:
|
|||||||
prettier-plugin-tailwindcss: ^0.6.9
|
prettier-plugin-tailwindcss: ^0.6.9
|
||||||
publint: ^0.2.12
|
publint: ^0.2.12
|
||||||
qrcode: ^1.5.4
|
qrcode: ^1.5.4
|
||||||
radix-vue: ^1.9.10
|
radix-vue: ^1.9.11
|
||||||
resolve.exports: ^2.0.3
|
resolve.exports: ^2.0.3
|
||||||
rimraf: ^6.0.1
|
rimraf: ^6.0.1
|
||||||
rollup: ^4.28.0
|
rollup: ^4.28.1
|
||||||
rollup-plugin-visualizer: ^5.12.0
|
rollup-plugin-visualizer: ^5.12.0
|
||||||
sass: 1.80.6
|
sass: 1.80.6
|
||||||
sortablejs: ^1.15.6
|
sortablejs: ^1.15.6
|
||||||
@ -161,7 +161,7 @@ catalog:
|
|||||||
unbuild: ^3.0.0-rc.11
|
unbuild: ^3.0.0-rc.11
|
||||||
unplugin-element-plus: ^0.8.0
|
unplugin-element-plus: ^0.8.0
|
||||||
vee-validate: ^4.14.7
|
vee-validate: ^4.14.7
|
||||||
vite: ^6.0.2
|
vite: ^6.0.3
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: 4.2.1
|
vite-plugin-dts: 4.2.1
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
@ -179,5 +179,5 @@ catalog:
|
|||||||
vxe-pc-ui: ^4.3.4
|
vxe-pc-ui: ^4.3.4
|
||||||
vxe-table: 4.9.5
|
vxe-table: 4.9.5
|
||||||
watermark-js-plus: ^1.5.7
|
watermark-js-plus: ^1.5.7
|
||||||
zod: ^3.23.8
|
zod: ^3.24.1
|
||||||
zod-defaults: ^0.1.3
|
zod-defaults: ^0.1.3
|
||||||
|
Loading…
Reference in New Issue
Block a user