Pre Merge pull request !28 from 玲娜贝er/dev
This commit is contained in:
commit
7a5438574f
@ -1,3 +1,9 @@
|
|||||||
|
# 1.3.2
|
||||||
|
|
||||||
|
**REFACTOR**
|
||||||
|
|
||||||
|
- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况)
|
||||||
|
|
||||||
# 1.3.1
|
# 1.3.1
|
||||||
|
|
||||||
**REFACTOR**
|
**REFACTOR**
|
||||||
|
@ -9,7 +9,6 @@ import type { BaseFormComponentType } from '@vben/common-ui';
|
|||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
computed,
|
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
@ -91,15 +90,10 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
name: component.name,
|
name: component.name,
|
||||||
setup: (props: any, { attrs, expose, slots }) => {
|
setup: (props: any, { attrs, expose, slots }) => {
|
||||||
/**
|
const placeholder =
|
||||||
* 需要使用computed 否则后续updateSchema更新的placeholder无法显示(响应式问题)
|
|
||||||
*/
|
|
||||||
const placeholder = computed(
|
|
||||||
() =>
|
|
||||||
props?.placeholder ||
|
props?.placeholder ||
|
||||||
attrs?.placeholder ||
|
attrs?.placeholder ||
|
||||||
$t(`ui.placeholder.${type}`),
|
$t(`ui.placeholder.${type}`);
|
||||||
);
|
|
||||||
|
|
||||||
// 透传组件暴露的方法
|
// 透传组件暴露的方法
|
||||||
const innerRef = ref();
|
const innerRef = ref();
|
||||||
@ -118,7 +112,7 @@ const withDefaultPlaceholder = <T extends Component>(
|
|||||||
component,
|
component,
|
||||||
{
|
{
|
||||||
...componentProps,
|
...componentProps,
|
||||||
placeholder: placeholder.value,
|
placeholder,
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
ref: innerRef,
|
ref: innerRef,
|
||||||
|
@ -51,7 +51,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -60,7 +60,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 120,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -86,6 +86,7 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 120,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -95,7 +95,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -71,7 +71,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -39,11 +39,13 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
{
|
{
|
||||||
field: 'orderNum',
|
field: 'orderNum',
|
||||||
title: '排序',
|
title: '排序',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
slots: {
|
slots: {
|
||||||
default: ({ row }) => {
|
default: ({ row }) => {
|
||||||
|
@ -45,7 +45,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -145,7 +145,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 200,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -69,7 +69,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -81,7 +81,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -69,7 +69,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -65,7 +65,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -68,7 +68,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 200,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 200,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -91,7 +91,8 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 210,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
import type { CategoryTree } from '#/api/workflow/category/model';
|
import type { CategoryTree } from '#/api/workflow/category/model';
|
||||||
|
|
||||||
import { onMounted, type PropType, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { SyncOutlined } from '@ant-design/icons-vue';
|
import { SyncOutlined } from '@ant-design/icons-vue';
|
||||||
import { InputSearch, Skeleton, Tree } from 'ant-design-vue';
|
import { InputSearch, Skeleton, Tree } from 'ant-design-vue';
|
||||||
|
@ -63,7 +63,7 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
resizable: false,
|
resizable: false,
|
||||||
width: 200,
|
width: 'auto',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Space } from 'ant-design-vue';
|
import { Space } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid, type VxeGridProps } from '#/adapter/vxe-table';
|
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { sseList } from './api';
|
import { sseList } from './api';
|
||||||
import sendMsgModal from './send-msg-modal.vue';
|
import sendMsgModal from './send-msg-modal.vue';
|
||||||
@ -31,7 +33,8 @@ const gridOptions: VxeGridProps = {
|
|||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'action' },
|
slots: { default: 'action' },
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 180,
|
resizable: false,
|
||||||
|
width: 'auto',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
|
@ -104,6 +104,11 @@
|
|||||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modal zIndex
|
||||||
|
*/
|
||||||
|
--popup-z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
@ -12,6 +12,12 @@ Alert提供的功能与Modal类似,但只适用于简单应用场景。例如
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
::: tip 注意
|
||||||
|
|
||||||
|
Alert提供的快捷方法alert、confirm、prompt动态创建的弹窗在已打开的情况下,不支持HMR(热更新),代码变更后需要关闭这些弹窗后重新打开。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
::: tip README
|
::: tip README
|
||||||
|
|
||||||
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
下方示例代码中的,存在一些主题色未适配、样式缺失的问题,这些问题只在文档内会出现,实际使用并不会有这些问题,可忽略,不必纠结。
|
||||||
@ -32,6 +38,23 @@ Alert提供的功能与Modal类似,但只适用于简单应用场景。例如
|
|||||||
|
|
||||||
<DemoPreview dir="demos/vben-alert/prompt" />
|
<DemoPreview dir="demos/vben-alert/prompt" />
|
||||||
|
|
||||||
|
## useAlertContext
|
||||||
|
|
||||||
|
当弹窗的content、footer、icon使用自定义组件时,在这些组件中可以使用 `useAlertContext` 获取当前弹窗的上下文对象,用来主动控制弹窗。
|
||||||
|
|
||||||
|
::: tip 注意
|
||||||
|
|
||||||
|
`useAlertContext`只能用在setup或者函数式组件中。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
| 方法 | 描述 | 类型 | 版本要求 |
|
||||||
|
| --------- | ------------------ | -------- | -------- |
|
||||||
|
| doConfirm | 调用弹窗的确认操作 | ()=>void | >5.5.4 |
|
||||||
|
| doCancel | 调用弹窗的取消操作 | ()=>void | >5.5.4 |
|
||||||
|
|
||||||
## 类型说明
|
## 类型说明
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
@ -69,8 +92,14 @@ export type AlertProps = {
|
|||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
contentMasking?: boolean;
|
contentMasking?: boolean;
|
||||||
|
/** 弹窗底部内容(与按钮在同一个容器中) */
|
||||||
|
footer?: Component | string;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
|
/**
|
||||||
|
* 弹窗遮罩模糊效果
|
||||||
|
*/
|
||||||
|
overlayBlur?: number;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
showCancel?: boolean;
|
showCancel?: boolean;
|
||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
|
@ -131,26 +131,37 @@ function fetchApi(): Promise<Record<string, any>> {
|
|||||||
|
|
||||||
### Props
|
### Props
|
||||||
|
|
||||||
| 属性名 | 描述 | 类型 | 默认值 |
|
| 属性名 | 描述 | 类型 | 默认值 | 版本要求 |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| modelValue(v-model) | 当前值 | `any` | - |
|
| modelValue(v-model) | 当前值 | `any` | - | - |
|
||||||
| component | 欲包装的组件(以下称为目标组件) | `Component` | - |
|
| component | 欲包装的组件(以下称为目标组件) | `Component` | - | - |
|
||||||
| numberToString | 是否将value从数字转为string | `boolean` | `false` |
|
| numberToString | 是否将value从数字转为string | `boolean` | `false` | - |
|
||||||
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - |
|
| api | 获取数据的函数 | `(arg?: any) => Promise<OptionsItem[] \| Record<string, any>>` | - | - |
|
||||||
| params | 传递给api的参数 | `Record<string, any>` | - |
|
| params | 传递给api的参数 | `Record<string, any>` | - | - |
|
||||||
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - |
|
| resultField | 从api返回的结果中提取options数组的字段名 | `string` | - | - |
|
||||||
| labelField | label字段名 | `string` | `label` |
|
| labelField | label字段名 | `string` | `label` | - |
|
||||||
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` |
|
| childrenField | 子级数据字段名,需要层级数据的组件可用 | `string` | `` | - |
|
||||||
| valueField | value字段名 | `string` | `value` |
|
| valueField | value字段名 | `string` | `value` | - |
|
||||||
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` |
|
| optionsPropName | 目标组件接收options数据的属性名称 | `string` | `options` | - |
|
||||||
| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` |
|
| modelPropName | 目标组件的双向绑定属性名,默认为modelValue。部分组件可能为value | `string` | `modelValue` | - |
|
||||||
| immediate | 是否立即调用api | `boolean` | `true` |
|
| immediate | 是否立即调用api | `boolean` | `true` | - |
|
||||||
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` |
|
| alwaysLoad | 每次`visibleEvent`事件发生时都重新请求数据 | `boolean` | `false` | - |
|
||||||
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
| beforeFetch | 在api请求之前的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||||
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - |
|
| afterFetch | 在api请求之后的回调函数 | `AnyPromiseFunction<any, any>` | - | - |
|
||||||
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
|
| options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - | - |
|
||||||
| visibleEvent | 触发重新请求数据的事件名 | `string` | - |
|
| visibleEvent | 触发重新请求数据的事件名 | `string` | - | - |
|
||||||
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
|
| loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - | - |
|
||||||
|
| autoSelect | 自动设置选项 | `'first' \| 'last' \| 'one'\| ((item: OptionsItem[]) => OptionsItem) \| false` | `false` | >5.5.4 |
|
||||||
|
|
||||||
|
#### autoSelect 自动设置选项
|
||||||
|
|
||||||
|
如果当前值为undefined,在选项数据成功加载之后,自动从备选项中选择一个作为当前值。默认值为`false`,即不自动选择选项。注意:该属性不应用于多选组件。可选值有:
|
||||||
|
|
||||||
|
- `"first"`:自动选择第一个选项
|
||||||
|
- `"last"`:自动选择最后一个选项
|
||||||
|
- `"one"`:有且仅有一个选项时,自动选择它
|
||||||
|
- `自定义函数`:自定义选择逻辑,函数的参数为options,返回值为选择的选项
|
||||||
|
- `false`:不自动选择选项
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
@ -158,3 +169,5 @@ function fetchApi(): Promise<Record<string, any>> {
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
|
| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
|
||||||
| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record<string, any>)=>void | >5.5.4 |
|
| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record<string, any>)=>void | >5.5.4 |
|
||||||
|
| getOptions | 获取已加载的选项数据 | ()=>OptionsItem[] | >5.5.4 |
|
||||||
|
| getValue | 获取当前值 | ()=>any | >5.5.4 |
|
||||||
|
@ -128,10 +128,11 @@ const [Drawer, drawerApi] = useVbenDrawer({
|
|||||||
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
||||||
|
|
||||||
| 插槽名 | 描述 |
|
| 插槽名 | 描述 |
|
||||||
| -------------- | ------------------- |
|
| -------------- | -------------------------------------------------- |
|
||||||
| default | 默认插槽 - 弹窗内容 |
|
| default | 默认插槽 - 弹窗内容 |
|
||||||
| prepend-footer | 取消按钮左侧 |
|
| prepend-footer | 取消按钮左侧 |
|
||||||
| append-footer | 取消按钮右侧 |
|
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
|
||||||
|
| append-footer | 确认按钮右侧 |
|
||||||
| close-icon | 关闭按钮图标 |
|
| close-icon | 关闭按钮图标 |
|
||||||
| extra | 额外内容(标题右侧) |
|
| extra | 额外内容(标题右侧) |
|
||||||
|
|
||||||
|
@ -310,7 +310,7 @@ 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` | - |
|
| handleValuesChange | 表单值变化回调 | `(values: Record<string, any>, fieldsChanged: string[]) => void` | - |
|
||||||
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
| actionButtonsReverse | 调换操作按钮位置 | `boolean` | `false` |
|
||||||
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
| resetButtonOptions | 重置按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
| submitButtonOptions | 提交按钮组件参数 | `ActionButtonOptions` | - |
|
||||||
@ -325,6 +325,12 @@ useVbenForm 返回的第二个参数,是一个对象,包含了一些表单
|
|||||||
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
| submitOnChange | 字段值改变时提交表单(内部防抖,这个属性一般用于表格的搜索表单) | `boolean` | false |
|
||||||
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
|
| compact | 是否紧凑模式(忽略为校验信息所预留的空间) | `boolean` | false |
|
||||||
|
|
||||||
|
::: tip handleValuesChange
|
||||||
|
|
||||||
|
`handleValuesChange` 回调函数的第一个参数`values`装载了表单改变后的当前值对象,第二个参数`fieldsChanged`是一个数组,包含了所有被改变的字段名。注意:第二个参数仅在v5.5.4(不含)以上版本可用,并且传递的是已在schema中定义的字段名。如果你使用了字段映射并且需要检查是哪些字段发生了变化的话,请注意该参数并不会包含映射后的字段名。
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
::: tip fieldMappingTime
|
::: tip fieldMappingTime
|
||||||
|
|
||||||
此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
|
此属性用于将表单内的数组值映射成 2 个字段,它应当传入一个数组,数组的每一项是一个映射规则,规则的第一个成员是一个字符串,表示需要映射的字段名,第二个成员是一个数组,表示映射后的字段名,第三个成员是一个可选的格式掩码,用于格式化日期时间字段;也可以提供一个格式化函数(参数分别为当前值和当前字段名,返回格式化后的值)。如果明确地将格式掩码设为null,则原值映射而不进行格式化(适用于非日期时间字段)。例如:`[['timeRange', ['startTime', 'endTime'], 'YYYY-MM-DD']]`,`timeRange`应当是一个至少具有2个成员的数组类型的值。Form会将`timeRange`的值前两个值分别按照格式掩码`YYYY-MM-DD`格式化后映射到`startTime`和`endTime`字段上。每一项的第三个参数是一个可选的格式掩码,
|
||||||
|
@ -60,7 +60,6 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
|
|||||||
|
|
||||||
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
|
||||||
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
|
||||||
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
|
|
||||||
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
|
||||||
|
|
||||||
:::
|
:::
|
||||||
@ -84,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
|
||||||
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
|
||||||
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
|
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
|
||||||
| title | 标题 | `string\|slot` | - |
|
| title | 标题 | `string\|slot` | - |
|
||||||
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
| titleTooltip | 标题提示信息 | `string\|slot` | - |
|
||||||
| description | 描述信息 | `string\|slot` | - |
|
| description | 描述信息 | `string\|slot` | - |
|
||||||
@ -139,10 +138,11 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
除了上面的属性类型包含`slot`,还可以通过插槽来自定义弹窗的内容。
|
||||||
|
|
||||||
| 插槽名 | 描述 |
|
| 插槽名 | 描述 |
|
||||||
| -------------- | ------------------- |
|
| -------------- | -------------------------------------------------- |
|
||||||
| default | 默认插槽 - 弹窗内容 |
|
| default | 默认插槽 - 弹窗内容 |
|
||||||
| prepend-footer | 取消按钮左侧 |
|
| prepend-footer | 取消按钮左侧 |
|
||||||
| append-footer | 取消按钮右侧 |
|
| center-footer | 取消按钮和确认按钮中间(不使用 footer 插槽时有效) |
|
||||||
|
| append-footer | 确认按钮右侧 |
|
||||||
|
|
||||||
### modalApi
|
### modalApi
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { h, ref } from 'vue';
|
||||||
|
|
||||||
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
import { alert, confirm, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
|
import { Checkbox, message } from 'ant-design-vue';
|
||||||
|
|
||||||
function showConfirm() {
|
function showConfirm() {
|
||||||
confirm('This is an alert message')
|
confirm('This is an alert message')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -18,6 +22,34 @@ function showIconConfirm() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showfooterConfirm() {
|
||||||
|
const checked = ref(false);
|
||||||
|
confirm({
|
||||||
|
cancelText: '不要虾扯蛋',
|
||||||
|
confirmText: '是的,我们都是NPC',
|
||||||
|
content:
|
||||||
|
'刚才发生的事情,为什么我似乎早就经历过一般?\n我甚至能在事情发生过程中潜意识里预知到接下来会发生什么。\n\n听起来挺玄乎的,你有过这种感觉吗?',
|
||||||
|
footer: () =>
|
||||||
|
h(
|
||||||
|
Checkbox,
|
||||||
|
{
|
||||||
|
checked: checked.value,
|
||||||
|
class: 'flex-1',
|
||||||
|
'onUpdate:checked': (v) => (checked.value = v),
|
||||||
|
},
|
||||||
|
'不再提示',
|
||||||
|
),
|
||||||
|
icon: 'question',
|
||||||
|
title: '未解之谜',
|
||||||
|
}).then(() => {
|
||||||
|
if (checked.value) {
|
||||||
|
message.success('我不会再拿这个问题烦你了');
|
||||||
|
} else {
|
||||||
|
message.info('下次还要继续问你哟');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showAsyncConfirm() {
|
function showAsyncConfirm() {
|
||||||
confirm({
|
confirm({
|
||||||
beforeClose({ isConfirm }) {
|
beforeClose({ isConfirm }) {
|
||||||
@ -37,6 +69,7 @@ function showAsyncConfirm() {
|
|||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
<VbenButton @click="showConfirm">Confirm</VbenButton>
|
||||||
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
<VbenButton @click="showIconConfirm">Confirm With Icon</VbenButton>
|
||||||
|
<VbenButton @click="showfooterConfirm">Confirm With Footer</VbenButton>
|
||||||
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
<VbenButton @click="showAsyncConfirm">Async Confirm</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { alert, prompt, VbenButton } from '@vben/common-ui';
|
import { alert, prompt, useAlertContext, VbenButton } from '@vben/common-ui';
|
||||||
|
|
||||||
import { Input, RadioGroup } from 'ant-design-vue';
|
import { Input, RadioGroup, Select } from 'ant-design-vue';
|
||||||
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
import { BadgeJapaneseYen } from 'lucide-vue-next';
|
||||||
|
|
||||||
function showPrompt() {
|
function showPrompt() {
|
||||||
@ -18,18 +18,32 @@ function showPrompt() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSelectPrompt() {
|
function showSlotsPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
component: Input,
|
component: () => {
|
||||||
componentProps: {
|
// 获取弹窗上下文。注意:只能在setup或者函数式组件中调用
|
||||||
|
const { doConfirm } = useAlertContext();
|
||||||
|
return h(
|
||||||
|
Input,
|
||||||
|
{
|
||||||
|
onKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
// 调用弹窗提供的确认方法
|
||||||
|
doConfirm();
|
||||||
|
}
|
||||||
|
},
|
||||||
placeholder: '请输入',
|
placeholder: '请输入',
|
||||||
prefix: '充值金额',
|
prefix: '充值金额:',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
componentSlots: {
|
{
|
||||||
addonAfter: () => h(BadgeJapaneseYen),
|
addonAfter: () => h(BadgeJapaneseYen),
|
||||||
},
|
},
|
||||||
content: '此弹窗演示了如何使用componentSlots传递自定义插槽',
|
);
|
||||||
|
},
|
||||||
|
content:
|
||||||
|
'此弹窗演示了如何使用自定义插槽,并且可以使用useAlertContext获取到弹窗的上下文。\n在输入框中按下回车键会触发确认操作。',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
modelPropName: 'value',
|
modelPropName: 'value',
|
||||||
}).then((val) => {
|
}).then((val) => {
|
||||||
@ -37,6 +51,29 @@ function showSelectPrompt() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showSelectPrompt() {
|
||||||
|
prompt({
|
||||||
|
component: Select,
|
||||||
|
componentProps: {
|
||||||
|
options: [
|
||||||
|
{ label: 'Option A', value: 'Option A' },
|
||||||
|
{ label: 'Option B', value: 'Option B' },
|
||||||
|
{ label: 'Option C', value: 'Option C' },
|
||||||
|
],
|
||||||
|
placeholder: '请选择',
|
||||||
|
// 弹窗会设置body的pointer-events为none,这回影响下拉框的点击事件
|
||||||
|
popupClassName: 'pointer-events-auto',
|
||||||
|
},
|
||||||
|
content: '此弹窗演示了如何使用component传递自定义组件',
|
||||||
|
icon: 'question',
|
||||||
|
modelPropName: 'value',
|
||||||
|
}).then((val) => {
|
||||||
|
if (val) {
|
||||||
|
alert(`你选择了${val}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function sleep(ms: number) {
|
function sleep(ms: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@ -44,7 +81,6 @@ function sleep(ms: number) {
|
|||||||
function showAsyncPrompt() {
|
function showAsyncPrompt() {
|
||||||
prompt({
|
prompt({
|
||||||
async beforeClose(scope) {
|
async beforeClose(scope) {
|
||||||
console.log(scope);
|
|
||||||
if (scope.isConfirm) {
|
if (scope.isConfirm) {
|
||||||
if (scope.value) {
|
if (scope.value) {
|
||||||
// 模拟异步操作,如果不成功,可以返回false
|
// 模拟异步操作,如果不成功,可以返回false
|
||||||
@ -75,6 +111,7 @@ function showAsyncPrompt() {
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
<VbenButton @click="showPrompt">Prompt</VbenButton>
|
||||||
|
<VbenButton @click="showSlotsPrompt"> Prompt With slots </VbenButton>
|
||||||
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
<VbenButton @click="showSelectPrompt">Prompt With Select</VbenButton>
|
||||||
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
<VbenButton @click="showAsyncPrompt">Prompt With Async</VbenButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@ async function handleReset(e: Event) {
|
|||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
const props = unref(rootProps);
|
const props = unref(rootProps);
|
||||||
|
|
||||||
const values = toRaw(props.formApi?.getValues());
|
const values = toRaw(await props.formApi?.getValues());
|
||||||
|
|
||||||
if (isFunction(props.handleReset)) {
|
if (isFunction(props.handleReset)) {
|
||||||
await props.handleReset?.(values);
|
await props.handleReset?.(values);
|
||||||
|
@ -295,6 +295,7 @@ export class FormApi {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
const filteredFields = fieldMergeFn(fields, form.values);
|
const filteredFields = fieldMergeFn(fields, form.values);
|
||||||
|
this.handleStringToArrayFields(filteredFields);
|
||||||
form.setValues(filteredFields, shouldValidate);
|
form.setValues(filteredFields, shouldValidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,6 +305,7 @@ export class FormApi {
|
|||||||
const form = await this.getForm();
|
const form = await this.getForm();
|
||||||
await form.submitForm();
|
await form.submitForm();
|
||||||
const rawValues = toRaw(await this.getValues());
|
const rawValues = toRaw(await this.getValues());
|
||||||
|
this.handleArrayToStringFields(rawValues);
|
||||||
await this.state?.handleSubmit?.(rawValues);
|
await this.state?.handleSubmit?.(rawValues);
|
||||||
|
|
||||||
return rawValues;
|
return rawValues;
|
||||||
@ -392,10 +394,53 @@ export class FormApi {
|
|||||||
return this.form;
|
return this.form;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleArrayToStringFields = (originValues: Record<string, any>) => {
|
||||||
|
const arrayToStringFields = this.state?.arrayToStringFields;
|
||||||
|
if (!arrayToStringFields || !Array.isArray(arrayToStringFields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processFields = (fields: string[], separator: string = ',') => {
|
||||||
|
this.processFields(fields, separator, originValues, (value, sep) =>
|
||||||
|
Array.isArray(value) ? value.join(sep) : value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理简单数组格式 ['field1', 'field2', ';'] 或 ['field1', 'field2']
|
||||||
|
if (arrayToStringFields.every((item) => typeof item === 'string')) {
|
||||||
|
const lastItem =
|
||||||
|
arrayToStringFields[arrayToStringFields.length - 1] || '';
|
||||||
|
const fields =
|
||||||
|
lastItem.length === 1
|
||||||
|
? arrayToStringFields.slice(0, -1)
|
||||||
|
: arrayToStringFields;
|
||||||
|
const separator = lastItem.length === 1 ? lastItem : ',';
|
||||||
|
processFields(fields, separator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理嵌套数组格式 [['field1'], ';']
|
||||||
|
arrayToStringFields.forEach((fieldConfig) => {
|
||||||
|
if (Array.isArray(fieldConfig)) {
|
||||||
|
const [fields, separator = ','] = fieldConfig;
|
||||||
|
// 根据类型定义,fields 应该始终是字符串数组
|
||||||
|
if (!Array.isArray(fields)) {
|
||||||
|
console.warn(
|
||||||
|
`Invalid field configuration: fields should be an array of strings, got ${typeof fields}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processFields(fields, separator);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private handleRangeTimeValue = (originValues: Record<string, any>) => {
|
private handleRangeTimeValue = (originValues: Record<string, any>) => {
|
||||||
const values = { ...originValues };
|
const values = { ...originValues };
|
||||||
const fieldMappingTime = this.state?.fieldMappingTime;
|
const fieldMappingTime = this.state?.fieldMappingTime;
|
||||||
|
|
||||||
|
this.handleStringToArrayFields(values);
|
||||||
|
|
||||||
if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {
|
if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
@ -441,6 +486,80 @@ export class FormApi {
|
|||||||
return values;
|
return values;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleStringToArrayFields = (originValues: Record<string, any>) => {
|
||||||
|
const arrayToStringFields = this.state?.arrayToStringFields;
|
||||||
|
if (!arrayToStringFields || !Array.isArray(arrayToStringFields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processFields = (fields: string[], separator: string = ',') => {
|
||||||
|
this.processFields(fields, separator, originValues, (value, sep) => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
// 处理空字符串的情况
|
||||||
|
if (value === '') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// 处理复杂分隔符的情况
|
||||||
|
const escapedSeparator = sep.replaceAll(
|
||||||
|
/[.*+?^${}()|[\]\\]/g,
|
||||||
|
String.raw`\$&`,
|
||||||
|
);
|
||||||
|
return value.split(new RegExp(escapedSeparator));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理简单数组格式 ['field1', 'field2', ';'] 或 ['field1', 'field2']
|
||||||
|
if (arrayToStringFields.every((item) => typeof item === 'string')) {
|
||||||
|
const lastItem =
|
||||||
|
arrayToStringFields[arrayToStringFields.length - 1] || '';
|
||||||
|
const fields =
|
||||||
|
lastItem.length === 1
|
||||||
|
? arrayToStringFields.slice(0, -1)
|
||||||
|
: arrayToStringFields;
|
||||||
|
const separator = lastItem.length === 1 ? lastItem : ',';
|
||||||
|
processFields(fields, separator);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理嵌套数组格式 [['field1'], ';']
|
||||||
|
arrayToStringFields.forEach((fieldConfig) => {
|
||||||
|
if (Array.isArray(fieldConfig)) {
|
||||||
|
const [fields, separator = ','] = fieldConfig;
|
||||||
|
if (Array.isArray(fields)) {
|
||||||
|
processFields(fields, separator);
|
||||||
|
} else if (typeof originValues[fields] === 'string') {
|
||||||
|
const value = originValues[fields];
|
||||||
|
if (value === '') {
|
||||||
|
originValues[fields] = [];
|
||||||
|
} else {
|
||||||
|
const escapedSeparator = separator.replaceAll(
|
||||||
|
/[.*+?^${}()|[\]\\]/g,
|
||||||
|
String.raw`\$&`,
|
||||||
|
);
|
||||||
|
originValues[fields] = value.split(new RegExp(escapedSeparator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private processFields = (
|
||||||
|
fields: string[],
|
||||||
|
separator: string,
|
||||||
|
originValues: Record<string, any>,
|
||||||
|
transformFn: (value: any, separator: string) => any,
|
||||||
|
) => {
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const value = originValues[field];
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
originValues[field] = transformFn(value, separator);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
private updateState() {
|
private updateState() {
|
||||||
const currentSchema = this.state?.schema ?? [];
|
const currentSchema = this.state?.schema ?? [];
|
||||||
const prevSchema = this.prevState?.schema ?? [];
|
const prevSchema = this.prevState?.schema ?? [];
|
||||||
|
@ -232,6 +232,12 @@ export type FieldMappingTime = [
|
|||||||
)?,
|
)?,
|
||||||
][];
|
][];
|
||||||
|
|
||||||
|
export type ArrayToStringFields = Array<
|
||||||
|
| [string[], string?] // 嵌套数组格式,可选分隔符
|
||||||
|
| string // 单个字段,使用默认分隔符
|
||||||
|
| string[] // 简单数组格式,最后一个元素可以是分隔符
|
||||||
|
>;
|
||||||
|
|
||||||
export interface FormSchema<
|
export interface FormSchema<
|
||||||
T extends BaseFormComponentType = BaseFormComponentType,
|
T extends BaseFormComponentType = BaseFormComponentType,
|
||||||
> extends FormCommonConfig {
|
> extends FormCommonConfig {
|
||||||
@ -266,6 +272,10 @@ export interface FormFieldProps extends FormSchema {
|
|||||||
export interface FormRenderProps<
|
export interface FormRenderProps<
|
||||||
T extends BaseFormComponentType = BaseFormComponentType,
|
T extends BaseFormComponentType = BaseFormComponentType,
|
||||||
> {
|
> {
|
||||||
|
/**
|
||||||
|
* 表单字段数组映射字符串配置 默认使用","
|
||||||
|
*/
|
||||||
|
arrayToStringFields?: ArrayToStringFields;
|
||||||
/**
|
/**
|
||||||
* 是否展开,在showCollapseButton=true下生效
|
* 是否展开,在showCollapseButton=true下生效
|
||||||
*/
|
*/
|
||||||
@ -296,6 +306,10 @@ export interface FormRenderProps<
|
|||||||
* 组件集合
|
* 组件集合
|
||||||
*/
|
*/
|
||||||
componentMap: Record<BaseFormComponentType, Component>;
|
componentMap: Record<BaseFormComponentType, Component>;
|
||||||
|
/**
|
||||||
|
* 表单字段映射到时间格式
|
||||||
|
*/
|
||||||
|
fieldMappingTime?: FieldMappingTime;
|
||||||
/**
|
/**
|
||||||
* 表单实例
|
* 表单实例
|
||||||
*/
|
*/
|
||||||
@ -308,10 +322,15 @@ export interface FormRenderProps<
|
|||||||
* 表单定义
|
* 表单定义
|
||||||
*/
|
*/
|
||||||
schema?: FormSchema<T>[];
|
schema?: FormSchema<T>[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示展开/折叠
|
* 是否显示展开/折叠
|
||||||
*/
|
*/
|
||||||
showCollapseButton?: boolean;
|
showCollapseButton?: boolean;
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单栅格布局
|
* 表单栅格布局
|
||||||
* @default "grid-cols-1"
|
* @default "grid-cols-1"
|
||||||
@ -339,6 +358,11 @@ export interface VbenFormProps<
|
|||||||
* 表单操作区域class
|
* 表单操作区域class
|
||||||
*/
|
*/
|
||||||
actionWrapperClass?: ClassType;
|
actionWrapperClass?: ClassType;
|
||||||
|
/**
|
||||||
|
* 表单字段数组映射字符串配置 默认使用","
|
||||||
|
*/
|
||||||
|
arrayToStringFields?: ArrayToStringFields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表单字段映射
|
* 表单字段映射
|
||||||
*/
|
*/
|
||||||
@ -354,11 +378,15 @@ export interface VbenFormProps<
|
|||||||
/**
|
/**
|
||||||
* 表单值变化回调
|
* 表单值变化回调
|
||||||
*/
|
*/
|
||||||
handleValuesChange?: (values: Record<string, any>) => void;
|
handleValuesChange?: (
|
||||||
|
values: Record<string, any>,
|
||||||
|
fieldsChanged: string[],
|
||||||
|
) => void;
|
||||||
/**
|
/**
|
||||||
* 重置按钮参数
|
* 重置按钮参数
|
||||||
*/
|
*/
|
||||||
resetButtonOptions?: ActionButtonOptions;
|
resetButtonOptions?: ActionButtonOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示默认操作按钮
|
* 是否显示默认操作按钮
|
||||||
* @default true
|
* @default true
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
import type { ExtendedFormApi, VbenFormProps } from './types';
|
import type { ExtendedFormApi, VbenFormProps } from './types';
|
||||||
|
|
||||||
// import { toRaw, watch } from 'vue';
|
// import { toRaw, watch } from 'vue';
|
||||||
import { nextTick, onMounted, watch } from 'vue';
|
import { nextTick, onMounted, watch } from 'vue';
|
||||||
// import { isFunction } from '@vben-core/shared/utils';
|
|
||||||
|
|
||||||
import { useForwardPriorityValues } from '@vben-core/composables';
|
import { useForwardPriorityValues } from '@vben-core/composables';
|
||||||
import { cloneDeep } from '@vben-core/shared/utils';
|
import { cloneDeep, get, isEqual, set } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import { useDebounceFn } from '@vueuse/core';
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
|
||||||
@ -61,16 +62,46 @@ function handleKeyDownEnter(event: KeyboardEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
const handleValuesChangeDebounced = useDebounceFn(async () => {
|
||||||
forward.value.handleValuesChange?.(
|
|
||||||
cloneDeep(await forward.value.formApi.getValues()),
|
|
||||||
);
|
|
||||||
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
state.value.submitOnChange && forward.value.formApi?.validateAndSubmitForm();
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
|
const valuesCache: Recordable<any> = {};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 只在挂载后开始监听,form.values会有一个初始化的过程
|
// 只在挂载后开始监听,form.values会有一个初始化的过程
|
||||||
await nextTick();
|
await nextTick();
|
||||||
watch(() => form.values, handleValuesChangeDebounced, { deep: true });
|
watch(
|
||||||
|
() => form.values,
|
||||||
|
async (newVal) => {
|
||||||
|
if (forward.value.handleValuesChange) {
|
||||||
|
const fields = state.value.schema?.map((item) => {
|
||||||
|
return item.fieldName;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fields && fields.length > 0) {
|
||||||
|
const changedFields: string[] = [];
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const newFieldValue = get(newVal, field);
|
||||||
|
const oldFieldValue = get(valuesCache, field);
|
||||||
|
if (!isEqual(newFieldValue, oldFieldValue)) {
|
||||||
|
changedFields.push(field);
|
||||||
|
set(valuesCache, field, newFieldValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (changedFields.length > 0) {
|
||||||
|
// 调用handleValuesChange回调,传入所有表单值的深拷贝和变更的字段列表
|
||||||
|
forward.value.handleValuesChange(
|
||||||
|
cloneDeep(await forward.value.formApi.getValues()),
|
||||||
|
changedFields,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleValuesChangeDebounced();
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -208,6 +208,8 @@ onBeforeUnmount(() => {
|
|||||||
nsMenu.e('popup-container'),
|
nsMenu.e('popup-container'),
|
||||||
is(rootMenu.theme, true),
|
is(rootMenu.theme, true),
|
||||||
opened ? '' : 'hidden',
|
opened ? '' : 'hidden',
|
||||||
|
'overflow-auto',
|
||||||
|
'max-h-[calc(var(--radix-hover-card-content-available-height)-20px)]',
|
||||||
]"
|
]"
|
||||||
:content-props="contentProps"
|
:content-props="contentProps"
|
||||||
:open="true"
|
:open="true"
|
||||||
|
@ -7,7 +7,7 @@ import type { AlertProps, BeforeCloseScope, PromptProps } from './alert';
|
|||||||
import { h, nextTick, ref, render } from 'vue';
|
import { h, nextTick, ref, render } from 'vue';
|
||||||
|
|
||||||
import { useSimpleLocale } from '@vben-core/composables';
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
import { Input } from '@vben-core/shadcn-ui';
|
import { Input, VbenRenderContent } from '@vben-core/shadcn-ui';
|
||||||
import { isFunction, isString } from '@vben-core/shared/utils';
|
import { isFunction, isString } from '@vben-core/shared/utils';
|
||||||
|
|
||||||
import Alert from './alert.vue';
|
import Alert from './alert.vue';
|
||||||
@ -146,11 +146,7 @@ export async function vbenPrompt<T = any>(
|
|||||||
const inputComponentRef = ref<null | VNode>(null);
|
const inputComponentRef = ref<null | VNode>(null);
|
||||||
const staticContents: Component[] = [];
|
const staticContents: Component[] = [];
|
||||||
|
|
||||||
if (isString(content)) {
|
staticContents.push(h(VbenRenderContent, { content, renderBr: true }));
|
||||||
staticContents.push(h('span', content));
|
|
||||||
} else if (content) {
|
|
||||||
staticContents.push(content as Component);
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelPropName = _modelPropName || 'modelValue';
|
const modelPropName = _modelPropName || 'modelValue';
|
||||||
const componentProps = { ..._componentProps };
|
const componentProps = { ..._componentProps };
|
||||||
|
@ -2,6 +2,8 @@ import type { Component, VNode, VNodeArrayChildren } from 'vue';
|
|||||||
|
|
||||||
import type { Recordable } from '@vben-core/typings';
|
import type { Recordable } from '@vben-core/typings';
|
||||||
|
|
||||||
|
import { createContext } from '@vben-core/shadcn-ui';
|
||||||
|
|
||||||
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
export type IconType = 'error' | 'info' | 'question' | 'success' | 'warning';
|
||||||
|
|
||||||
export type BeforeCloseScope = {
|
export type BeforeCloseScope = {
|
||||||
@ -34,8 +36,14 @@ export type AlertProps = {
|
|||||||
contentClass?: string;
|
contentClass?: string;
|
||||||
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
/** 执行beforeClose回调期间,在内容区域显示一个loading遮罩*/
|
||||||
contentMasking?: boolean;
|
contentMasking?: boolean;
|
||||||
|
/** 弹窗底部内容(与按钮在同一个容器中) */
|
||||||
|
footer?: Component | string;
|
||||||
/** 弹窗的图标(在标题的前面) */
|
/** 弹窗的图标(在标题的前面) */
|
||||||
icon?: Component | IconType;
|
icon?: Component | IconType;
|
||||||
|
/**
|
||||||
|
* 弹窗遮罩模糊效果
|
||||||
|
*/
|
||||||
|
overlayBlur?: number;
|
||||||
/** 是否显示取消按钮 */
|
/** 是否显示取消按钮 */
|
||||||
showCancel?: boolean;
|
showCancel?: boolean;
|
||||||
/** 弹窗标题 */
|
/** 弹窗标题 */
|
||||||
@ -64,3 +72,28 @@ export type PromptProps<T = any> = {
|
|||||||
/** 输入组件的值属性名 */
|
/** 输入组件的值属性名 */
|
||||||
modelPropName?: string;
|
modelPropName?: string;
|
||||||
} & Omit<AlertProps, 'beforeClose'>;
|
} & Omit<AlertProps, 'beforeClose'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alert上下文
|
||||||
|
*/
|
||||||
|
export type AlertContext = {
|
||||||
|
/** 执行取消操作 */
|
||||||
|
doCancel: () => void;
|
||||||
|
/** 执行确认操作 */
|
||||||
|
doConfirm: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const [injectAlertContext, provideAlertContext] =
|
||||||
|
createContext<AlertContext>('VbenAlertContext');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Alert上下文
|
||||||
|
* @returns AlertContext
|
||||||
|
*/
|
||||||
|
export function useAlertContext() {
|
||||||
|
const context = injectAlertContext();
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useAlertContext must be used within an AlertProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@ import type { Component } from 'vue';
|
|||||||
|
|
||||||
import type { AlertProps } from './alert';
|
import type { AlertProps } from './alert';
|
||||||
|
|
||||||
import { computed, h, nextTick, ref, watch } from 'vue';
|
import { computed, h, nextTick, ref } from 'vue';
|
||||||
|
|
||||||
import { useSimpleLocale } from '@vben-core/composables';
|
import { useSimpleLocale } from '@vben-core/composables';
|
||||||
import {
|
import {
|
||||||
@ -28,6 +28,8 @@ import {
|
|||||||
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';
|
||||||
|
|
||||||
|
import { provideAlertContext } from './alert';
|
||||||
|
|
||||||
const props = withDefaults(defineProps<AlertProps>(), {
|
const props = withDefaults(defineProps<AlertProps>(), {
|
||||||
bordered: true,
|
bordered: true,
|
||||||
buttonAlign: 'end',
|
buttonAlign: 'end',
|
||||||
@ -39,14 +41,12 @@ const open = defineModel<boolean>('open', { default: false });
|
|||||||
const { $t } = useSimpleLocale();
|
const { $t } = useSimpleLocale();
|
||||||
const components = globalShareState.getComponents();
|
const components = globalShareState.getComponents();
|
||||||
const isConfirm = ref(false);
|
const isConfirm = ref(false);
|
||||||
watch(open, async (val) => {
|
|
||||||
await nextTick();
|
function onAlertClosed() {
|
||||||
if (val) {
|
|
||||||
isConfirm.value = false;
|
|
||||||
} else {
|
|
||||||
emits('closed', isConfirm.value);
|
emits('closed', isConfirm.value);
|
||||||
|
isConfirm.value = false;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
const getIconRender = computed(() => {
|
const getIconRender = computed(() => {
|
||||||
let iconRender: Component | null = null;
|
let iconRender: Component | null = null;
|
||||||
if (props.icon) {
|
if (props.icon) {
|
||||||
@ -89,6 +89,23 @@ const getIconRender = computed(() => {
|
|||||||
}
|
}
|
||||||
return iconRender;
|
return iconRender;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function doCancel() {
|
||||||
|
isConfirm.value = false;
|
||||||
|
handleOpenChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doConfirm() {
|
||||||
|
isConfirm.value = true;
|
||||||
|
handleOpenChange(false);
|
||||||
|
emits('confirm');
|
||||||
|
}
|
||||||
|
|
||||||
|
provideAlertContext({
|
||||||
|
doCancel,
|
||||||
|
doConfirm,
|
||||||
|
});
|
||||||
|
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
isConfirm.value = true;
|
isConfirm.value = true;
|
||||||
emits('confirm');
|
emits('confirm');
|
||||||
@ -100,6 +117,7 @@ function handleCancel() {
|
|||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
async function handleOpenChange(val: boolean) {
|
async function handleOpenChange(val: boolean) {
|
||||||
|
await nextTick();
|
||||||
if (!val && props.beforeClose) {
|
if (!val && props.beforeClose) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -120,15 +138,16 @@ async function handleOpenChange(val: boolean) {
|
|||||||
<AlertDialogContent
|
<AlertDialogContent
|
||||||
:open="open"
|
:open="open"
|
||||||
:centered="centered"
|
:centered="centered"
|
||||||
|
:overlay-blur="overlayBlur"
|
||||||
@opened="emits('opened')"
|
@opened="emits('opened')"
|
||||||
|
@closed="onAlertClosed"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
containerClass,
|
containerClass,
|
||||||
'left-0 right-0 top-[10vh] mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
'left-0 right-0 mx-auto flex max-h-[80%] flex-col p-0 duration-300 sm:rounded-[var(--radius)] md:w-[520px] md:max-w-[80%]',
|
||||||
{
|
{
|
||||||
'border-border border': bordered,
|
'border-border border': bordered,
|
||||||
'shadow-3xl': !bordered,
|
'shadow-3xl': !bordered,
|
||||||
'top-1/2 !-translate-y-1/2': centered,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@ -152,12 +171,16 @@ async function handleOpenChange(val: boolean) {
|
|||||||
</div>
|
</div>
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>
|
||||||
<div class="m-4 mb-6 min-h-[30px]">
|
<div class="m-4 min-h-[30px]">
|
||||||
<VbenRenderContent :content="content" render-br />
|
<VbenRenderContent :content="content" render-br />
|
||||||
</div>
|
</div>
|
||||||
<VbenLoading v-if="loading && contentMasking" :spinning="loading" />
|
<VbenLoading v-if="loading && contentMasking" :spinning="loading" />
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
<div class="flex justify-end gap-x-2" :class="`justify-${buttonAlign}`">
|
<div
|
||||||
|
class="flex items-center justify-end gap-x-2"
|
||||||
|
:class="`justify-${buttonAlign}`"
|
||||||
|
>
|
||||||
|
<VbenRenderContent :content="footer" />
|
||||||
<AlertDialogCancel v-if="showCancel" as-child>
|
<AlertDialogCancel v-if="showCancel" as-child>
|
||||||
<component
|
<component
|
||||||
:is="components.DefaultButton || VbenButton"
|
:is="components.DefaultButton || VbenButton"
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
export * from './alert';
|
export type {
|
||||||
|
AlertProps,
|
||||||
|
BeforeCloseScope,
|
||||||
|
IconType,
|
||||||
|
PromptProps,
|
||||||
|
} from './alert';
|
||||||
|
export { useAlertContext } from './alert';
|
||||||
export { default as Alert } from './alert.vue';
|
export { default as Alert } from './alert.vue';
|
||||||
export {
|
export {
|
||||||
vbenAlert as alert,
|
vbenAlert as alert,
|
||||||
|
@ -9,7 +9,11 @@ vi.mock('@vben-core/shared/store', () => {
|
|||||||
return {
|
return {
|
||||||
isFunction: (fn: any) => typeof fn === 'function',
|
isFunction: (fn: any) => typeof fn === 'function',
|
||||||
Store: class {
|
Store: class {
|
||||||
|
get state() {
|
||||||
|
return this._state;
|
||||||
|
}
|
||||||
private _state: DrawerState;
|
private _state: DrawerState;
|
||||||
|
|
||||||
private options: any;
|
private options: any;
|
||||||
|
|
||||||
constructor(initialState: DrawerState, options: any) {
|
constructor(initialState: DrawerState, options: any) {
|
||||||
@ -25,10 +29,6 @@ vi.mock('@vben-core/shared/store', () => {
|
|||||||
this._state = fn(this._state);
|
this._state = fn(this._state);
|
||||||
this.options.onUpdate();
|
this.options.onUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
get state() {
|
|
||||||
return this._state;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -54,7 +54,6 @@ describe('drawerApi', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should close the drawer if onBeforeClose allows it', () => {
|
it('should close the drawer if onBeforeClose allows it', () => {
|
||||||
drawerApi.open();
|
|
||||||
drawerApi.close();
|
drawerApi.close();
|
||||||
expect(drawerApi.store.state.isOpen).toBe(false);
|
expect(drawerApi.store.state.isOpen).toBe(false);
|
||||||
});
|
});
|
||||||
|
@ -86,7 +86,8 @@ export class DrawerApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭弹窗
|
* 关闭抽屉
|
||||||
|
* @description 关闭抽屉时会调用 onBeforeClose 钩子函数,如果 onBeforeClose 返回 false,则不关闭弹窗
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
// 通过 onBeforeClose 钩子函数来判断是否允许关闭弹窗
|
||||||
|
@ -274,7 +274,7 @@ const getAppendTo = computed(() => {
|
|||||||
{{ cancelText || $t('cancel') }}
|
{{ cancelText || $t('cancel') }}
|
||||||
</slot>
|
</slot>
|
||||||
</component>
|
</component>
|
||||||
|
<slot name="center-footer"></slot>
|
||||||
<component
|
<component
|
||||||
:is="components.PrimaryButton || VbenButton"
|
:is="components.PrimaryButton || VbenButton"
|
||||||
v-if="showConfirmButton"
|
v-if="showConfirmButton"
|
||||||
|
@ -44,6 +44,7 @@ export class ModalApi {
|
|||||||
confirmDisabled: false,
|
confirmDisabled: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
contentClass: '',
|
contentClass: '',
|
||||||
|
destroyOnClose: true,
|
||||||
draggable: false,
|
draggable: false,
|
||||||
footer: true,
|
footer: true,
|
||||||
footerClass: '',
|
footerClass: '',
|
||||||
|
@ -60,6 +60,10 @@ export interface ModalProps {
|
|||||||
* 弹窗描述
|
* 弹窗描述
|
||||||
*/
|
*/
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/**
|
||||||
|
* 在关闭时销毁弹窗
|
||||||
|
*/
|
||||||
|
destroyOnClose?: boolean;
|
||||||
/**
|
/**
|
||||||
* 是否可拖拽
|
* 是否可拖拽
|
||||||
* @default false
|
* @default false
|
||||||
@ -153,10 +157,6 @@ export interface ModalApiOptions extends ModalState {
|
|||||||
* 独立的弹窗组件
|
* 独立的弹窗组件
|
||||||
*/
|
*/
|
||||||
connectedComponent?: Component;
|
connectedComponent?: Component;
|
||||||
/**
|
|
||||||
* 在关闭时销毁弹窗。仅在使用 connectedComponent 时有效
|
|
||||||
*/
|
|
||||||
destroyOnClose?: boolean;
|
|
||||||
/**
|
/**
|
||||||
* 关闭前的回调,返回 false 可以阻止关闭
|
* 关闭前的回调,返回 false 可以阻止关闭
|
||||||
* @returns
|
* @returns
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ExtendedModalApi, ModalProps } from './modal';
|
import type { ExtendedModalApi, ModalProps } from './modal';
|
||||||
|
|
||||||
import { computed, nextTick, provide, ref, useId, watch } from 'vue';
|
import { computed, nextTick, provide, ref, unref, useId, watch } from 'vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useIsMobile,
|
useIsMobile,
|
||||||
@ -34,6 +34,7 @@ interface Props extends ModalProps {
|
|||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
appendToMain: false,
|
appendToMain: false,
|
||||||
|
destroyOnClose: true,
|
||||||
modalApi: undefined,
|
modalApi: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,6 +68,7 @@ const {
|
|||||||
confirmText,
|
confirmText,
|
||||||
contentClass,
|
contentClass,
|
||||||
description,
|
description,
|
||||||
|
destroyOnClose,
|
||||||
draggable,
|
draggable,
|
||||||
footer: showFooter,
|
footer: showFooter,
|
||||||
footerClass,
|
footerClass,
|
||||||
@ -100,10 +102,15 @@ const { dragging, transform } = useModalDraggable(
|
|||||||
shouldDraggable,
|
shouldDraggable,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const firstOpened = ref(false);
|
||||||
|
const isClosed = ref(true);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => state?.value?.isOpen,
|
() => state?.value?.isOpen,
|
||||||
async (v) => {
|
async (v) => {
|
||||||
if (v) {
|
if (v) {
|
||||||
|
isClosed.value = false;
|
||||||
|
if (!firstOpened.value) firstOpened.value = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (!contentRef.value) return;
|
if (!contentRef.value) return;
|
||||||
const innerContentRef = contentRef.value.getContentRef();
|
const innerContentRef = contentRef.value.getContentRef();
|
||||||
@ -113,6 +120,7 @@ watch(
|
|||||||
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@ -176,6 +184,15 @@ const getAppendTo = computed(() => {
|
|||||||
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
|
||||||
: undefined;
|
: undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getForceMount = computed(() => {
|
||||||
|
return !unref(destroyOnClose) && unref(firstOpened);
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleClosed() {
|
||||||
|
isClosed.value = true;
|
||||||
|
props.modalApi?.onClosed();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<Dialog
|
||||||
@ -197,9 +214,11 @@ const getAppendTo = computed(() => {
|
|||||||
shouldFullscreen,
|
shouldFullscreen,
|
||||||
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
|
||||||
'duration-300': !dragging,
|
'duration-300': !dragging,
|
||||||
|
hidden: isClosed,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
:force-mount="getForceMount"
|
||||||
:modal="modal"
|
:modal="modal"
|
||||||
:open="state?.isOpen"
|
:open="state?.isOpen"
|
||||||
:show-close="closable"
|
:show-close="closable"
|
||||||
@ -207,7 +226,7 @@ const getAppendTo = computed(() => {
|
|||||||
:overlay-blur="overlayBlur"
|
:overlay-blur="overlayBlur"
|
||||||
close-class="top-3"
|
close-class="top-3"
|
||||||
@close-auto-focus="handleFocusOutside"
|
@close-auto-focus="handleFocusOutside"
|
||||||
@closed="() => modalApi?.onClosed()"
|
@closed="handleClosed"
|
||||||
:close-disabled="submitting"
|
:close-disabled="submitting"
|
||||||
@escape-key-down="escapeKeyDown"
|
@escape-key-down="escapeKeyDown"
|
||||||
@focus-outside="handleFocusOutside"
|
@focus-outside="handleFocusOutside"
|
||||||
@ -302,7 +321,7 @@ const getAppendTo = computed(() => {
|
|||||||
{{ cancelText || $t('cancel') }}
|
{{ cancelText || $t('cancel') }}
|
||||||
</slot>
|
</slot>
|
||||||
</component>
|
</component>
|
||||||
|
<slot name="center-footer"></slot>
|
||||||
<component
|
<component
|
||||||
:is="components.PrimaryButton || VbenButton"
|
:is="components.PrimaryButton || VbenButton"
|
||||||
v-if="showConfirmButton"
|
v-if="showConfirmButton"
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
|
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
|
||||||
|
|
||||||
import {
|
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
|
||||||
defineComponent,
|
|
||||||
h,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
provide,
|
|
||||||
reactive,
|
|
||||||
ref,
|
|
||||||
} from 'vue';
|
|
||||||
|
|
||||||
import { useStore } from '@vben-core/shared/store';
|
import { useStore } from '@vben-core/shared/store';
|
||||||
|
|
||||||
@ -32,7 +24,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
const { connectedComponent } = options;
|
const { connectedComponent } = options;
|
||||||
if (connectedComponent) {
|
if (connectedComponent) {
|
||||||
const extendedApi = reactive({});
|
const extendedApi = reactive({});
|
||||||
const isModalReady = ref(true);
|
|
||||||
const Modal = defineComponent(
|
const Modal = defineComponent(
|
||||||
(props: TParentModalProps, { attrs, slots }) => {
|
(props: TParentModalProps, { attrs, slots }) => {
|
||||||
provide(USER_MODAL_INJECT_KEY, {
|
provide(USER_MODAL_INJECT_KEY, {
|
||||||
@ -42,11 +33,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
Object.setPrototypeOf(extendedApi, api);
|
Object.setPrototypeOf(extendedApi, api);
|
||||||
},
|
},
|
||||||
options,
|
options,
|
||||||
async reCreateModal() {
|
|
||||||
isModalReady.value = false;
|
|
||||||
await nextTick();
|
|
||||||
isModalReady.value = true;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
checkProps(extendedApi as ExtendedModalApi, {
|
checkProps(extendedApi as ExtendedModalApi, {
|
||||||
...props,
|
...props,
|
||||||
@ -55,7 +41,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
});
|
});
|
||||||
return () =>
|
return () =>
|
||||||
h(
|
h(
|
||||||
isModalReady.value ? connectedComponent : 'div',
|
connectedComponent,
|
||||||
{
|
{
|
||||||
...props,
|
...props,
|
||||||
...attrs,
|
...attrs,
|
||||||
@ -84,14 +70,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
|
|||||||
injectData.options?.onOpenChange?.(isOpen);
|
injectData.options?.onOpenChange?.(isOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClosed = mergedOptions.onClosed;
|
|
||||||
|
|
||||||
mergedOptions.onClosed = () => {
|
|
||||||
onClosed?.();
|
|
||||||
if (mergedOptions.destroyOnClose) {
|
|
||||||
injectData.reCreateModal?.();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const api = new ModalApi(mergedOptions);
|
const api = new ModalApi(mergedOptions);
|
||||||
|
|
||||||
const extendedApi: ExtendedModalApi = api as never;
|
const extendedApi: ExtendedModalApi = api as never;
|
||||||
|
@ -31,12 +31,11 @@ export default defineComponent({
|
|||||||
if (props.renderBr && isString(props.content)) {
|
if (props.renderBr && isString(props.content)) {
|
||||||
const lines = props.content.split('\n');
|
const lines = props.content.split('\n');
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (const [i, line] of lines.entries()) {
|
||||||
const line = lines[i];
|
result.push(h('p', { key: i }, line));
|
||||||
result.push(h('span', { key: i }, line));
|
// if (i < lines.length - 1) {
|
||||||
if (i < lines.length - 1) {
|
// result.push(h('br'));
|
||||||
result.push(h('br'));
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
|
@ -39,6 +39,14 @@ const isAtRight = ref(false);
|
|||||||
const isAtBottom = ref(false);
|
const isAtBottom = ref(false);
|
||||||
const isAtLeft = ref(true);
|
const isAtLeft = ref(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We have to check if the scroll amount is close enough to some threshold in order to
|
||||||
|
* more accurately calculate arrivedState. This is because scrollTop/scrollLeft are non-rounded
|
||||||
|
* numbers, while scrollHeight/scrollWidth and clientHeight/clientWidth are rounded.
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
|
||||||
|
*/
|
||||||
|
const ARRIVED_STATE_THRESHOLD_PIXELS = 1;
|
||||||
|
|
||||||
const showShadowTop = computed(() => props.shadow && props.shadowTop);
|
const showShadowTop = computed(() => props.shadow && props.shadowTop);
|
||||||
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
|
const showShadowBottom = computed(() => props.shadow && props.shadowBottom);
|
||||||
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
|
const showShadowLeft = computed(() => props.shadow && props.shadowLeft);
|
||||||
@ -60,14 +68,18 @@ function handleScroll(event: Event) {
|
|||||||
const target = event.target as HTMLElement;
|
const target = event.target as HTMLElement;
|
||||||
const scrollTop = target?.scrollTop ?? 0;
|
const scrollTop = target?.scrollTop ?? 0;
|
||||||
const scrollLeft = target?.scrollLeft ?? 0;
|
const scrollLeft = target?.scrollLeft ?? 0;
|
||||||
const offsetHeight = target?.offsetHeight ?? 0;
|
const clientHeight = target?.clientHeight ?? 0;
|
||||||
const offsetWidth = target?.offsetWidth ?? 0;
|
const clientWidth = target?.clientWidth ?? 0;
|
||||||
const scrollHeight = target?.scrollHeight ?? 0;
|
const scrollHeight = target?.scrollHeight ?? 0;
|
||||||
const scrollWidth = target?.scrollWidth ?? 0;
|
const scrollWidth = target?.scrollWidth ?? 0;
|
||||||
isAtTop.value = scrollTop <= 0;
|
isAtTop.value = scrollTop <= 0;
|
||||||
isAtLeft.value = scrollLeft <= 0;
|
isAtLeft.value = scrollLeft <= 0;
|
||||||
isAtBottom.value = scrollTop + offsetHeight >= scrollHeight;
|
isAtBottom.value =
|
||||||
isAtRight.value = scrollLeft + offsetWidth >= scrollWidth;
|
Math.abs(scrollTop) + clientHeight >=
|
||||||
|
scrollHeight - ARRIVED_STATE_THRESHOLD_PIXELS;
|
||||||
|
isAtRight.value =
|
||||||
|
Math.abs(scrollLeft) + clientWidth >=
|
||||||
|
scrollWidth - ARRIVED_STATE_THRESHOLD_PIXELS;
|
||||||
|
|
||||||
emit('scrollAt', {
|
emit('scrollAt', {
|
||||||
bottom: isAtBottom.value,
|
bottom: isAtBottom.value,
|
||||||
|
@ -61,7 +61,7 @@ defineExpose({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<Transition name="fade">
|
<Transition name="fade" appear>
|
||||||
<AlertDialogOverlay
|
<AlertDialogOverlay
|
||||||
v-if="open && modal"
|
v-if="open && modal"
|
||||||
:style="{
|
:style="{
|
||||||
@ -80,7 +80,17 @@ defineExpose({
|
|||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
'z-popup bg-background w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||||
|
'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
|
||||||
|
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
||||||
|
{
|
||||||
|
'data-[state=open]:slide-in-from-top-[48%] data-[state=closed]:slide-out-to-top-[48%]':
|
||||||
|
!centered,
|
||||||
|
'data-[state=open]:slide-in-from-top-[98%] data-[state=closed]:slide-out-to-top-[148%]':
|
||||||
|
centered,
|
||||||
|
'top-[10vh]': !centered,
|
||||||
|
'top-1/2 -translate-y-1/2': centered,
|
||||||
|
},
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
@ -54,6 +54,20 @@ interface Props {
|
|||||||
visibleEvent?: string;
|
visibleEvent?: string;
|
||||||
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
/** 组件的v-model属性名,默认为modelValue。部分组件可能为value */
|
||||||
modelPropName?: string;
|
modelPropName?: string;
|
||||||
|
/**
|
||||||
|
* 自动选择
|
||||||
|
* - `first`:自动选择第一个选项
|
||||||
|
* - `last`:自动选择最后一个选项
|
||||||
|
* - `one`: 当请求的结果只有一个选项时,自动选择该选项
|
||||||
|
* - 函数:自定义选择逻辑,函数的参数为请求的结果数组,返回值为选择的选项
|
||||||
|
* - false:不自动选择(默认)
|
||||||
|
*/
|
||||||
|
autoSelect?:
|
||||||
|
| 'first'
|
||||||
|
| 'last'
|
||||||
|
| 'one'
|
||||||
|
| ((item: OptionsItem[]) => OptionsItem)
|
||||||
|
| false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
||||||
@ -74,6 +88,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
afterFetch: undefined,
|
afterFetch: undefined,
|
||||||
modelPropName: 'modelValue',
|
modelPropName: 'modelValue',
|
||||||
api: undefined,
|
api: undefined,
|
||||||
|
autoSelect: false,
|
||||||
options: () => [],
|
options: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -81,7 +96,7 @@ const emit = defineEmits<{
|
|||||||
optionsChange: [OptionsItem[]];
|
optionsChange: [OptionsItem[]];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modelValue = defineModel({ default: '' });
|
const modelValue = defineModel<any>({ default: undefined });
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
const innerParams = ref({});
|
const innerParams = ref({});
|
||||||
@ -194,10 +209,43 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
function emitChange() {
|
function emitChange() {
|
||||||
|
if (
|
||||||
|
modelValue.value === undefined &&
|
||||||
|
props.autoSelect &&
|
||||||
|
unref(getOptions).length > 0
|
||||||
|
) {
|
||||||
|
let firstOption;
|
||||||
|
if (isFunction(props.autoSelect)) {
|
||||||
|
firstOption = props.autoSelect(unref(getOptions));
|
||||||
|
} else {
|
||||||
|
switch (props.autoSelect) {
|
||||||
|
case 'first': {
|
||||||
|
firstOption = unref(getOptions)[0];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'last': {
|
||||||
|
firstOption = unref(getOptions)[unref(getOptions).length - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'one': {
|
||||||
|
if (unref(getOptions).length === 1) {
|
||||||
|
firstOption = unref(getOptions)[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstOption) modelValue.value = firstOption.value;
|
||||||
|
}
|
||||||
emit('optionsChange', unref(getOptions));
|
emit('optionsChange', unref(getOptions));
|
||||||
}
|
}
|
||||||
const componentRef = ref();
|
const componentRef = ref();
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
/** 获取options数据 */
|
||||||
|
getOptions: () => unref(getOptions),
|
||||||
|
/** 获取当前值 */
|
||||||
|
getValue: () => unref(modelValue),
|
||||||
/** 获取被包装的组件实例 */
|
/** 获取被包装的组件实例 */
|
||||||
getComponentRef: <T = any,>() => componentRef.value as T,
|
getComponentRef: <T = any,>() => componentRef.value as T,
|
||||||
/** 更新Api参数 */
|
/** 更新Api参数 */
|
||||||
|
@ -2,6 +2,8 @@ import { addIcon } from '@vben-core/icons';
|
|||||||
|
|
||||||
import schedule from '@iconify/icons-akar-icons/schedule';
|
import schedule from '@iconify/icons-akar-icons/schedule';
|
||||||
import settingOutline from '@iconify/icons-ant-design/setting-outlined';
|
import settingOutline from '@iconify/icons-ant-design/setting-outlined';
|
||||||
|
import antdTool from '@iconify/icons-ant-design/tool-outlined';
|
||||||
|
import UserAntd from '@iconify/icons-ant-design/user-outlined';
|
||||||
import Operation from '@iconify/icons-arcticons/one-hand-operation';
|
import Operation from '@iconify/icons-arcticons/one-hand-operation';
|
||||||
import BaseLineHousesFill from '@iconify/icons-bi/houses-fill';
|
import BaseLineHousesFill from '@iconify/icons-bi/houses-fill';
|
||||||
import BxPackage from '@iconify/icons-bx/package';
|
import BxPackage from '@iconify/icons-bx/package';
|
||||||
@ -39,11 +41,15 @@ import workflowOutline from '@iconify/icons-mdi/workflow-outline';
|
|||||||
import DepartmentLine from '@iconify/icons-mingcute/department-line';
|
import DepartmentLine from '@iconify/icons-mingcute/department-line';
|
||||||
import profileLine from '@iconify/icons-mingcute/profile-line';
|
import profileLine from '@iconify/icons-mingcute/profile-line';
|
||||||
import UserDuotone from '@iconify/icons-ph/user-duotone';
|
import UserDuotone from '@iconify/icons-ph/user-duotone';
|
||||||
|
import userList from '@iconify/icons-ph/user-list';
|
||||||
|
import users from '@iconify/icons-ph/users-light';
|
||||||
import insatnceLine from '@iconify/icons-ri/instance-line';
|
import insatnceLine from '@iconify/icons-ri/instance-line';
|
||||||
import todoLine from '@iconify/icons-ri/todo-line';
|
import todoLine from '@iconify/icons-ri/todo-line';
|
||||||
import Authy from '@iconify/icons-simple-icons/authy';
|
import Authy from '@iconify/icons-simple-icons/authy';
|
||||||
import FolderWithFilesOutline from '@iconify/icons-solar/folder-with-files-outline';
|
import FolderWithFilesOutline from '@iconify/icons-solar/folder-with-files-outline';
|
||||||
import monitorBoldDuotone from '@iconify/icons-solar/monitor-bold-duotone';
|
import monitorBoldDuotone from '@iconify/icons-solar/monitor-bold-duotone';
|
||||||
|
import monitorCameraOutlined from '@iconify/icons-solar/monitor-camera-outline';
|
||||||
|
import monitorPhoneOutlined from '@iconify/icons-solar/monitor-smartphone-outline';
|
||||||
import InterfaceLoginDialPadFingerPasswordDialPadDotFinger from '@iconify/icons-streamline/interface-login-dial-pad-finger-password-dial-pad-dot-finger';
|
import InterfaceLoginDialPadFingerPasswordDialPadDotFinger from '@iconify/icons-streamline/interface-login-dial-pad-finger-password-dial-pad-dot-finger';
|
||||||
import categoryPlus from '@iconify/icons-tabler/category-plus';
|
import categoryPlus from '@iconify/icons-tabler/category-plus';
|
||||||
import code from '@iconify/icons-tabler/code';
|
import code from '@iconify/icons-tabler/code';
|
||||||
@ -53,6 +59,7 @@ import code from '@iconify/icons-tabler/code';
|
|||||||
*/
|
*/
|
||||||
addIcon('eos-icons:system-group', SystemGroup);
|
addIcon('eos-icons:system-group', SystemGroup);
|
||||||
addIcon('ph:user-duotone', UserDuotone);
|
addIcon('ph:user-duotone', UserDuotone);
|
||||||
|
addIcon('ant-design:user-outlined', UserAntd);
|
||||||
addIcon('eos-icons:role-binding-outlined', RoleBindingOutlined);
|
addIcon('eos-icons:role-binding-outlined', RoleBindingOutlined);
|
||||||
addIcon('ic:sharp-menu', MenuSharp);
|
addIcon('ic:sharp-menu', MenuSharp);
|
||||||
addIcon('mingcute:department-line', DepartmentLine);
|
addIcon('mingcute:department-line', DepartmentLine);
|
||||||
@ -68,15 +75,20 @@ addIcon(
|
|||||||
);
|
);
|
||||||
addIcon('solar:folder-with-files-outline', FolderWithFilesOutline);
|
addIcon('solar:folder-with-files-outline', FolderWithFilesOutline);
|
||||||
addIcon('simple-icons:authy', Authy);
|
addIcon('simple-icons:authy', Authy);
|
||||||
|
addIcon('solar:monitor-smartphone-outline', monitorPhoneOutlined);
|
||||||
addIcon('ic:baseline-house', BaseLineHouse);
|
addIcon('ic:baseline-house', BaseLineHouse);
|
||||||
|
addIcon('ph:users-light', users);
|
||||||
addIcon('bi:houses-fill', BaseLineHousesFill);
|
addIcon('bi:houses-fill', BaseLineHousesFill);
|
||||||
|
addIcon('ph:user-list', userList);
|
||||||
addIcon('bx:package', BxPackage);
|
addIcon('bx:package', BxPackage);
|
||||||
addIcon('solar:monitor-bold-duotone', monitorBoldDuotone);
|
addIcon('solar:monitor-bold-duotone', monitorBoldDuotone);
|
||||||
|
addIcon('solar:monitor-camera-outline', monitorCameraOutlined);
|
||||||
addIcon('material-symbols:generating-tokens-outline', generatingTokensOutline);
|
addIcon('material-symbols:generating-tokens-outline', generatingTokensOutline);
|
||||||
addIcon('devicon:redis-wordmark', redisWordmark);
|
addIcon('devicon:redis-wordmark', redisWordmark);
|
||||||
addIcon('devicon:spring-wordmark', springWordmark);
|
addIcon('devicon:spring-wordmark', springWordmark);
|
||||||
addIcon('akar-icons:schedule', schedule);
|
addIcon('akar-icons:schedule', schedule);
|
||||||
addIcon('mdi:tools', tools);
|
addIcon('mdi:tools', tools);
|
||||||
|
addIcon('ant-design:tool-outlined', antdTool);
|
||||||
addIcon('tabler:code', code);
|
addIcon('tabler:code', code);
|
||||||
addIcon('flat-color-icons:plus', plus);
|
addIcon('flat-color-icons:plus', plus);
|
||||||
addIcon('devicon:vscode', vscode);
|
addIcon('devicon:vscode', vscode);
|
||||||
|
27
packages/icons/src/svg/icons/snail-job.svg
Normal file
27
packages/icons/src/svg/icons/snail-job.svg
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="715.000000pt" height="697.000000pt" viewBox="0 0 715.000000 697.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,697.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#1366ff" stroke="none">
|
||||||
|
<path d="M3430 6914 c-243 -21 -310 -28 -410 -45 -1406 -237 -2524 -1268
|
||||||
|
-2869 -2644 -76 -305 -104 -534 -104 -850 -1 -331 27 -558 104 -867 99 -394
|
||||||
|
260 -760 479 -1091 139 -209 251 -347 425 -526 298 -305 620 -538 992 -718 83
|
||||||
|
-40 157 -73 164 -73 6 0 54 -13 105 -30 447 -143 922 -37 1384 309 41 31 129
|
||||||
|
108 194 171 101 97 124 126 152 185 156 339 146 690 -32 1109 -86 201 -259
|
||||||
|
461 -433 650 -30 34 -107 96 -171 140 -245 167 -437 321 -543 434 -63 68 -161
|
||||||
|
219 -187 289 -28 75 -36 175 -20 244 18 78 72 187 120 242 41 46 42 48 35 100
|
||||||
|
-16 117 -178 620 -206 638 -6 3 -33 9 -60 12 -105 13 -183 69 -227 166 -21 45
|
||||||
|
-24 64 -20 121 11 155 135 260 292 248 183 -15 301 -192 237 -358 -12 -30 -21
|
||||||
|
-65 -21 -77 0 -31 82 -185 142 -266 139 -188 300 -276 484 -264 106 6 133 20
|
||||||
|
164 82 23 45 25 62 25 170 0 77 -7 150 -19 205 -25 116 -113 384 -137 414 -11
|
||||||
|
13 -41 32 -68 41 -147 49 -233 167 -233 321 0 131 72 243 195 300 78 36 190
|
||||||
|
34 272 -5 35 -16 78 -48 103 -75 103 -112 118 -244 45 -394 -33 -67 -30 -81
|
||||||
|
51 -246 182 -368 522 -768 1011 -1187 392 -337 649 -654 849 -1051 156 -310
|
||||||
|
241 -579 302 -963 10 -61 18 -197 21 -346 6 -260 -3 -395 -38 -593 -11 -60
|
||||||
|
-18 -110 -16 -112 8 -8 317 317 401 421 189 235 386 566 504 845 130 308 227
|
||||||
|
689 264 1040 18 173 15 547 -6 720 -132 1110 -735 2061 -1673 2640 -133 82
|
||||||
|
-374 205 -522 265 -297 121 -671 213 -991 245 -114 11 -438 20 -510 14z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -15,6 +15,7 @@ const SvgMaxKeyIcon = createIconifyIcon('svg:max-key');
|
|||||||
const SvgTopiamIcon = createIconifyIcon('svg:topiam');
|
const SvgTopiamIcon = createIconifyIcon('svg:topiam');
|
||||||
const SvgWechatIcon = createIconifyIcon('svg:wechat');
|
const SvgWechatIcon = createIconifyIcon('svg:wechat');
|
||||||
const SvgQQIcon = createIconifyIcon('svg:qq');
|
const SvgQQIcon = createIconifyIcon('svg:qq');
|
||||||
|
const SvgSnailJobIcon = createIconifyIcon('svg:snail-job');
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SvgAntdvLogoIcon,
|
SvgAntdvLogoIcon,
|
||||||
@ -28,6 +29,7 @@ export {
|
|||||||
SvgDownloadIcon,
|
SvgDownloadIcon,
|
||||||
SvgMaxKeyIcon,
|
SvgMaxKeyIcon,
|
||||||
SvgQQIcon,
|
SvgQQIcon,
|
||||||
|
SvgSnailJobIcon,
|
||||||
SvgTopiamIcon,
|
SvgTopiamIcon,
|
||||||
SvgWechatIcon,
|
SvgWechatIcon,
|
||||||
};
|
};
|
||||||
|
@ -212,7 +212,12 @@ setupVbenVxeTable({
|
|||||||
Popconfirm,
|
Popconfirm,
|
||||||
{
|
{
|
||||||
getPopupContainer(el) {
|
getPopupContainer(el) {
|
||||||
return el.closest('tbody') || document.body;
|
return (
|
||||||
|
el
|
||||||
|
.closest('.vxe-table--viewport-wrapper')
|
||||||
|
?.querySelector('.vxe-table--main-wrapper')
|
||||||
|
?.querySelector('tbody') || document.body
|
||||||
|
);
|
||||||
},
|
},
|
||||||
placement: 'topLeft',
|
placement: 'topLeft',
|
||||||
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
|
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
|
||||||
|
@ -30,5 +30,6 @@ function lockDrawer() {
|
|||||||
<Button type="primary" @click="lockDrawer">锁定抽屉状态</Button>
|
<Button type="primary" @click="lockDrawer">锁定抽屉状态</Button>
|
||||||
<!-- <template #prepend-footer> slot </template> -->
|
<!-- <template #prepend-footer> slot </template> -->
|
||||||
<!-- <template #append-footer> prepend slot </template> -->
|
<!-- <template #append-footer> prepend slot </template> -->
|
||||||
|
<!-- <template #center-footer> center slot </template> -->
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,6 +42,9 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],
|
fieldMappingTime: [['rangePicker', ['startTime', 'endTime'], 'YYYY-MM-DD']],
|
||||||
// 提交函数
|
// 提交函数
|
||||||
handleSubmit: onSubmit,
|
handleSubmit: onSubmit,
|
||||||
|
handleValuesChange(_values, fieldsChanged) {
|
||||||
|
message.info(`表单以下字段发生变化:${fieldsChanged.join(',')}`);
|
||||||
|
},
|
||||||
|
|
||||||
// 垂直布局,label和input在不同行,值为vertical
|
// 垂直布局,label和input在不同行,值为vertical
|
||||||
// 水平布局,label和input在同一行
|
// 水平布局,label和input在同一行
|
||||||
@ -74,6 +77,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||||||
},
|
},
|
||||||
// 菜单接口
|
// 菜单接口
|
||||||
api: getAllMenusApi,
|
api: getAllMenusApi,
|
||||||
|
autoSelect: 'first',
|
||||||
},
|
},
|
||||||
// 字段名
|
// 字段名
|
||||||
fieldName: 'api',
|
fieldName: 'api',
|
||||||
|
@ -16,15 +16,18 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
},
|
},
|
||||||
onOpenChange(isOpen) {
|
onOpenChange(isOpen) {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
handleUpdate(10);
|
handleUpdate();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleUpdate(len: number) {
|
function handleUpdate(len?: number) {
|
||||||
modalApi.setState({ confirmDisabled: true, loading: true });
|
modalApi.setState({ confirmDisabled: true, loading: true });
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
list.value = Array.from({ length: len }, (_v, k) => k + 1);
|
list.value = Array.from(
|
||||||
|
{ length: len ?? Math.floor(Math.random() * 10) + 1 },
|
||||||
|
(_v, k) => k + 1,
|
||||||
|
);
|
||||||
modalApi.setState({ confirmDisabled: false, loading: false });
|
modalApi.setState({ confirmDisabled: false, loading: false });
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
@ -40,7 +43,7 @@ function handleUpdate(len: number) {
|
|||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
<template #prepend-footer>
|
<template #prepend-footer>
|
||||||
<Button type="link" @click="handleUpdate(6)">点击更新数据</Button>
|
<Button type="link" @click="handleUpdate()">点击更新数据</Button>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -24,7 +24,7 @@ const value = ref();
|
|||||||
title="基础弹窗示例"
|
title="基础弹窗示例"
|
||||||
title-tooltip="标题提示内容"
|
title-tooltip="标题提示内容"
|
||||||
>
|
>
|
||||||
此弹窗指定在内容区域打开
|
此弹窗指定在内容区域打开,并且在关闭之后弹窗内容不会被销毁
|
||||||
<Input v-model="value" placeholder="KeepAlive测试" />
|
<Input v-model:value="value" placeholder="KeepAlive测试" />
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -138,6 +138,7 @@ function openConfirm() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
centered: false,
|
||||||
content: '这是一个确认弹窗',
|
content: '这是一个确认弹窗',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
})
|
})
|
||||||
@ -160,6 +161,7 @@ async function openPrompt() {
|
|||||||
componentProps: { placeholder: '不能吃芝士...' },
|
componentProps: { placeholder: '不能吃芝士...' },
|
||||||
content: '中午吃了什么?',
|
content: '中午吃了什么?',
|
||||||
icon: 'question',
|
icon: 'question',
|
||||||
|
overlayBlur: 3,
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
message.success(`用户输入了:${res}`);
|
message.success(`用户输入了:${res}`);
|
||||||
@ -196,7 +198,7 @@ async function openPrompt() {
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card class="w-[300px]" title="指定容器">
|
<Card class="w-[300px]" title="指定容器+关闭后不销毁">
|
||||||
<p>在内容区域打开弹窗的示例</p>
|
<p>在内容区域打开弹窗的示例</p>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<Button type="primary" @click="openInContentModal">打开弹窗</Button>
|
<Button type="primary" @click="openInContentModal">打开弹窗</Button>
|
||||||
|
@ -94,8 +94,9 @@ export function useColumns(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'createTime',
|
field: 'createTime',
|
||||||
|
resizable: false,
|
||||||
title: $t('system.dept.createTime'),
|
title: $t('system.dept.createTime'),
|
||||||
width: 180,
|
width: 'auto',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'remark',
|
field: 'remark',
|
||||||
|
@ -37,7 +37,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
const { valid } = await formApi.validate();
|
const { valid } = await formApi.validate();
|
||||||
if (valid) {
|
if (valid) {
|
||||||
modalApi.lock();
|
modalApi.lock();
|
||||||
const data = formApi.getValues();
|
const data = await formApi.getValues();
|
||||||
try {
|
try {
|
||||||
await (formData.value?.id
|
await (formData.value?.id
|
||||||
? updateDept(formData.value.id, data)
|
? updateDept(formData.value.id, data)
|
||||||
|
@ -11,7 +11,7 @@ export function getMenuTypeOptions() {
|
|||||||
value: 'catalog',
|
value: 'catalog',
|
||||||
},
|
},
|
||||||
{ color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' },
|
{ color: 'default', label: $t('system.menu.typeMenu'), value: 'menu' },
|
||||||
{ color: 'error', label: $t('system.menu.typeButton'), value: 'button' },
|
{ color: 'error', label: $t('system.menu.typeButton'), value: 'action' },
|
||||||
{
|
{
|
||||||
color: 'success',
|
color: 'success',
|
||||||
label: $t('system.menu.typeEmbedded'),
|
label: $t('system.menu.typeEmbedded'),
|
||||||
|
@ -241,10 +241,10 @@ const schema: VbenFormSchema[] = [
|
|||||||
component: 'Input',
|
component: 'Input',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
rules: (values) => {
|
rules: (values) => {
|
||||||
return values.type === 'button' ? 'required' : null;
|
return values.type === 'action' ? 'required' : null;
|
||||||
},
|
},
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return ['button', 'catalog', 'embedded', 'menu'].includes(values.type);
|
return ['action', 'catalog', 'embedded', 'menu'].includes(values.type);
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -277,7 +277,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return values.type !== 'button';
|
return values.type !== 'action';
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -295,7 +295,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return values.type !== 'button';
|
return values.type !== 'action';
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -314,7 +314,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
},
|
},
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return values.type !== 'button';
|
return values.type !== 'action';
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -325,7 +325,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
component: 'Divider',
|
component: 'Divider',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return !['button', 'link'].includes(values.type);
|
return !['action', 'link'].includes(values.type);
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -372,7 +372,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
component: 'Checkbox',
|
component: 'Checkbox',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return !['button'].includes(values.type);
|
return !['action'].includes(values.type);
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -402,7 +402,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
component: 'Checkbox',
|
component: 'Checkbox',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return !['button', 'link'].includes(values.type);
|
return !['action', 'link'].includes(values.type);
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
@ -417,7 +417,7 @@ const schema: VbenFormSchema[] = [
|
|||||||
component: 'Checkbox',
|
component: 'Checkbox',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: (values) => {
|
show: (values) => {
|
||||||
return !['button', 'link'].includes(values.type);
|
return !['action', 'link'].includes(values.type);
|
||||||
},
|
},
|
||||||
triggerFields: ['type'],
|
triggerFields: ['type'],
|
||||||
},
|
},
|
||||||
|
@ -167,7 +167,7 @@ catalog:
|
|||||||
unbuild: ^3.5.0
|
unbuild: ^3.5.0
|
||||||
unplugin-element-plus: ^0.9.1
|
unplugin-element-plus: ^0.9.1
|
||||||
vee-validate: ^4.15.0
|
vee-validate: ^4.15.0
|
||||||
vite: ^6.2.5
|
vite: 6.2.5
|
||||||
vite-plugin-compression: ^0.5.1
|
vite-plugin-compression: ^0.5.1
|
||||||
vite-plugin-dts: ^4.5.3
|
vite-plugin-dts: ^4.5.3
|
||||||
vite-plugin-html: ^3.2.2
|
vite-plugin-html: ^3.2.2
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
UPDATE sys_menu SET icon = 'eos-icons:system-group' WHERE menu_id = 1;
|
UPDATE sys_menu SET icon = 'eos-icons:system-group' WHERE menu_id = 1;
|
||||||
UPDATE sys_menu SET icon = 'solar:monitor-bold-duotone' WHERE menu_id = 2;
|
UPDATE sys_menu SET icon = 'solar:monitor-camera-outline' WHERE menu_id = 2;
|
||||||
UPDATE sys_menu SET icon = 'mdi:tools' WHERE menu_id = 3;
|
UPDATE sys_menu SET icon = 'ant-design:tool-outlined' WHERE menu_id = 3;
|
||||||
UPDATE sys_menu SET icon = 'flat-color-icons:plus' WHERE menu_id = 4;
|
UPDATE sys_menu SET icon = 'flat-color-icons:plus' WHERE menu_id = 4;
|
||||||
UPDATE sys_menu SET icon = 'devicon:vscode' WHERE menu_id = 5;
|
UPDATE sys_menu SET icon = 'devicon:vscode' WHERE menu_id = 5;
|
||||||
UPDATE sys_menu SET icon = 'ic:baseline-house' WHERE menu_id = 6;
|
UPDATE sys_menu SET icon = 'ph:users-light' WHERE menu_id = 6;
|
||||||
UPDATE sys_menu SET icon = 'ph:user-duotone' WHERE menu_id = 100;
|
UPDATE sys_menu SET icon = 'ant-design:user-outlined' WHERE menu_id = 100;
|
||||||
UPDATE sys_menu SET icon = 'eos-icons:role-binding-outlined' WHERE menu_id = 101;
|
UPDATE sys_menu SET icon = 'eos-icons:role-binding-outlined' WHERE menu_id = 101;
|
||||||
UPDATE sys_menu SET icon = 'ic:sharp-menu' WHERE menu_id = 102;
|
UPDATE sys_menu SET icon = 'ic:sharp-menu' WHERE menu_id = 102;
|
||||||
UPDATE sys_menu SET icon = 'mingcute:department-line' WHERE menu_id = 103;
|
UPDATE sys_menu SET icon = 'mingcute:department-line' WHERE menu_id = 103;
|
||||||
UPDATE sys_menu SET icon = 'icon-park-outline:appointment' WHERE menu_id = 104;
|
UPDATE sys_menu SET icon = 'icon-park-outline:appointment' WHERE menu_id = 104;
|
||||||
UPDATE sys_menu SET icon = 'fluent-mdl2:dictionary' WHERE menu_id = 105;
|
UPDATE sys_menu SET icon = 'fluent-mdl2:dictionary' WHERE menu_id = 105;
|
||||||
UPDATE sys_menu SET icon = 'icon-park-twotone:setting-two' WHERE menu_id = 106;
|
UPDATE sys_menu SET icon = 'ant-design:setting-outlined' WHERE menu_id = 106;
|
||||||
UPDATE sys_menu SET icon = 'fe:notice-push' WHERE menu_id = 107;
|
UPDATE sys_menu SET icon = 'fe:notice-push' WHERE menu_id = 107;
|
||||||
UPDATE sys_menu SET icon = 'material-symbols:logo-dev-outline' WHERE menu_id = 108;
|
UPDATE sys_menu SET icon = 'material-symbols:logo-dev-outline' WHERE menu_id = 108;
|
||||||
UPDATE sys_menu SET icon = 'material-symbols:generating-tokens-outline' WHERE menu_id = 109;
|
UPDATE sys_menu SET icon = 'material-symbols:generating-tokens-outline' WHERE menu_id = 109;
|
||||||
@ -19,10 +19,10 @@ UPDATE sys_menu SET icon = 'fluent:form-new-24-regular' WHERE menu_id = 114;
|
|||||||
UPDATE sys_menu SET icon = 'tabler:code' WHERE menu_id = 115;
|
UPDATE sys_menu SET icon = 'tabler:code' WHERE menu_id = 115;
|
||||||
UPDATE sys_menu SET icon = 'devicon:spring-wordmark' WHERE menu_id = 117;
|
UPDATE sys_menu SET icon = 'devicon:spring-wordmark' WHERE menu_id = 117;
|
||||||
UPDATE sys_menu SET icon = 'solar:folder-with-files-outline' WHERE menu_id = 118;
|
UPDATE sys_menu SET icon = 'solar:folder-with-files-outline' WHERE menu_id = 118;
|
||||||
UPDATE sys_menu SET icon = 'akar-icons:schedule' WHERE menu_id = 120;
|
UPDATE sys_menu SET icon = 'svg:snail-job' WHERE menu_id = 120;
|
||||||
UPDATE sys_menu SET icon = 'bi:houses-fill' WHERE menu_id = 121;
|
UPDATE sys_menu SET icon = 'ph:user-list' WHERE menu_id = 121;
|
||||||
UPDATE sys_menu SET icon = 'bx:package' WHERE menu_id = 122;
|
UPDATE sys_menu SET icon = 'bx:package' WHERE menu_id = 122;
|
||||||
UPDATE sys_menu SET icon = 'simple-icons:authy' WHERE menu_id = 123;
|
UPDATE sys_menu SET icon = 'solar:monitor-smartphone-outline' WHERE menu_id = 123;
|
||||||
UPDATE sys_menu SET icon = 'arcticons:one-hand-operation' WHERE menu_id = 500;
|
UPDATE sys_menu SET icon = 'arcticons:one-hand-operation' WHERE menu_id = 500;
|
||||||
UPDATE sys_menu SET icon = 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger' WHERE menu_id = 501;
|
UPDATE sys_menu SET icon = 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger' WHERE menu_id = 501;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user