@ -36,8 +36,11 @@ import type { Component, Ref } from 'vue';
import type {
ApiComponentSharedProps ,
BaseFormComponentType ,
CollapsibleParamsProps ,
IconPickerProps ,
} from '@vben/common-ui' ;
import type { Sortable } from '@vben/hooks' ;
import type { TipTapProps } from '@vben/plugins/tiptap' ;
import type { Recordable } from '@vben/types' ;
import {
@ -45,6 +48,9 @@ import {
defineAsyncComponent ,
defineComponent ,
h ,
nextTick ,
onMounted ,
onUnmounted ,
ref ,
render ,
unref ,
@ -55,19 +61,25 @@ import {
ApiComponent ,
globalShareState ,
IconPicker ,
VbenCollapsibleParams ,
VCropper ,
} from '@vben/common-ui' ;
import { useSortable } from '@vben/hooks' ;
import { IconifyIcon } from '@vben/icons' ;
import { $t } from '@vben/locales' ;
import { VbenTiptap } from '@vben/plugins/tiptap' ;
import { isEmpty } from '@vben/utils' ;
import { message , Modal , notification } from 'antdv-next' ;
import { upload_file } from '#/api' ;
type AdapterUploadProps = UploadProps & {
aspectRatio? : string ;
crop? : boolean ;
draggable? : boolean ;
handleChange ? : ( event : UploadChangeParam ) = > void ;
maxSize? : number ;
onDragSort ? : ( oldIndex : number , newIndex : number ) = > void ;
onHandleChange ? : ( event : UploadChangeParam ) = > void ;
} ;
@ -80,8 +92,8 @@ const Button = defineAsyncComponent(
const Checkbox = defineAsyncComponent (
( ) = > import ( 'antdv-next/dist/checkbox/index' ) ,
) ;
const CheckboxGroup = defineAsyncComponent (
( ) = > import ( 'antdv-next/dist/checkbox/Group' ) ,
const CheckboxGroup = defineAsyncComponent ( ( ) = >
import ( 'antdv-next/dist/checkbox/index' ) . then ( ( res ) = > res . CheckboxGroup ) ,
) ;
const DatePicker = defineAsyncComponent (
( ) = > import ( 'antdv-next/dist/date-picker/index' ) ,
@ -170,260 +182,263 @@ const withDefaultPlaceholder = (
} ) ;
} ;
const withPreviewUpload = ( ) = > {
// 检查是否为图片文件的辅助函数
const isImageFile = ( file : UploadFile ) : boolean = > {
const imageExtensions = new Set ( [
'bmp' ,
'gif' ,
'jpeg' ,
'jpg' ,
'png' ,
'svg' ,
'webp' ,
] ) ;
if ( file . url ) {
try {
const pathname = new URL ( file . url , 'http://localhost' ) . pathname ;
const ext = pathname . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? imageExtensions . has ( ext ) : false ;
} catch {
const ext = file . url ? . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? imageExtensions . has ( ext ) : false ;
}
}
if ( ! file . type ) {
const ext = file . name ? . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? imageExtensions . has ( ext ) : false ;
const IMAGE_EXTENSIONS = new Set ( [
'bmp' ,
'gif' ,
'jpeg' ,
'jpg' ,
'png' ,
'svg' ,
'webp' ,
] ) ;
/ * *
* 检 查 是 否 为 图 片 文 件
* /
function isImageFile ( file : UploadFile ) : boolean {
if ( file . url ) {
try {
const pathname = new URL ( file . url , 'http://localhost' ) . pathname ;
const ext = pathname . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? IMAGE_EXTENSIONS . has ( ext ) : false ;
} catch {
const ext = file . url ? . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? IMAGE_EXTENSIONS . has ( ext ) : false ;
}
return file . type . startsWith ( 'image/' ) ;
}
if ( ! file . type ) {
const ext = file . name ? . split ( '.' ) . pop ( ) ? . toLowerCase ( ) ;
return ext ? IMAGE_EXTENSIONS . has ( ext ) : false ;
}
return file . type . startsWith ( 'image/' ) ;
}
/ * *
* 创 建 默 认 的 上 传 按 钮 插 槽
* /
function createDefaultUploadSlots ( listType : string , placeholder : string ) {
if ( listType === 'picture-card' ) {
return { default : ( ) = > placeholder } ;
}
return {
default : ( ) = >
h (
Button ,
{
icon : h ( IconifyIcon , {
icon : 'ant-design:upload-outlined' ,
class : 'mb-1 size-4' ,
} ) ,
} ,
( ) = > placeholder ,
) ,
} ;
// 创建默认的上传按钮插槽
const createDefaultSlotsWithUpload = (
listType : string ,
placeholder : string ,
) = > {
switch ( listType ) {
case 'picture-card' : {
return {
default : ( ) = > placeholder ,
} ;
}
default : {
return {
default : ( ) = >
h (
Button ,
{
icon : h ( IconifyIcon , {
icon : 'ant-design:upload-outlined' ,
class : 'mb-1 size-4' ,
} ) ,
}
/ * *
* 获 取 文 件 的 Base64
* /
function getBase64 ( file : File ) : Promise < string > {
return new Promise ( ( resolve , reject ) = > {
const reader = new FileReader ( ) ;
reader . readAsDataURL ( file ) ;
reader . addEventListener ( 'load' , ( ) = > resolve ( reader . result as string ) ) ;
reader . addEventListener ( 'error' , reject ) ;
} ) ;
}
/ * *
* 预 览 图 片
* /
async function previewImage (
file : UploadFile ,
open : Ref < boolean > ,
fileList : Ref < UploadProps [ ' fileList ' ] > ,
) {
// 非图片文件直接打开链接
if ( ! isImageFile ( file ) ) {
const url = file . url || file . preview ;
if ( url ) {
window . open ( url , '_blank' ) ;
} else if ( file . preview ) {
window . open ( file . preview , '_blank' ) ;
} else {
message . error ( $t ( 'ui.formRules.previewWarning' ) ) ;
}
return ;
}
const [ ImageComponent , PreviewGroupComponent ] = await Promise . all ( [
Image ,
PreviewGroup ,
] ) ;
// 过滤图片文件并生成预览
const imageFiles = ( unref ( fileList ) || [ ] ) . filter ( ( f ) = > isImageFile ( f ) ) ;
for ( const imgFile of imageFiles ) {
if ( ! imgFile . url && ! imgFile . preview && imgFile . originFileObj ) {
imgFile . preview = await getBase64 ( imgFile . originFileObj ) ;
}
}
const container = document . createElement ( 'div' ) ;
document . body . append ( container ) ;
let isUnmounted = false ;
const currentIndex = imageFiles . findIndex ( ( f ) = > f . uid === file . uid ) ;
const PreviewWrapper = {
setup() {
return ( ) = > {
if ( isUnmounted ) return null ;
return h (
PreviewGroupComponent ,
{
class : 'hidden' ,
preview : {
open : open.value ,
current : currentIndex ,
onOpenChange : ( value : boolean ) = > {
open . value = value ;
if ( ! value ) {
setTimeout ( ( ) = > {
if ( ! isUnmounted && container ) {
isUnmounted = true ;
render ( null , container ) ;
container . remove ( ) ;
}
} , 300 ) ;
}
} ,
( ) = > placeholder ,
} ,
} ,
( ) = >
imageFiles . map ( ( imgFile ) = >
h ( ImageComponent , {
key : imgFile.uid ,
src : imgFile.url || imgFile . preview ,
} ) ,
) ,
} ;
}
}
) ;
} ;
} ,
} ;
// 构建预览图片组
const previewImage = async (
file : UploadFile ,
visible : Ref < boolean > ,
fileList : Ref < UploadProps [ ' fileList ' ] > ,
) = > {
// 如果当前文件不是图片,直接打开
if ( ! isImageFile ( file ) ) {
if ( file . url ) {
window . open ( file . url , '_blank' ) ;
} else if ( file . preview ) {
window . open ( file . preview , '_blank' ) ;
} else {
message . error ( $t ( 'ui.formRules.previewWarning' ) ) ;
}
return ;
}
// 对于图片文件,继续使用预览组
const [ ImageComponent , PreviewGroupComponent ] = await Promise . all ( [
Image ,
PreviewGroup ,
] ) ;
const getBase64 = ( file : File ) = > {
return new Promise ( ( resolve , reject ) = > {
const reader = new FileReader ( ) ;
reader . readAsDataURL ( file ) ;
reader . addEventListener ( 'load' , ( ) = > resolve ( reader . result ) ) ;
reader . addEventListener ( 'error' , ( error ) = > reject ( error ) ) ;
} ) ;
} ;
// 从fileList中过滤出所有图片文件
const imageFiles = ( unref ( fileList ) || [ ] ) . filter ( ( element ) = >
isImageFile ( element ) ,
) ;
// 为所有没有预览地址的图片生成预览
for ( const imgFile of imageFiles ) {
if ( ! imgFile . url && ! imgFile . preview && imgFile . originFileObj ) {
imgFile . preview = ( await getBase64 ( imgFile . originFileObj ) ) as string ;
}
}
const container : HTMLElement | null = document . createElement ( 'div' ) ;
render ( h ( PreviewWrapper ) , container ) ;
}
/ * *
* 图 片 裁 剪 操 作
* /
function cropImage ( file : File , aspectRatio : string | undefined ) {
return new Promise < Blob | string | undefined > ( ( resolve , reject ) = > {
const container = document . createElement ( 'div' ) ;
document . body . append ( container ) ;
// 用于追踪组件是否已卸载
let isUnmounted = false ;
let objectUrl : null | string = null ;
const open = ref < boolean > ( true ) ;
const cropperRef = ref < InstanceType < typeof VCropper > | null > ( null ) ;
const PreviewWrapper = {
function closeModal() {
open . value = false ;
setTimeout ( ( ) = > {
if ( ! isUnmounted && container ) {
if ( objectUrl ) {
URL . revokeObjectURL ( objectUrl ) ;
}
isUnmounted = true ;
render ( null , container ) ;
container . remove ( ) ;
}
} , 300 ) ;
}
const CropperWrapper = {
setup() {
return ( ) = > {
if ( isUnmounted ) return null ;
if ( ! objectUrl ) {
objectUrl = URL . createObjectURL ( file ) ;
}
return h (
PreviewGroupComponent ,
Modal ,
{
class : 'hidden' ,
preview : {
open : visible.value ,
// 设置初始显示的图片索引
current : imageFiles.findIndex ( ( f ) = > f . uid === file . uid ) ,
onOpenChange : ( value : boolean ) = > {
visible . value = value ;
if ( ! value ) {
// 延迟清理,确保动画完成
setTimeout ( ( ) = > {
if ( ! isUnmounted && container ) {
isUnmounted = true ;
render ( null , container ) ;
container . remove ( ) ;
}
} , 300 ) ;
open : open.value ,
title : h ( 'div' , { } , [
$t ( 'ui.crop.title' ) ,
h (
'span' ,
{
class : ` ${ aspectRatio ? '' : 'hidden' } ml-2 text-sm text-gray-400 font-normal ` ,
} ,
$t ( 'ui.crop.titleTip' , [ aspectRatio ] ) ,
) ,
] ) ,
centered : true ,
width : 548 ,
keyboard : false ,
maskClosable : false ,
closable : false ,
cancelText : $t ( 'common.cancel' ) ,
okText : $t ( 'ui.crop.confirm' ) ,
destroyOnHidden : true ,
onOk : async ( ) = > {
const cropper = cropperRef . value ;
if ( ! cropper ) {
reject ( new Error ( 'Cropper not found' ) ) ;
closeModal ( ) ;
return ;
}
try {
const dataUrl = await cropper . getCropImage ( ) ;
if ( dataUrl ) {
resolve ( dataUrl ) ;
} else {
reject ( new Error ( $t ( 'ui.crop.errorTip' ) ) ) ;
}
} ,
} catch {
reject ( new Error ( $t ( 'ui.crop.errorTip' ) ) ) ;
} finally {
closeModal ( ) ;
}
} ,
onCancel() {
resolve ( '' ) ;
closeModal ( ) ;
} ,
} ,
( ) = >
// 渲染所有图片文件
imageFiles . map ( ( imgFile ) = >
h ( ImageComponent , {
key : imgFile.uid ,
src : imgFile.url || imgFile . preview ,
} ) ,
) ,
h ( VCropper , {
ref : ( ref : any ) = > ( cropperRef . value = ref ) ,
img : objectUrl as string ,
aspectRatio ,
} ) ,
) ;
} ;
} ,
} ;
render ( h ( PreviewWrapper ) , container ) ;
} ;
// 图片裁剪操作
const cropImage = ( file : File , aspectRatio : string | undefined ) = > {
return new Promise ( ( resolve , reject ) = > {
const container : HTMLElement | null = document . createElement ( 'div' ) ;
document . body . append ( container ) ;
// 用于追踪组件是否已卸载
let isUnmounted = false ;
let objectUrl : null | string = null ;
const open = ref < boolean > ( true ) ;
const cropperRef = ref < InstanceType < typeof VCropper > | null > ( null ) ;
const closeModal = ( ) = > {
open . value = false ;
// 延迟清理,确保动画完成
setTimeout ( ( ) = > {
if ( ! isUnmounted && container ) {
if ( objectUrl ) {
URL . revokeObjectURL ( objectUrl ) ;
}
isUnmounted = true ;
render ( null , container ) ;
container . remove ( ) ;
}
} , 300 ) ;
} ;
const CropperWrapper = {
setup() {
return ( ) = > {
if ( isUnmounted ) return null ;
if ( ! objectUrl ) {
objectUrl = URL . createObjectURL ( file ) ;
}
return h (
Modal ,
{
open : open.value ,
title : h ( 'div' , { } , [
$t ( 'ui.crop.title' ) ,
h (
'span' ,
{
class : ` ${ aspectRatio ? '' : 'hidden' } ml-2 text-sm text-gray-400 font-normal ` ,
} ,
$t ( 'ui.crop.titleTip' , [ aspectRatio ] ) ,
) ,
] ) ,
centered : true ,
width : 548 ,
keyboard : false ,
maskClosable : false ,
closable : false ,
cancelText : $t ( 'common.cancel' ) ,
okText : $t ( 'ui.crop.confirm' ) ,
destroyOnHidden : true ,
onOk : async ( ) = > {
const cropper = cropperRef . value ;
if ( ! cropper ) {
reject ( new Error ( 'Cropper not found' ) ) ;
closeModal ( ) ;
return ;
}
try {
const dataUrl = await cropper . getCropImage ( ) ;
resolve ( dataUrl ) ;
} catch {
reject ( new Error ( $t ( 'ui.crop.errorTip' ) ) ) ;
} finally {
closeModal ( ) ;
}
} ,
onCancel() {
resolve ( '' ) ;
closeModal ( ) ;
} ,
} ,
( ) = >
h ( VCropper , {
ref : ( ref : any ) = > ( cropperRef . value = ref ) ,
img : objectUrl as string ,
aspectRatio ,
} ) ,
) ;
} ;
} ,
} ;
render ( h ( CropperWrapper ) , container ) ;
} ) ;
} ;
render ( h ( CropperWrapper ) , container ) ;
} ) ;
}
/ * *
* 带 预 览 功 能 的 上 传 组 件
* /
function withPreviewUpload() {
return defineComponent ( {
name : 'AUpload' ,
name : Upload.name ,
emits : [ 'update:modelValue' ] ,
setup : (
setup (
props : any ,
{ attrs , slots , emit } : { attrs : any ; emit : any ; slots : any } ,
) = > {
) {
const previewVisible = ref < boolean > ( false ) ;
const placeholder = attrs ? . placeholder || $t ( ` ui.placeholder.upload ` ) ;
const placeholder = attrs ? . placeholder || $t ( 'ui.placeholder.upload' ) ;
const listType = attrs ? . listType || attrs ? . [ 'list-type' ] || 'text' ;
const fileList = ref < UploadProps [ ' fileList ' ] > (
attrs ? . fileList || attrs ? . [ 'file-list' ] || [ ] ,
) ;
@ -433,16 +448,18 @@ const withPreviewUpload = () => {
( ) = > attrs ? . aspectRatio ? ? attrs ? . [ 'aspect-ratio' ] ,
) ;
const handleBeforeUpload = async (
async function handleBeforeUpload (
file : UploadFile ,
originFileList : Array < File > ,
) = > {
) {
// 文件大小限制
if ( maxSize . value && ( file . size || 0 ) / 1024 / 1024 > maxSize . value ) {
message . error ( $t ( 'ui.formRules.sizeLimit' , [ maxSize . value ] ) ) ;
file . status = 'removed' ;
return false ;
}
// 多选或者非图片不唤起裁剪框
// 图片裁剪处理
if (
attrs . crop &&
! attrs . multiple &&
@ -450,27 +467,21 @@ const withPreviewUpload = () => {
isImageFile ( file )
) {
file . status = 'removed' ;
// antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
const blob = await cropImage ( originFileList [ 0 ] , aspectRatio . value ) ;
return new Promise ( ( resolve , reject ) = > {
if ( ! blob ) {
return reject ( new Error ( $t ( 'ui.crop.errorTip' ) ) ) ;
}
resolve ( blob ) ;
} ) ;
if ( ! blob ) {
throw new Error ( $t ( 'ui.crop.errorTip' ) ) ;
}
return blob ;
}
return attrs . beforeUpload ? . ( file ) ? ? true ;
} ;
}
const handleChange = ( event : UploadChangeParam ) = > {
function handleChange ( event : UploadChangeParam ) {
try {
// 行内写法 handleChange: (event) => {}
attrs . handleChange ? . ( event ) ;
// template写法 @handle-change="(event) => {}"
attrs . onHandleChange ? . ( event ) ;
} catch ( error ) {
// Avoid breaking internal v-model sync on user handler errors
console . error ( error ) ;
}
fileList . value = event . fileList . filter (
@ -480,28 +491,95 @@ const withPreviewUpload = () => {
'update:modelValue' ,
event . fileList ? . length ? fileList.value : undefined ,
) ;
} ;
}
const handlePreview = async ( file : UploadFile ) = > {
function handlePreview ( file : UploadFile ) {
previewVisible . value = true ;
await previewImage ( file , previewVisible , fileList ) ;
} ;
return previewImage ( file , previewVisible , fileList ) ;
}
const renderUploadButton = ( ) : any = > {
const isDisabled = attrs . disabled ;
function renderUploadButton() {
if ( attrs . disabled ) return null ;
return isEmpty ( slots )
? createDefaultUploadSlots ( listType , placeholder )
: slots ;
}
// 如果禁用,不渲染上传按钮
if ( isDisabled ) {
return null ;
// 拖拽排序
const draggable = computed (
( ) = > ( attrs . draggable ? ? false ) && ! attrs . disabled ,
) ;
const uploadId = ` upload- ${ Date . now ( ) } - ${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 9 ) } ` ;
const sortableInstance = ref < null | Sortable > ( null ) ;
const styleId = ` upload-drag-style- ${ uploadId } ` ;
function injectDragStyle() {
if ( ! document . querySelector ( ` [id=" ${ styleId } "] ` ) ) {
const style = document . createElement ( 'style' ) ;
style . id = styleId ;
style . textContent = `
[ data - upload - id = "${uploadId}" ] . ant - upload - list - item { cursor : move ; }
[ data - upload - id = "${uploadId}" ] . ant - upload - list - item :hover { box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.15 ) ; }
` ;
document . head . append ( style ) ;
}
}
// 否则渲染默认上传按钮
return isEmpty ( slots )
? createDefaultSlotsWithUpload ( listType , placeholder )
: slots ;
} ;
function removeDragStyle() {
document . querySelector ( ` [id=" ${ styleId } "] ` ) ? . remove ( ) ;
}
async function initSortable ( retryCount = 0 ) {
if ( ! draggable . value ) return ;
injectDragStyle ( ) ;
await nextTick ( ) ;
await new Promise ( ( resolve ) = > setTimeout ( resolve , 100 ) ) ;
const container = document . querySelector (
` [data-upload-id=" ${ uploadId } "] .ant-upload-list ` ,
) as HTMLElement ;
if ( ! container ) {
if ( retryCount < 5 ) {
setTimeout ( ( ) = > initSortable ( retryCount + 1 ) , 200 ) ;
}
return ;
}
const { initializeSortable } = useSortable ( container , {
animation : 300 ,
delay : 400 ,
delayOnTouchOnly : true ,
filter :
'.ant-upload-select, .ant-upload-list-item-error, .ant-upload-list-item-uploading' ,
onEnd : ( evt ) = > {
const { oldIndex , newIndex } = evt ;
if (
oldIndex === undefined ||
newIndex === undefined ||
oldIndex === newIndex
) {
return ;
}
const list = [ . . . ( fileList . value || [ ] ) ] ;
const [ movedItem ] = list . splice ( oldIndex , 1 ) ;
if ( movedItem ) {
list . splice ( newIndex , 0 , movedItem ) ;
fileList . value = list ;
}
attrs . onDragSort ? . ( oldIndex , newIndex ) ;
emit ( 'update:modelValue' , fileList . value ) ;
} ,
} ) ;
sortableInstance . value = await initializeSortable ( ) ;
}
// 可以监听到表单API设置的值
// 监听表单值变化
watch (
( ) = > attrs . modelValue ,
( res ) = > {
@ -509,22 +587,32 @@ const withPreviewUpload = () => {
} ,
) ;
onMounted ( initSortable ) ;
onUnmounted ( ( ) = > {
sortableInstance . value ? . destroy ( ) ;
removeDragStyle ( ) ;
} ) ;
return ( ) = >
h (
Upload ,
{
. . . props ,
. . . attrs ,
fileList : fileList.value ,
beforeUpload : handleBeforeUpload ,
onChange : handleChange ,
onPreview : handlePreview ,
} ,
renderUploadButton ( ) ,
'div' ,
{ 'data-upload-id' : uploadId , class : 'w-full' } ,
h (
Upload ,
{
. . . props ,
. . . attrs ,
fileList : fileList.value ,
beforeUpload : handleBeforeUpload ,
onChange : handleChange ,
onPreview : handlePreview ,
} ,
renderUploadButton ( ) as any ,
) ,
) ;
} ,
} ) ;
} ;
}
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
export type ComponentType =
@ -535,6 +623,7 @@ export type ComponentType =
| 'Cascader'
| 'Checkbox'
| 'CheckboxGroup'
| 'CollapsibleParams'
| 'DatePicker'
| 'DefaultButton'
| 'Divider'
@ -548,6 +637,7 @@ export type ComponentType =
| 'RadioGroup'
| 'RangePicker'
| 'Rate'
| 'RichEditor'
| 'Select'
| 'Space'
| 'Switch'
@ -568,6 +658,7 @@ export interface ComponentPropsMap {
Cascader : CascaderProps ;
Checkbox : CheckboxProps ;
CheckboxGroup : CheckboxGroupProps ;
CollapsibleParams : CollapsibleParamsProps ;
DatePicker : DatePickerProps ;
DefaultButton : ButtonProps ;
Divider : DividerProps ;
@ -581,6 +672,7 @@ export interface ComponentPropsMap {
RadioGroup : RadioGroupProps ;
RangePicker : RangePickerProps ;
Rate : RateProps ;
RichEditor : TipTapProps ;
Select : SelectProps ;
Space : SpaceProps ;
Switch : SwitchProps ;
@ -601,13 +693,13 @@ async function initComponentAdapter() {
fieldNames : { label : 'label' , value : 'value' , children : 'children' } ,
loadingSlot : 'suffixIcon' ,
modelPropName : 'value' ,
visibleEvent : 'onVisible Change' ,
visibleEvent : 'onOpen Change' ,
} ) ,
ApiSelect : withDefaultPlaceholder ( ApiComponent , 'select' , {
component : Select ,
loadingSlot : 'suffixIcon' ,
modelPropName : 'value' ,
visibleEvent : 'onVisible Change' ,
visibleEvent : 'onOpen Change' ,
} ) ,
ApiTreeSelect : withDefaultPlaceholder ( ApiComponent , 'select' , {
component : TreeSelect ,
@ -615,7 +707,7 @@ async function initComponentAdapter() {
loadingSlot : 'suffixIcon' ,
modelPropName : 'value' ,
optionsPropName : 'treeData' ,
visibleEvent : 'onVisible Change' ,
visibleEvent : 'onOpen Change' ,
} ) ,
AutoComplete ,
Cascader ,
@ -646,6 +738,27 @@ async function initComponentAdapter() {
RadioGroup ,
RangePicker ,
Rate ,
RichEditor : withDefaultPlaceholder ( VbenTiptap , 'input' , {
imageUpload : {
upload : ( file : any , onProgress : any ) = > {
return new Promise ( ( resolve , reject ) = > {
upload_file ( {
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' ) ,
Space ,
Switch ,
@ -653,6 +766,7 @@ async function initComponentAdapter() {
TimePicker ,
TreeSelect : withDefaultPlaceholder ( TreeSelect , 'select' ) ,
Upload : withPreviewUpload ( ) ,
CollapsibleParams : VbenCollapsibleParams ,
} ;
// 将组件注册到全局共享状态中