|
|
|
@ -29,7 +29,7 @@ import { IconifyIcon } from '@vben/icons'; |
|
|
|
import { $t } from '@vben/locales'; |
|
|
|
import { isEmpty } from '@vben/utils'; |
|
|
|
|
|
|
|
import { notification } from 'ant-design-vue'; |
|
|
|
import { message, notification } from 'ant-design-vue'; |
|
|
|
|
|
|
|
const AutoComplete = defineAsyncComponent( |
|
|
|
() => import('ant-design-vue/es/auto-complete'), |
|
|
|
@ -75,6 +75,9 @@ const TimePicker = defineAsyncComponent( |
|
|
|
const TreeSelect = defineAsyncComponent( |
|
|
|
() => import('ant-design-vue/es/tree-select'), |
|
|
|
); |
|
|
|
const Cascader = defineAsyncComponent( |
|
|
|
() => import('ant-design-vue/es/cascader'), |
|
|
|
); |
|
|
|
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); |
|
|
|
const Image = defineAsyncComponent(() => import('ant-design-vue/es/image')); |
|
|
|
const PreviewGroup = defineAsyncComponent(() => |
|
|
|
@ -125,79 +128,11 @@ const withDefaultPlaceholder = <T extends Component>( |
|
|
|
}; |
|
|
|
|
|
|
|
const withPreviewUpload = () => { |
|
|
|
return defineComponent({ |
|
|
|
name: Upload.name, |
|
|
|
emits: ['change', 'update:modelValue'], |
|
|
|
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 listType = attrs?.listType || attrs?.['list-type'] || 'text'; |
|
|
|
|
|
|
|
const fileList = ref<UploadProps['fileList']>( |
|
|
|
attrs?.fileList || attrs?.['file-list'] || [], |
|
|
|
); |
|
|
|
|
|
|
|
const handleChange = async (event: UploadChangeParam) => { |
|
|
|
fileList.value = event.fileList; |
|
|
|
emit('change', event); |
|
|
|
emit( |
|
|
|
'update:modelValue', |
|
|
|
event.fileList?.length ? fileList.value : undefined, |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const handlePreview = async (file: UploadFile) => { |
|
|
|
previewVisible.value = true; |
|
|
|
await previewImage(file, previewVisible, fileList); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderUploadButton = (): any => { |
|
|
|
const isDisabled = attrs.disabled; |
|
|
|
|
|
|
|
// 如果禁用,不渲染上传按钮
|
|
|
|
if (isDisabled) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 否则渲染默认上传按钮
|
|
|
|
return isEmpty(slots) |
|
|
|
? createDefaultSlotsWithUpload(listType, placeholder) |
|
|
|
: slots; |
|
|
|
}; |
|
|
|
|
|
|
|
// 可以监听到表单API设置的值
|
|
|
|
watch( |
|
|
|
() => attrs.modelValue, |
|
|
|
(res) => { |
|
|
|
fileList.value = res; |
|
|
|
}, |
|
|
|
); |
|
|
|
|
|
|
|
return () => |
|
|
|
h( |
|
|
|
Upload, |
|
|
|
{ |
|
|
|
...props, |
|
|
|
...attrs, |
|
|
|
fileList: fileList.value, |
|
|
|
onChange: handleChange, |
|
|
|
onPreview: handlePreview, |
|
|
|
}, |
|
|
|
renderUploadButton(), |
|
|
|
); |
|
|
|
}, |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
const createDefaultSlotsWithUpload = ( |
|
|
|
// 创建默认的上传按钮插槽
|
|
|
|
const createDefaultSlotsWithUpload = ( |
|
|
|
listType: string, |
|
|
|
placeholder: string, |
|
|
|
) => { |
|
|
|
) => { |
|
|
|
switch (listType) { |
|
|
|
case 'picture-card': { |
|
|
|
return { |
|
|
|
@ -220,13 +155,13 @@ const createDefaultSlotsWithUpload = ( |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const previewImage = async ( |
|
|
|
}; |
|
|
|
// 构建预览图片组
|
|
|
|
const previewImage = async ( |
|
|
|
file: UploadFile, |
|
|
|
visible: Ref<boolean>, |
|
|
|
fileList: Ref<UploadProps['fileList']>, |
|
|
|
) => { |
|
|
|
) => { |
|
|
|
// 检查是否为图片文件的辅助函数
|
|
|
|
const isImageFile = (file: UploadFile): boolean => { |
|
|
|
const imageExtensions = new Set([ |
|
|
|
@ -255,7 +190,7 @@ const previewImage = async ( |
|
|
|
} else if (file.preview) { |
|
|
|
window.open(file.preview, '_blank'); |
|
|
|
} else { |
|
|
|
console.warn('无法打开文件,没有可用的URL或预览地址'); |
|
|
|
message.error($t('ui.formRules.previewWarning')); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
@ -332,13 +267,94 @@ const previewImage = async ( |
|
|
|
}; |
|
|
|
|
|
|
|
render(h(PreviewWrapper), container); |
|
|
|
}; |
|
|
|
return defineComponent({ |
|
|
|
name: Upload.name, |
|
|
|
emits: ['update:modelValue'], |
|
|
|
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 listType = attrs?.listType || attrs?.['list-type'] || 'text'; |
|
|
|
|
|
|
|
const fileList = ref<UploadProps['fileList']>( |
|
|
|
attrs?.fileList || attrs?.['file-list'] || [], |
|
|
|
); |
|
|
|
|
|
|
|
const handleBeforeUpload = (file: UploadFile) => { |
|
|
|
if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) { |
|
|
|
message.error($t('ui.formRules.sizeLimit', [attrs.maxSize])); |
|
|
|
file.status = 'removed'; |
|
|
|
return false; |
|
|
|
} |
|
|
|
return attrs.beforeUpload?.(file) ?? true; |
|
|
|
}; |
|
|
|
|
|
|
|
const handleChange = async (event: UploadChangeParam) => { |
|
|
|
fileList.value = event.fileList.filter( |
|
|
|
(file) => file.status !== 'removed', |
|
|
|
); |
|
|
|
emit( |
|
|
|
'update:modelValue', |
|
|
|
event.fileList?.length ? fileList.value : undefined, |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
const handlePreview = async (file: UploadFile) => { |
|
|
|
previewVisible.value = true; |
|
|
|
await previewImage(file, previewVisible, fileList); |
|
|
|
}; |
|
|
|
|
|
|
|
const renderUploadButton = (): any => { |
|
|
|
const isDisabled = attrs.disabled; |
|
|
|
|
|
|
|
// 如果禁用,不渲染上传按钮
|
|
|
|
if (isDisabled) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
// 否则渲染默认上传按钮
|
|
|
|
return isEmpty(slots) |
|
|
|
? createDefaultSlotsWithUpload(listType, placeholder) |
|
|
|
: slots; |
|
|
|
}; |
|
|
|
|
|
|
|
// 可以监听到表单API设置的值
|
|
|
|
watch( |
|
|
|
() => attrs.modelValue, |
|
|
|
(res) => { |
|
|
|
fileList.value = res; |
|
|
|
}, |
|
|
|
); |
|
|
|
|
|
|
|
return () => |
|
|
|
h( |
|
|
|
Upload, |
|
|
|
{ |
|
|
|
...props, |
|
|
|
...attrs, |
|
|
|
fileList: fileList.value, |
|
|
|
beforeUpload: handleBeforeUpload, |
|
|
|
onChange: handleChange, |
|
|
|
onPreview: handlePreview, |
|
|
|
}, |
|
|
|
renderUploadButton(), |
|
|
|
); |
|
|
|
}, |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
|
|
|
export type ComponentType = |
|
|
|
| 'ApiCascader' |
|
|
|
| 'ApiSelect' |
|
|
|
| 'ApiTreeSelect' |
|
|
|
| 'AutoComplete' |
|
|
|
| 'Cascader' |
|
|
|
| 'Checkbox' |
|
|
|
| 'CheckboxGroup' |
|
|
|
| 'DatePicker' |
|
|
|
@ -369,6 +385,13 @@ async function initComponentAdapter() { |
|
|
|
// Button: () =>
|
|
|
|
// import('xxx').then((res) => res.Button),
|
|
|
|
|
|
|
|
ApiCascader: withDefaultPlaceholder(ApiComponent, 'select', { |
|
|
|
component: Cascader, |
|
|
|
fieldNames: { label: 'label', value: 'value', children: 'children' }, |
|
|
|
loadingSlot: 'suffixIcon', |
|
|
|
modelPropName: 'value', |
|
|
|
visibleEvent: 'onVisibleChange', |
|
|
|
}), |
|
|
|
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', { |
|
|
|
component: Select, |
|
|
|
loadingSlot: 'suffixIcon', |
|
|
|
@ -384,6 +407,7 @@ async function initComponentAdapter() { |
|
|
|
visibleEvent: 'onVisibleChange', |
|
|
|
}), |
|
|
|
AutoComplete, |
|
|
|
Cascader, |
|
|
|
Checkbox, |
|
|
|
CheckboxGroup, |
|
|
|
DatePicker, |
|
|
|
|