pull/360/head^2
xingyu4j 2026-06-08 10:39:47 +08:00
commit b37657a92d
15 changed files with 1440 additions and 1309 deletions

View File

@ -61,7 +61,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@ -89,6 +89,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v4
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'

View File

@ -19,7 +19,7 @@ jobs:
steps: steps:
# 关闭未活动的 Issues # 关闭未活动的 Issues
- name: Close Inactive Issues - name: Close Inactive Issues
uses: actions/stale@v9 uses: actions/stale@v10
with: with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically. days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: needs-reproduction # Label that flags an issue as stale. stale-issue-label: needs-reproduction # Label that flags an issue as stale.

View File

@ -14,7 +14,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin' if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@v6
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '14' issue-inactive-days: '14'

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Validate PR title - name: Validate PR title
uses: amannn/action-semantic-pull-request@v5 uses: amannn/action-semantic-pull-request@v6
with: with:
wip: true wip: true
subjectPattern: ^(?![A-Z]).+$ subjectPattern: ^(?![A-Z]).+$

View File

@ -9,7 +9,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin' if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v10
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'

View File

@ -1 +1 @@
22.22.0 24.16.0

View File

@ -36,13 +36,16 @@ import type { Component, Ref } from 'vue';
import type { import type {
ApiComponentSharedProps, ApiComponentSharedProps,
BaseFormComponentType, BaseFormComponentType,
CollapsibleParamsProps,
IconPickerProps, IconPickerProps,
} from '@vben/common-ui'; } from '@vben/common-ui';
import type { Sortable } from '@vben/hooks'; import type { Sortable } from '@vben/hooks';
import type { TipTapProps } from '@vben/plugins/tiptap';
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import { import {
computed, computed,
defineAsyncComponent,
defineComponent, defineComponent,
h, h,
nextTick, nextTick,
@ -58,44 +61,18 @@ import {
ApiComponent, ApiComponent,
globalShareState, globalShareState,
IconPicker, IconPicker,
VbenCollapsibleParams,
VCropper, VCropper,
} from '@vben/common-ui'; } from '@vben/common-ui';
import { useSortable } from '@vben/hooks'; import { useSortable } from '@vben/hooks';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { VbenTiptap } from '@vben/plugins/tiptap';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
import { import { message, Modal, notification } from 'antdv-next';
AutoComplete as AutoCompleteComponent,
Button,
Cascader as CascaderComponent,
Checkbox as CheckboxComponent,
CheckboxGroup as CheckboxGroupComponent,
DatePicker as DatePickerComponent,
Divider as DividerComponent,
Image as ImageComponent,
ImagePreviewGroup,
Input as InputComponent,
InputNumber as InputNumberComponent,
InputPassword,
Mentions as MentionsComponent,
message,
Modal,
notification,
Radio as RadioComponent,
RadioGroup as RadioGroupComponent,
DateRangePicker as RangePickerComponent,
Rate as RateComponent,
Select as SelectComponent,
Space as SpaceComponent,
Switch as SwitchComponent,
TextArea as TextareaComponent,
TimePicker as TimePickerComponent,
TimeRangePicker as TimeRangePickerComponent,
TreeSelect as TreeSelectComponent,
Upload as UploadComponent,
} from 'antdv-next';
import { uploadFile as uploadFileApi } from '#/api/infra/file';
import { Tinymce as RichTextarea } from '#/components/tinymce'; import { Tinymce as RichTextarea } from '#/components/tinymce';
import { FileUpload, ImageUpload } from '#/components/upload'; import { FileUpload, ImageUpload } from '#/components/upload';
type AdapterUploadProps = UploadProps & { type AdapterUploadProps = UploadProps & {
@ -108,29 +85,70 @@ type AdapterUploadProps = UploadProps & {
onHandleChange?: (event: UploadChangeParam) => void; onHandleChange?: (event: UploadChangeParam) => void;
}; };
const AutoComplete = AutoCompleteComponent; const AutoComplete = defineAsyncComponent(
const Checkbox = CheckboxComponent; () => import('antdv-next/dist/auto-complete/index'),
const CheckboxGroup = CheckboxGroupComponent; );
const DatePicker = DatePickerComponent; const Button = defineAsyncComponent(
const Divider = DividerComponent; () => import('antdv-next/dist/button/index'),
const Input = InputComponent; );
const InputNumber = InputNumberComponent; const Checkbox = defineAsyncComponent(
const Mentions = MentionsComponent; () => import('antdv-next/dist/checkbox/index'),
const Radio = RadioComponent; );
const RadioGroup = RadioGroupComponent; const CheckboxGroup = defineAsyncComponent(() =>
const RangePicker = RangePickerComponent; import('antdv-next/dist/checkbox/index').then((res) => res.CheckboxGroup),
const Rate = RateComponent; );
const Select = SelectComponent; const DatePicker = defineAsyncComponent(
const Space = SpaceComponent; () => import('antdv-next/dist/date-picker/index'),
const Switch = SwitchComponent; );
const TextArea = TextareaComponent; const Divider = defineAsyncComponent(
const TimePicker = TimePickerComponent; () => import('antdv-next/dist/divider/index'),
const TimeRangePicker = TimeRangePickerComponent; );
const TreeSelect = TreeSelectComponent; const Input = defineAsyncComponent(() => import('antdv-next/dist/input/index'));
const Cascader = CascaderComponent; const InputNumber = defineAsyncComponent(
const Upload = UploadComponent; () => import('antdv-next/dist/input-number/index'),
const Image = ImageComponent; );
const PreviewGroup = ImagePreviewGroup; const InputPassword = defineAsyncComponent(() =>
import('antdv-next/dist/input/index').then((res) => res.InputPassword),
);
const Mentions = defineAsyncComponent(
() => import('antdv-next/dist/mentions/index'),
);
const Radio = defineAsyncComponent(() => import('antdv-next/dist/radio/index'));
const RadioGroup = defineAsyncComponent(() =>
import('antdv-next/dist/radio/index').then((res) => res.RadioGroup),
);
const RangePicker = defineAsyncComponent(() =>
import('antdv-next/dist/date-picker/index').then(
(res) => res.DateRangePicker,
),
);
const Rate = defineAsyncComponent(() => import('antdv-next/dist/rate/index'));
const Select = defineAsyncComponent(
() => import('antdv-next/dist/select/index'),
);
const Space = defineAsyncComponent(() => import('antdv-next/dist/space/index'));
const Switch = defineAsyncComponent(
() => import('antdv-next/dist/switch/index'),
);
const Textarea = defineAsyncComponent(
() => import('antdv-next/dist/input/TextArea'),
);
const TimePicker = defineAsyncComponent(
() => import('antdv-next/dist/time-picker/index'),
);
const TreeSelect = defineAsyncComponent(
() => import('antdv-next/dist/tree-select/index'),
);
const Cascader = defineAsyncComponent(
() => import('antdv-next/dist/cascader/index'),
);
const Upload = defineAsyncComponent(
() => import('antdv-next/dist/upload/index'),
);
const Image = defineAsyncComponent(() => import('antdv-next/dist/image/index'));
const PreviewGroup = defineAsyncComponent(() =>
import('antdv-next/dist/image/index').then((res) => res.ImagePreviewGroup),
);
const withDefaultPlaceholder = ( const withDefaultPlaceholder = (
component: Component, component: Component,
@ -236,7 +254,7 @@ function getBase64(file: File): Promise<string> {
*/ */
async function previewImage( async function previewImage(
file: UploadFile, file: UploadFile,
visible: Ref<boolean>, open: Ref<boolean>,
fileList: Ref<UploadProps['fileList']>, fileList: Ref<UploadProps['fileList']>,
) { ) {
// 非图片文件直接打开链接 // 非图片文件直接打开链接
@ -244,6 +262,8 @@ async function previewImage(
const url = file.url || file.preview; const url = file.url || file.preview;
if (url) { if (url) {
window.open(url, '_blank'); window.open(url, '_blank');
} else if (file.preview) {
window.open(file.preview, '_blank');
} else { } else {
message.error($t('ui.formRules.previewWarning')); message.error($t('ui.formRules.previewWarning'));
} }
@ -279,10 +299,10 @@ async function previewImage(
{ {
class: 'hidden', class: 'hidden',
preview: { preview: {
open: visible.value, open: open.value,
current: currentIndex, current: currentIndex,
onOpenChange: (value: boolean) => { onOpenChange: (value: boolean) => {
visible.value = value; open.value = value;
if (!value) { if (!value) {
setTimeout(() => { setTimeout(() => {
if (!isUnmounted && container) { if (!isUnmounted && container) {
@ -324,7 +344,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
const open = ref<boolean>(true); const open = ref<boolean>(true);
const cropperRef = ref<InstanceType<typeof VCropper> | null>(null); const cropperRef = ref<InstanceType<typeof VCropper> | null>(null);
const closeModal = () => { function closeModal() {
open.value = false; open.value = false;
setTimeout(() => { setTimeout(() => {
if (!isUnmounted && container) { if (!isUnmounted && container) {
@ -336,7 +356,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
container.remove(); container.remove();
} }
}, 300); }, 300);
}; }
const CropperWrapper = { const CropperWrapper = {
setup() { setup() {
@ -366,7 +386,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
closable: false, closable: false,
cancelText: $t('common.cancel'), cancelText: $t('common.cancel'),
okText: $t('ui.crop.confirm'), okText: $t('ui.crop.confirm'),
destroyOnClose: true, destroyOnHidden: true,
onOk: async () => { onOk: async () => {
const cropper = cropperRef.value; const cropper = cropperRef.value;
if (!cropper) { if (!cropper) {
@ -410,7 +430,7 @@ function cropImage(file: File, aspectRatio: string | undefined) {
/** /**
* *
*/ */
const withPreviewUpload = () => { function withPreviewUpload() {
return defineComponent({ return defineComponent({
name: Upload.name, name: Upload.name,
emits: ['update:modelValue'], emits: ['update:modelValue'],
@ -430,10 +450,10 @@ const withPreviewUpload = () => {
() => attrs?.aspectRatio ?? attrs?.['aspect-ratio'], () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
); );
const handleBeforeUpload = async ( async function handleBeforeUpload(
file: UploadFile, file: UploadFile,
originFileList: Array<File>, originFileList: Array<File>,
) => { ) {
// 文件大小限制 // 文件大小限制
if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) { if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
message.error($t('ui.formRules.sizeLimit', [maxSize.value])); message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
@ -457,9 +477,9 @@ const withPreviewUpload = () => {
} }
return attrs.beforeUpload?.(file) ?? true; return attrs.beforeUpload?.(file) ?? true;
}; }
const handleChange = (event: UploadChangeParam) => { function handleChange(event: UploadChangeParam) {
try { try {
attrs.handleChange?.(event); attrs.handleChange?.(event);
attrs.onHandleChange?.(event); attrs.onHandleChange?.(event);
@ -473,19 +493,19 @@ const withPreviewUpload = () => {
'update:modelValue', 'update:modelValue',
event.fileList?.length ? fileList.value : undefined, event.fileList?.length ? fileList.value : undefined,
); );
}; }
const handlePreview = async (file: UploadFile) => { function handlePreview(file: UploadFile) {
previewVisible.value = true; previewVisible.value = true;
await previewImage(file, previewVisible, fileList); return previewImage(file, previewVisible, fileList);
}; }
const renderUploadButton = () => { function renderUploadButton() {
if (attrs.disabled) return null; if (attrs.disabled) return null;
return isEmpty(slots) return isEmpty(slots)
? createDefaultUploadSlots(listType, placeholder) ? createDefaultUploadSlots(listType, placeholder)
: slots; : slots;
}; }
// 拖拽排序 // 拖拽排序
const draggable = computed( const draggable = computed(
@ -594,7 +614,7 @@ const withPreviewUpload = () => {
); );
}, },
}); });
}; }
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType = export type ComponentType =
@ -605,6 +625,7 @@ export type ComponentType =
| 'Cascader' | 'Cascader'
| 'Checkbox' | 'Checkbox'
| 'CheckboxGroup' | 'CheckboxGroup'
| 'CollapsibleParams'
| 'DatePicker' | 'DatePicker'
| 'DefaultButton' | 'DefaultButton'
| 'Divider' | 'Divider'
@ -620,13 +641,13 @@ export type ComponentType =
| 'RadioGroup' | 'RadioGroup'
| 'RangePicker' | 'RangePicker'
| 'Rate' | 'Rate'
| 'RichEditor'
| 'RichTextarea' | 'RichTextarea'
| 'Select' | 'Select'
| 'Space' | 'Space'
| 'Switch' | 'Switch'
| 'TextArea' | 'TextArea'
| 'TimePicker' | 'TimePicker'
| 'TimeRangePicker'
| 'TreeSelect' | 'TreeSelect'
| 'Upload' | 'Upload'
| BaseFormComponentType; | BaseFormComponentType;
@ -642,6 +663,7 @@ export interface ComponentPropsMap {
Cascader: CascaderProps; Cascader: CascaderProps;
Checkbox: CheckboxProps; Checkbox: CheckboxProps;
CheckboxGroup: CheckboxGroupProps; CheckboxGroup: CheckboxGroupProps;
CollapsibleParams: CollapsibleParamsProps;
DatePicker: DatePickerProps; DatePicker: DatePickerProps;
DefaultButton: ButtonProps; DefaultButton: ButtonProps;
Divider: DividerProps; Divider: DividerProps;
@ -655,6 +677,7 @@ export interface ComponentPropsMap {
RadioGroup: RadioGroupProps; RadioGroup: RadioGroupProps;
RangePicker: RangePickerProps; RangePicker: RangePickerProps;
Rate: RateProps; Rate: RateProps;
RichEditor: TipTapProps;
Select: SelectProps; Select: SelectProps;
Space: SpaceProps; Space: SpaceProps;
Switch: SwitchProps; Switch: SwitchProps;
@ -720,18 +743,39 @@ async function initComponentAdapter() {
RadioGroup, RadioGroup,
RangePicker, RangePicker,
Rate, Rate,
RichEditor: withDefaultPlaceholder(VbenTiptap, 'input', {
imageUpload: {
upload: (file: any, onProgress: any) => {
return new Promise((resolve, reject) => {
uploadFileApi({
file,
onProgress({ percent }) {
onProgress?.(percent);
},
onSuccess(response) {
// 从响应中提取图片URL
resolve(response?.data?.url ?? response?.url ?? '');
},
onError() {
reject(new Error($t('ui.tiptap.upload.uploadFailed')));
},
});
});
},
},
}),
Select: withDefaultPlaceholder(Select, 'select'), Select: withDefaultPlaceholder(Select, 'select'),
Space, Space,
Switch, Switch,
TextArea: withDefaultPlaceholder(TextArea, 'input'), TextArea: withDefaultPlaceholder(Textarea, 'input'),
RichTextarea, RichTextarea,
TimePicker, TimePicker,
TimeRangePicker,
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
Upload,
PreviewUpload: withPreviewUpload(), PreviewUpload: withPreviewUpload(),
FileUpload, FileUpload,
ImageUpload, ImageUpload,
Upload: withPreviewUpload(),
CollapsibleParams: VbenCollapsibleParams,
}; };
// 将组件注册到全局共享状态中 // 将组件注册到全局共享状态中

View File

@ -105,5 +105,5 @@
"node": "^22.18.0 || ^24.0.0", "node": "^22.18.0 || ^24.0.0",
"pnpm": ">=11.0.0" "pnpm": ">=11.0.0"
}, },
"packageManager": "pnpm@11.5.0" "packageManager": "pnpm@11.5.2"
} }

View File

@ -67,7 +67,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{ const emit = defineEmits<{
sideMouseLeave: []; sideMouseLeave: [];
toggleSidebar: []; toggleSidebar: [];
'update:sidebar-width': [value: number]; 'update:sidebarWidth': [value: number];
}>(); }>();
const sidebarDraggable = defineModel<boolean>('sidebarDraggable', { const sidebarDraggable = defineModel<boolean>('sidebarDraggable', {
default: true, default: true,
@ -520,7 +520,7 @@ const idMainContent = ELEMENT_ID_MAIN_CONTENT;
:width="getSidebarWidth" :width="getSidebarWidth"
:z-index="sidebarZIndex" :z-index="sidebarZIndex"
@leave="() => emit('sideMouseLeave')" @leave="() => emit('sideMouseLeave')"
@update:width="(val) => emit('update:sidebar-width', val)" @update:width="(val) => emit('update:sidebarWidth', val)"
> >
<template v-if="isSideMode && !isMixedNav" #logo> <template v-if="isSideMode && !isMixedNav" #logo>
<slot name="logo"></slot> <slot name="logo"></slot>

View File

@ -1,7 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ExtendedModalApi, ModalProps } from './modal'; import type { ExtendedModalApi, ModalProps } from './modal';
import { computed, nextTick, onDeactivated, ref, unref, watch } from 'vue'; import {
computed,
nextTick,
onDeactivated,
provide,
ref,
unref,
useId,
watch,
} from 'vue';
import { usePriorityValues, useSimpleLocale } from '@vben-core/composables'; import { usePriorityValues, useSimpleLocale } from '@vben-core/composables';
import { Expand, Shrink } from '@vben-core/icons'; import { Expand, Shrink } from '@vben-core/icons';
@ -47,6 +56,10 @@ const footerRef = ref();
const { $t } = useSimpleLocale(); const { $t } = useSimpleLocale();
const state = props.modalApi?.useStore?.(); const state = props.modalApi?.useStore?.();
const id = useId();
// id Modal
provide('DISMISSABLE_MODAL_ID', id);
const { const {
appendToMain, appendToMain,
bordered, bordered,
@ -181,8 +194,15 @@ function handleOpenAutoFocus(e: Event) {
// pointer-down-outside // pointer-down-outside
function pointerDownOutside(e: Event) { function pointerDownOutside(e: Event) {
if (!closeOnClickModal.value || submitting.value) { const target = e.target as HTMLElement;
const isDismissableModal = target?.dataset.dismissableModal;
if (
!closeOnClickModal.value ||
isDismissableModal !== id ||
submitting.value
) {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
} }
} }
@ -212,7 +232,7 @@ function handleClosed() {
</script> </script>
<template> <template>
<Dialog <Dialog
:modal="modal" :modal="false"
:open="state?.isOpen" :open="state?.isOpen"
@update:open="() => (!submitting ? modalApi?.close() : undefined)" @update:open="() => (!submitting ? modalApi?.close() : undefined)"
> >
@ -240,7 +260,7 @@ function handleClosed() {
:force-mount="getForceMount" :force-mount="getForceMount"
:modal="modal" :modal="modal"
:open="state?.isOpen" :open="state?.isOpen"
:show-close="closable" :show-close-button="closable"
:animation-type="animationType" :animation-type="animationType"
:z-index="zIndex" :z-index="zIndex"
:overlay-blur="overlayBlur" :overlay-blur="overlayBlur"

View File

@ -1,33 +1,39 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'; import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
import type { HTMLAttributes } from 'vue';
import type { ClassType } from '@vben-core/typings'; import type { ClassType } from '@vben-core/typings';
import { computed, ref } from 'vue'; import { computed, inject, ref } from 'vue';
import { useScrollLock } from '@vben-core/composables';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
import { X } from '@lucide/vue'; import { X } from '@lucide/vue';
import { import {
DialogClose, DialogClose,
DialogContent, DialogContent,
DialogOverlay,
DialogPortal, DialogPortal,
useForwardPropsEmits, useForwardPropsEmits,
} from 'reka-ui'; } from 'reka-ui';
defineOptions({
inheritAttrs: false,
});
const props = withDefaults( const props = withDefaults(
defineProps< defineProps<
DialogContentProps & { DialogContentProps & {
animationType?: 'scale' | 'slide'; animationType?: 'scale' | 'slide';
appendTo?: HTMLElement | string; appendTo?: HTMLElement | string;
class?: ClassType; class?: HTMLAttributes['class'];
closeClass?: ClassType; closeClass?: ClassType;
closeDisabled?: boolean; closeDisabled?: boolean;
modal?: boolean; modal?: boolean;
open?: boolean; open?: boolean;
overlayBlur?: number; overlayBlur?: number;
showClose?: boolean; showCloseButton?: boolean;
zIndex?: number; zIndex?: number;
} }
>(), >(),
@ -35,7 +41,7 @@ const props = withDefaults(
appendTo: 'body', appendTo: 'body',
animationType: 'slide', animationType: 'slide',
closeDisabled: false, closeDisabled: false,
showClose: true, showCloseButton: true,
}, },
); );
const emits = defineEmits< const emits = defineEmits<
@ -47,7 +53,7 @@ const delegatedProps = computed(() => {
class: _, class: _,
modal: _modal, modal: _modal,
open: _open, open: _open,
showClose: __, showCloseButton: __,
animationType: ___, animationType: ___,
...delegated ...delegated
} = props; } = props;
@ -67,6 +73,12 @@ const position = computed(() => {
return isAppendToBody() ? 'fixed' : 'absolute'; return isAppendToBody() ? 'fixed' : 'absolute';
}); });
// reka-ui Dialog modal=false
// / modal=true body pointer-events:none
// Select
useScrollLock();
const dismissableModalId = inject('DISMISSABLE_MODAL_ID', undefined);
const forwarded = useForwardPropsEmits(delegatedProps, emits); const forwarded = useForwardPropsEmits(delegatedProps, emits);
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null); const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
@ -87,23 +99,24 @@ defineExpose({
<template> <template>
<DialogPortal :to="appendTo"> <DialogPortal :to="appendTo">
<DialogOverlay <Transition name="fade">
v-if="open && modal" <div
:style="{ v-if="open && modal"
...(zIndex ? { zIndex } : {}), :data-dismissable-modal="dismissableModalId"
position, :style="{
backdropFilter: ...(zIndex ? { zIndex } : {}),
overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none', position,
}" backdropFilter:
:class=" overlayBlur && overlayBlur > 0 ? `blur(${overlayBlur}px)` : 'none',
cn( }"
'z-popup bg-overlay inset-0 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed', :class="cn('z-popup bg-overlay inset-0 fixed')"
) @click="() => emits('close')"
" ></div>
/> </Transition>
<DialogContent <DialogContent
ref="contentRef" ref="contentRef"
:style="{ ...(zIndex ? { zIndex } : {}), position }" :style="{ ...(zIndex ? { zIndex } : {}), position }"
data-slot="dialog-content"
@animationend="onAnimationEnd" @animationend="onAnimationEnd"
v-bind="forwarded" v-bind="forwarded"
:class=" :class="
@ -120,8 +133,9 @@ defineExpose({
<slot></slot> <slot></slot>
<DialogClose <DialogClose
v-if="showClose" v-if="showCloseButton"
:disabled="closeDisabled" :disabled="closeDisabled"
data-slot="dialog-close"
:class=" :class="
cn( cn(
'flex-center text-foreground/80 hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-3 right-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none', 'flex-center text-foreground/80 hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-3 right-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-hidden disabled:pointer-events-none',
@ -130,7 +144,7 @@ defineExpose({
" "
@click="() => emits('close')" @click="() => emits('close')"
> >
<X class="h-4 w-4" /> <X class="size-4" />
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>
</DialogPortal> </DialogPortal>

View File

@ -1,13 +1,31 @@
<script setup lang="ts"> <script setup lang="ts">
import type { DialogOverlayProps } from 'reka-ui'; import type { DialogOverlayProps } from 'reka-ui';
import type { HTMLAttributes } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { reactiveOmit } from '@vueuse/core';
import { DialogOverlay } from 'reka-ui'; import { DialogOverlay } from 'reka-ui';
const props = defineProps<DialogOverlayProps>(); const props = defineProps<
DialogOverlayProps & { class?: HTMLAttributes['class'] }
>();
const delegatedProps = reactiveOmit(props, 'class');
</script> </script>
<template> <template>
<DialogOverlay data-slot="dialog-overlay" v-bind="props"> <DialogOverlay
data-slot="dialog-overlay"
v-bind="delegatedProps"
:class="
cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
props.class,
)
"
>
<slot></slot> <slot></slot>
</DialogOverlay> </DialogOverlay>
</template> </template>

View File

@ -60,7 +60,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<DialogClose <DialogClose
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary" class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
> >
<X class="w-4 h-4" /> <X class="size-4" />
<span class="sr-only">Close</span> <span class="sr-only">Close</span>
</DialogClose> </DialogClose>
</DialogContent> </DialogContent>

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@ catalog:
'@changesets/changelog-github': ^0.7.0 '@changesets/changelog-github': ^0.7.0
'@changesets/cli': ^2.31.0 '@changesets/cli': ^2.31.0
'@changesets/git': ^3.0.4 '@changesets/git': ^3.0.4
'@clack/prompts': ^1.5.0 '@clack/prompts': ^1.5.1
'@commitlint/cli': ^21.0.2 '@commitlint/cli': ^21.0.2
'@commitlint/config-conventional': ^21.0.2 '@commitlint/config-conventional': ^21.0.2
'@ctrl/tinycolor': ^4.2.0 '@ctrl/tinycolor': ^4.2.0
@ -52,7 +52,7 @@ catalog:
'@form-create/designer': ^3.5.0 '@form-create/designer': ^3.5.0
'@form-create/element-ui': ^3.3.1 '@form-create/element-ui': ^3.3.1
'@form-create/naive-ui': ^3.3.1 '@form-create/naive-ui': ^3.3.1
'@iconify/json': ^2.2.481 '@iconify/json': ^2.2.483
'@iconify/tailwind4': ^1.2.3 '@iconify/tailwind4': ^1.2.3
'@iconify/vue': ^5.0.1 '@iconify/vue': ^5.0.1
'@intlify/core-base': ^11.4.4 '@intlify/core-base': ^11.4.4
@ -69,19 +69,19 @@ catalog:
'@tanstack/vue-store': ^0.11.0 '@tanstack/vue-store': ^0.11.0
'@tinyflow-ai/vue': ^1.3.6 '@tinyflow-ai/vue': ^1.3.6
'@tinymce/tinymce-vue': ^6.3.0 '@tinymce/tinymce-vue': ^6.3.0
'@tiptap/core': ^3.24.0 '@tiptap/core': ^3.26.0
'@tiptap/extension-document': ^3.24.0 '@tiptap/extension-document': ^3.26.0
'@tiptap/extension-highlight': ^3.24.0 '@tiptap/extension-highlight': ^3.26.0
'@tiptap/extension-image': ^3.24.0 '@tiptap/extension-image': ^3.26.0
'@tiptap/extension-link': ^3.24.0 '@tiptap/extension-link': ^3.26.0
'@tiptap/extension-placeholder': ^3.24.0 '@tiptap/extension-placeholder': ^3.26.0
'@tiptap/extension-text-align': ^3.24.0 '@tiptap/extension-text-align': ^3.26.0
'@tiptap/extension-text-style': ^3.24.0 '@tiptap/extension-text-style': ^3.26.0
'@tiptap/extension-underline': ^3.24.0 '@tiptap/extension-underline': ^3.26.0
'@tiptap/pm': ^3.24.0 '@tiptap/pm': ^3.26.0
'@tiptap/starter-kit': ^3.24.0 '@tiptap/starter-kit': ^3.26.0
'@tiptap/vue-3': ^3.24.0 '@tiptap/vue-3': ^3.26.0
'@tsdown/css': ^0.22.1 '@tsdown/css': ^0.22.2
'@types/archiver': ^7.0.0 '@types/archiver': ^7.0.0
'@types/codemirror': ^5.60.17 '@types/codemirror': ^5.60.17
'@types/crypto-js': ^4.2.2 '@types/crypto-js': ^4.2.2
@ -89,7 +89,7 @@ catalog:
'@types/json-bigint': ^1.0.4 '@types/json-bigint': ^1.0.4
'@types/lodash.clonedeep': ^4.5.9 '@types/lodash.clonedeep': ^4.5.9
'@types/markdown-it': ^14.1.2 '@types/markdown-it': ^14.1.2
'@types/node': ^25.9.1 '@types/node': ^25.9.2
'@types/nprogress': ^0.2.3 '@types/nprogress': ^0.2.3
'@types/qrcode': ^1.5.6 '@types/qrcode': ^1.5.6
'@types/qs': ^6.15.1 '@types/qs': ^6.15.1
@ -102,14 +102,14 @@ catalog:
'@vitejs/plugin-vue': ^6.0.7 '@vitejs/plugin-vue': ^6.0.7
'@vitejs/plugin-vue-jsx': ^5.1.5 '@vitejs/plugin-vue-jsx': ^5.1.5
'@vue/shared': ^3.5.35 '@vue/shared': ^3.5.35
'@vue/test-utils': ^2.4.10 '@vue/test-utils': ^2.4.11
'@vueuse/core': ^14.3.0 '@vueuse/core': ^14.3.0
'@vueuse/integrations': ^14.3.0 '@vueuse/integrations': ^14.3.0
'@vueuse/motion': ^3.0.3 '@vueuse/motion': ^3.0.3
ant-design-vue: ^4.2.6 ant-design-vue: ^4.2.6
antdv-next: ^1.3.1 antdv-next: ^1.3.3
archiver: ^7.0.1 archiver: ^7.0.1
axios: ^1.16.1 axios: ^1.17.0
axios-mock-adapter: ^2.1.0 axios-mock-adapter: ^2.1.0
benz-amr-recorder: ^1.1.5 benz-amr-recorder: ^1.1.5
bpmn-js: ^17.11.1 bpmn-js: ^17.11.1
@ -148,14 +148,14 @@ catalog:
eslint-plugin-pnpm: ^1.6.1 eslint-plugin-pnpm: ^1.6.1
eslint-plugin-unicorn: ^64.0.0 eslint-plugin-unicorn: ^64.0.0
eslint-plugin-unused-imports: ^4.4.1 eslint-plugin-unused-imports: ^4.4.1
eslint-plugin-vue: ^10.9.1 eslint-plugin-vue: ^10.9.2
eslint-plugin-yml: ^3.4.0 eslint-plugin-yml: ^3.4.0
execa: ^9.6.1 execa: ^9.6.1
fast-xml-parser: ^4.5.6 fast-xml-parser: ^4.5.6
find-up: ^8.0.0 find-up: ^8.0.0
get-port: ^7.2.0 get-port: ^7.2.0
globals: ^17.6.0 globals: ^17.6.0
happy-dom: ^20.9.0 happy-dom: ^20.10.2
highlight.js: ^11.11.1 highlight.js: ^11.11.1
html-minifier-terser: ^7.2.0 html-minifier-terser: ^7.2.0
is-ci: ^4.1.0 is-ci: ^4.1.0
@ -188,7 +188,7 @@ catalog:
publint: ^0.3.21 publint: ^0.3.21
qrcode: ^1.5.4 qrcode: ^1.5.4
qs: ^6.15.2 qs: ^6.15.2
reka-ui: ^2.9.8 reka-ui: ^2.9.9
resolve.exports: ^2.0.3 resolve.exports: ^2.0.3
rimraf: ^6.1.3 rimraf: ^6.1.3
rollup-plugin-visualizer: ^7.0.1 rollup-plugin-visualizer: ^7.0.1
@ -204,14 +204,14 @@ catalog:
stylelint-config-recommended-vue: ^1.6.1 stylelint-config-recommended-vue: ^1.6.1
stylelint-config-standard: ^40.0.0 stylelint-config-standard: ^40.0.0
stylelint-order: ^8.1.1 stylelint-order: ^8.1.1
stylelint-scss: ^7.1.1 stylelint-scss: ^7.2.0
tailwind-merge: ^3.6.0 tailwind-merge: ^3.6.0
tailwindcss: ^4.3.0 tailwindcss: ^4.3.0
tdesign-vue-next: ^1.20.0 tdesign-vue-next: ^1.20.1
theme-colors: ^0.1.0 theme-colors: ^0.1.0
tinymce: ^7.9.3 tinymce: ^7.9.3
tippy.js: ^6.3.7 tippy.js: ^6.3.7
tsdown: ^0.22.1 tsdown: ^0.22.2
turbo: ^2.9.16 turbo: ^2.9.16
tw-animate-css: ^1.4.0 tw-animate-css: ^1.4.0
tyme4ts: ^1.5.1 tyme4ts: ^1.5.1
@ -231,7 +231,7 @@ catalog:
vitest: ^4.1.8 vitest: ^4.1.8
vue: ^3.5.35 vue: ^3.5.35
vue-dompurify-html: ^5.3.0 vue-dompurify-html: ^5.3.0
vue-eslint-parser: ^10.4.0 vue-eslint-parser: ^10.4.1
vue-i18n: ^11.4.4 vue-i18n: ^11.4.4
vue-json-pretty: ^2.6.0 vue-json-pretty: ^2.6.0
vue-router: ^5.1.0 vue-router: ^5.1.0
@ -240,15 +240,15 @@ catalog:
vue3-print-nb: ^0.1.4 vue3-print-nb: ^0.1.4
vue3-signature: ^0.4.4 vue3-signature: ^0.4.4
vuedraggable: ^4.1.0 vuedraggable: ^4.1.0
vxe-pc-ui: ^4.14.26 vxe-pc-ui: ^4.14.30
vxe-table: ^4.19.6 vxe-table: ^4.19.7
watermark-js-plus: ^1.6.3 watermark-js-plus: ^1.6.3
yaml-eslint-parser: ^2.0.0 yaml-eslint-parser: ^2.0.0
zod: ^3.25.76 zod: ^3.25.76
zod-defaults: 0.1.3 zod-defaults: 0.1.3
allowBuilds: allowBuilds:
'@carbon/icons': true '@carbon/icons': false
'@parcel/watcher': true '@parcel/watcher': true
core-js: true core-js: true
core-js-pure: true core-js-pure: true