From 7dfa21246d61cc576c8ab8724c0f9d542e03d9c3 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 24 Oct 2024 20:37:43 +0800 Subject: [PATCH] Optimize upload component (50%) --- .../edit/Upload/components/Audio.tsx | 19 ++ .../edit/Upload/components/UploadBtn.tsx | 28 +- .../xgen/components/edit/Upload/filemap.tsx | 67 ++++- .../xgen/components/edit/Upload/index.less | 243 ++++++++++-------- .../xgen/components/edit/Upload/index.tsx | 48 +--- packages/xgen/components/edit/Upload/types.ts | 23 +- packages/xgen/knife/yao/getFileSrc.ts | 4 +- 7 files changed, 261 insertions(+), 171 deletions(-) create mode 100644 packages/xgen/components/edit/Upload/components/Audio.tsx diff --git a/packages/xgen/components/edit/Upload/components/Audio.tsx b/packages/xgen/components/edit/Upload/components/Audio.tsx new file mode 100644 index 00000000..f70c0fa9 --- /dev/null +++ b/packages/xgen/components/edit/Upload/components/Audio.tsx @@ -0,0 +1,19 @@ +import { getFileSrc } from '@/knife' +import { DeleteOutlined } from '@ant-design/icons' + +import type { IPropsCustomRender } from '../types' + +const Index = (props: IPropsCustomRender) => { + const { file, remove } = props + + return ( +
+
+ +
+ +
+ ) +} + +export default window.$app.memo(Index) diff --git a/packages/xgen/components/edit/Upload/components/UploadBtn.tsx b/packages/xgen/components/edit/Upload/components/UploadBtn.tsx index a76622ff..7df0fd4c 100644 --- a/packages/xgen/components/edit/Upload/components/UploadBtn.tsx +++ b/packages/xgen/components/edit/Upload/components/UploadBtn.tsx @@ -1,27 +1,17 @@ import clsx from 'clsx' - -import { CloudUploadOutlined } from '@ant-design/icons' -import { getLocale } from '@umijs/max' - -import filemap from '../filemap' - import type { IPropsUploadBtn } from '../types' +import { Icon } from '@/widgets' const Index = (props: IPropsUploadBtn) => { - const { length, filetype, maxCount, desc } = props - const locale = getLocale() - + const { filetype, placeholder, placeholderIcon } = props + const iconProps = { + name: typeof placeholderIcon === 'string' ? placeholderIcon : placeholderIcon?.name || 'cloud-upload', + size: typeof placeholderIcon === 'string' ? 14 : placeholderIcon?.size || 14 + } return ( -
- - {desc ?? filemap[filetype].desc[locale]} +
+ + {placeholder}
) } diff --git a/packages/xgen/components/edit/Upload/filemap.tsx b/packages/xgen/components/edit/Upload/filemap.tsx index 53d92798..11185f9d 100644 --- a/packages/xgen/components/edit/Upload/filemap.tsx +++ b/packages/xgen/components/edit/Upload/filemap.tsx @@ -1,31 +1,82 @@ import Video from './components/Video' +import Audio from './components/Audio' +import Image from './components/Image' -import type { FileType } from './types' +import type { FileType, PreviewProps } from './types' +import { UploadFile } from 'antd' export default { image: { listType: 'picture-card', className: 'image', - desc: { + placeholder: { 'zh-CN': '上传图片', 'en-US': 'Upload Image' + }, + placeholderIcon: 'icon-upload', + preview: (props: PreviewProps, file: UploadFile, remove: () => void) => { + return + } + }, + + audio: { + listType: 'picture-card', + className: 'image', + placeholder: { + 'zh-CN': '上传音频', + 'en-US': 'Upload Audio' + }, + placeholderIcon: 'icon-upload', + preview: (props: PreviewProps, file: UploadFile, remove: () => void) => { + return
Image
} }, + file: { - listType: 'text', - className: 'file', - desc: { + listType: 'picture-card', + className: 'image', + placeholder: { 'zh-CN': '上传文件', 'en-US': 'Upload File' + }, + placeholderIcon: 'icon-upload', + preview: (props: PreviewProps, file: UploadFile, remove: () => void) => { + return
File
} }, + video: { listType: 'picture-card', - className: 'video', - desc: { + className: 'image', + placeholder: { 'zh-CN': '上传视频', 'en-US': 'Upload Video' }, - render: (_, file, fileList, { remove }) => + placeholderIcon: 'icon-upload', + preview: (props: PreviewProps, file: UploadFile, remove: () => void) => { + return
Video
+ } } } as FileType + +function getSize(size: PreviewProps['size']): PreviewProps['size'] { + const defaultSize: PreviewProps['size'] = { + width: size?.width || '90px', + height: size?.height || '90px', + ratio: size?.ratio || 1 + } + + if (defaultSize.ratio && defaultSize.ratio != 1) { + const width = parseInt(defaultSize.width as string) + const height = parseInt(defaultSize.height as string) + if (!width && !height) { + defaultSize.width = '100%' + defaultSize.height = `${100 * defaultSize.ratio}%` + } + if (width) { + defaultSize.width = `${width * defaultSize.ratio}px` + } else if (height) defaultSize.height = `${width * defaultSize.ratio}px` + } + + return defaultSize +} diff --git a/packages/xgen/components/edit/Upload/index.less b/packages/xgen/components/edit/Upload/index.less index db441e08..a1d6224c 100644 --- a/packages/xgen/components/edit/Upload/index.less +++ b/packages/xgen/components/edit/Upload/index.less @@ -3,130 +3,173 @@ &.image { :global { + @min_width: 90px; + @min_height: 90px; + @width: 90px; + @height: 90px; + + .form_item_upload_wrap { + padding-top: 11px; + padding-left: 10px; + padding-right: 10px; + + .xgen-upload-select-picture-card { + min-height: @min_height; + width: @width; + height: auto; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .xgen-upload-list-picture-card-container { + min-height: @min_height; + min-width: @min_width; + width: auto; + height: auto; + .image { + max-width: 100%; + object-fit: cover; + } + } + } + } + } + + &.video { + :global { + @min_width: 160px; + @min_height: 90px; + @width: 160px; + @height: 100px; + .form_item_upload_wrap { padding-top: 11px; padding-left: 10px; &.custom { @image_size: unset; - @min_image_size: 83px; - .xgen-upload-select-picture-card { - min-width: @min_image_size; - min-height: @min_image_size; - width: @image_size; - height: @image_size; + min-width: @min_width; + min-height: @min_height; + width: @width; + height: @height; } .xgen-upload-list-picture-card-container { - min-width: @min_image_size; - min-height: @min_image_size; + min-width: @min_width; + min-height: @min_height; width: @image_size; - height: @image_size; + height: @height; } } - @image_size: 83px; - .xgen-upload-select-picture-card { - width: @image_size; - height: @image_size; - display: inline-flex; - align-items: center; - justify-content: center; + width: @width; + height: @height; + display: inline-flex; + align-items: center; + justify-content: center; } .xgen-upload-list-picture-card-container { - width: @image_size; - height: @image_size; + width: @width; + height: @height; } } } } - &.file { + &.file, + &.audio { :global { + @min_width: 160px; + @min_height: 36px; + @width: 150px; + @height: 36px; + .form_item_upload_wrap { - display: flex; - flex-direction: column-reverse; - border-radius: 6px; - background-color: var(--color_bg_nav); + padding-top: 11px; + padding-left: 10px; - .xgen-upload-list-text-container { - .xgen-upload-list-item { - margin: 0; - height: 36px; + &.custom { + @image_size: unset; + @min_image_size: 100px; - .xgen-upload-list-item-info { - padding: 0 9px; - border-radius: 6px; - } + .xgen-upload-select-picture-card { + min-width: @min_width; + min-height: @min_height; + width: @width; + height: @height; + } + + .xgen-upload-list-picture-card-container { + min-width: @min_width; + min-height: @min_height; + width: @width; + height: @height; } } - .btn_upload_wrap { - padding: 0 9px; + .xgen-upload-select-picture-card { + width: @width; + height: @height; + display: inline-flex; + align-items: center; + justify-content: center; } - } - } - } - &.video { - :global { - .form_item_upload_wrap { - .xgen-upload-select-picture-card, .xgen-upload-list-picture-card-container { - width: 300px; - height: 160px; + width: @width; + height: @height; } } } } :global { - .upload_custom_wrap { - &:hover { - .icon_delete { - display: flex; - } - } - - .video { - width: 100%; - height: 160px; - border-radius: 6px; - - object-fit: fill; - } - - .icon_delete { - right: -1px; - bottom: -1px; - z-index: 1; - display: none; - width: 27px; - height: 27px; - border-top-left-radius: 6px; - border-bottom-right-radius: 6px; - background-color: white; - color: black; - cursor: pointer; - - &:hover { - background-color: var(--color_danger); - - .icon { - color: white; - } - } - - .icon { - color: var(--color_danger); - font-size: 15px; - } - } - } - + .upload_custom_wrap { + &:hover { + .icon_delete { + display: flex; + } + } + + .video { + width: 100%; + height: 160px; + border-radius: 6px; + object-fit: fill; + } + + .icon_delete { + right: -1px; + bottom: -1px; + z-index: 1; + display: none; + width: 27px; + height: 27px; + border-top-left-radius: 6px; + border-bottom-right-radius: 6px; + background-color: white; + color: black; + cursor: pointer; + + &:hover { + background-color: var(--color_danger); + + .icon { + color: white; + } + } + + .icon { + color: var(--color_danger); + font-size: 15px; + } + } + } + .xgen-upload.xgen-upload-select-picture-card { margin-right: 11px; margin-bottom: 11px; @@ -142,27 +185,22 @@ display: flex; flex-direction: column; - &.file { - align-items: center; - flex-direction: row; - - &.has_data { - margin-bottom: 12px; - } + .desc { + margin-top: 6px; + font-size: 12px; + } + &.file, + &.audio { .desc { - margin-top: 0; - margin-left: 9px; + margin-left: 6px; + margin-top: 0px; } - } - &.one_file { - justify-content: center; - height: 150px; - } - - .desc { - margin-top: 6px; + margin-top: 0; + align-items: center; + flex-direction: row; + align-items: center; } } @@ -182,7 +220,6 @@ .xgen-upload-list-picture-card .xgen-upload-list-item-thumbnail, .xgen-upload-list-picture-card .xgen-upload-list-item-thumbnail img { opacity: 1; - object-fit: cover; } } diff --git a/packages/xgen/components/edit/Upload/index.tsx b/packages/xgen/components/edit/Upload/index.tsx index 329efc98..419106f2 100644 --- a/packages/xgen/components/edit/Upload/index.tsx +++ b/packages/xgen/components/edit/Upload/index.tsx @@ -13,12 +13,14 @@ import useList from './hooks/useList' import useVisibleBtn from './hooks/useVisibleBtn' import styles from './index.less' import handleFileList from './utils/handleFileList' +import { getLocale } from '@umijs/max' import type { UploadProps } from 'antd' -import type { IProps, CustomProps, IPropsUploadBtn } from './types' +import type { IProps, CustomProps, IPropsUploadBtn, PreviewProps } from './types' const Custom = window.$app.memo((props: CustomProps) => { - const { api, maxCount, desc, imageSize, onChange: trigger } = props + const locale = getLocale() + const { api, maxCount, desc, previewSize, imageSize, onChange: trigger } = props const { list, setList } = useList(props.value) const visible_btn = useVisibleBtn(list.length, maxCount || 1) const filetype = filemap[props.filetype] ? props.filetype : 'image' @@ -37,7 +39,7 @@ const Custom = window.$app.memo((props: CustomProps) => { const action = useMemo(() => { if (typeof api === 'string') return api - + if (typeof api === 'undefined') return '' return `${api.api}?${new URLSearchParams(api.params).toString()}` }, [api]) @@ -45,11 +47,7 @@ const Custom = window.$app.memo((props: CustomProps) => { ...props, name: 'file', listType: filemap[filetype].listType, - className: clsx([ - 'form_item_upload_wrap', - filemap[filetype].className, - filetype === 'image' && imageSize && 'custom' - ]), + className: clsx(['form_item_upload_wrap', filemap[filetype].className]), action, headers: { authorization: getToken() }, fileList: list, @@ -57,41 +55,23 @@ const Custom = window.$app.memo((props: CustomProps) => { onChange } - if (filemap[filetype]?.render) { - props_upload['itemRender'] = filemap[filetype].render - } - - if (filetype === 'image' && imageSize) { - if (!imageSize.width && !imageSize.height) { - imageSize.height = '92px' - } - - // If the ratio is set, the width will be 100% and the height will be calculated based on the ratio - if (imageSize.ratio) { - imageSize.width = `100%` - imageSize.height = `calc(100% / ${imageSize.ratio})` - } - - props_upload['itemRender'] = (_, file, _fileList, { remove }) => ( - - ) + const preview_props: PreviewProps = { size: previewSize || imageSize || undefined } + props_upload['itemRender'] = (_, file, fileList, { remove }) => { + return filemap[filetype].preview(preview_props, file, remove) } const props_upload_btn: IPropsUploadBtn = { length: list.length, filetype, maxCount, - desc + placeholder: + props.placeholder || filemap[filetype].placeholder[locale] || filemap['file'].placeholder['en-US'], + placeholderIcon: + props.placeholderIcon || filemap[filetype].placeholderIcon || filemap['file'].placeholderIcon } return ( -
+
1 && 'multiple')}> {visible_btn && }
) diff --git a/packages/xgen/components/edit/Upload/types.ts b/packages/xgen/components/edit/Upload/types.ts index 93ced140..81ce5a0a 100644 --- a/packages/xgen/components/edit/Upload/types.ts +++ b/packages/xgen/components/edit/Upload/types.ts @@ -4,9 +4,10 @@ import type { Component } from '@/types' interface CommonProps { value: Array - filetype: 'image' | 'file' | 'video' + filetype: 'image' | 'file' | 'video' | 'audio' desc?: string - imageSize?: { width: string; height: string; ratio?: number } + imageSize?: { width?: string | number; height?: string | number; ratio?: number } // will be deprecated, use previewSize instead + previewSize?: { width?: string | number; height?: string | number; ratio?: number } } export interface Storage { @@ -23,6 +24,12 @@ export interface IProps extends UploadProps, Component.PropsEditComponent, Commo export interface CustomProps extends UploadProps, CommonProps { api: string | { api: string; params: any } + placeholder?: string // the placeholder for the upload + placeholderIcon?: string | { name: string; size: number } // the placeholder icon for the upload + height?: number | string // the height for the upload + rows?: number // the rows for the upload. if set, height will be ignored + chunkSize?: number | string // the chunk size for the upload, if set, will use the chunk upload + previewURL?: string // the url for the preview image, if set, will use the preview image or video appRoot?: boolean // if false, use the data root, else use the app root, default is false storage?: Storage // storage option for the upload to the storage server directly (e.g. firebase, s3, etc) aigc?: AIGC // aigc option for the upload. if set, will use the ai generated image @@ -32,8 +39,9 @@ export interface FileType { [key: string]: { listType: UploadProps['listType'] className: string - desc: { [key: string]: string } - render?: UploadProps['itemRender'] + placeholder: { [key: string]: CustomProps['placeholder'] } + placeholderIcon: CustomProps['placeholderIcon'] + preview: (props: PreviewProps, file: UploadFile, remove: () => void) => JSX.Element } } @@ -52,5 +60,10 @@ export interface IPropsUploadBtn { length: number filetype: IProps['filetype'] maxCount: IProps['maxCount'] - desc: IProps['desc'] + placeholder: CustomProps['placeholder'] + placeholderIcon: CustomProps['placeholderIcon'] +} + +export interface PreviewProps { + size?: CommonProps['previewSize'] } diff --git a/packages/xgen/knife/yao/getFileSrc.ts b/packages/xgen/knife/yao/getFileSrc.ts index 7400afae..6c5bc9dc 100644 --- a/packages/xgen/knife/yao/getFileSrc.ts +++ b/packages/xgen/knife/yao/getFileSrc.ts @@ -1,9 +1,9 @@ import getToken from './getToken' const Index = (name: string, appRoot: boolean = false) => { - if (name && name.startsWith('http')) return name + if (typeof name === 'string' && name.startsWith('http')) return name const token = getToken() - const c = name.includes('?') ? '&' : '?' + const c = typeof name == 'string' && (name.includes('?') ? '&' : '?') return appRoot ? `${name}${c}token=${token}&app=1` : `${name}${c}token=${token}` }