diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f71753616..711109c24 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -11,3 +11,4 @@ export * from './useLazyLoad'; export * from './useResizeObserver'; export * from './useVirtualScrollNew'; export * from './useVModel'; +export * from './useImagePreviewUrl'; diff --git a/src/hooks/useImagePreviewUrl.ts b/src/hooks/useImagePreviewUrl.ts new file mode 100644 index 000000000..51da09292 --- /dev/null +++ b/src/hooks/useImagePreviewUrl.ts @@ -0,0 +1,27 @@ +import { + ComputedRef, ref, Ref, watch, +} from '@vue/composition-api'; +import { getFileUrlByFileRaw } from '../_common/js/upload/utils'; + +export function useImagePreviewUrl(imgUrl: Ref | ComputedRef) { + const previewUrl = ref(''); + + watch( + [imgUrl], + ([imgUrl], [preImgUrl]) => { + if (preImgUrl === imgUrl) return; + if (typeof imgUrl === 'string') { + previewUrl.value = imgUrl; + return; + } + getFileUrlByFileRaw(imgUrl).then((url) => { + previewUrl.value = url; + }); + }, + { immediate: true }, + ); + + return { previewUrl }; +} + +export default useImagePreviewUrl; diff --git a/src/image-viewer/base/ImageItem.tsx b/src/image-viewer/base/ImageItem.tsx index 1d1afcc26..f7db66810 100644 --- a/src/image-viewer/base/ImageItem.tsx +++ b/src/image-viewer/base/ImageItem.tsx @@ -1,10 +1,11 @@ import { - computed, defineComponent, ref, watch, + computed, defineComponent, PropType, ref, toRefs, watch, } from '@vue/composition-api'; import { ImageErrorIcon } from 'tdesign-icons-vue'; import { useConfig } from '../../hooks/useConfig'; import { useDrag } from '../hooks'; import { setTransform } from '../../utils/helper'; +import { useImagePreviewUrl } from '../../hooks'; export default defineComponent({ name: 'TImageItem', @@ -12,15 +13,20 @@ export default defineComponent({ rotate: Number, scale: Number, mirror: Number, - src: String, - placementSrc: String, + src: [String, File] as PropType, + placementSrc: [String, File] as PropType, }, + setup(props) { + const { src, placementSrc } = toRefs(props); const { classPrefix, global: globalConfig } = useConfig('imageViewer'); const error = ref(false); const loaded = ref(false); const { transform, mouseDownHandler } = useDrag({ translateX: 0, translateY: 0 }); + const { previewUrl: mainImagePreviewUrl } = useImagePreviewUrl(src); + const { previewUrl: placementImagePreviewUrl } = useImagePreviewUrl(placementSrc); + const imgStyle = computed(() => ({ ...setTransform(`rotate(${props.rotate}deg) scale(${props.scale})`), display: !props.placementSrc || loaded.value ? 'block' : 'none', @@ -55,6 +61,8 @@ export default defineComponent({ mouseDownHandler, placementImgStyle, imgStyle, + mainImagePreviewUrl, + placementImagePreviewUrl, }; }, render() { @@ -71,28 +79,28 @@ export default defineComponent({ )} - {!this.error && !!this.placementSrc && ( + {!this.error && !!this.placementSrc && this.placementImagePreviewUrl && ( { event.stopPropagation(); this.mouseDownHandler(event); }} - src={this.placementSrc} + src={this.placementImagePreviewUrl} style={this.placementImgStyle} alt="image" draggable="false" /> )} - {!this.error && ( + {!this.error && this.mainImagePreviewUrl && ( { event.stopPropagation(); this.mouseDownHandler(event); }} - src={this.src} + src={this.mainImagePreviewUrl} onLoad={() => (this.loaded = true)} onError={() => (this.error = true)} style={this.imgStyle} diff --git a/src/image-viewer/base/ImageViewerUtils.tsx b/src/image-viewer/base/ImageViewerUtils.tsx index 0710bcb8e..aeeb1cec1 100644 --- a/src/image-viewer/base/ImageViewerUtils.tsx +++ b/src/image-viewer/base/ImageViewerUtils.tsx @@ -1,13 +1,12 @@ -import { defineComponent, PropType } from '@vue/composition-api'; +import { computed, defineComponent, PropType } from '@vue/composition-api'; import { ImageIcon, ZoomInIcon, ZoomOutIcon, DownloadIcon, MirrorIcon, RotationIcon, } from 'tdesign-icons-vue'; - import TImageViewerIcon from './ImageModalIcon'; import TToolTip from '../../tooltip'; import { useConfig } from '../../hooks/useConfig'; import { downloadFile } from '../utils'; - +import { useImagePreviewUrl } from '../../hooks'; import { ImageInfo } from '../type'; const currentImage = { @@ -28,12 +27,17 @@ export default defineComponent({ resetHandler: Function as PropType<() => void>, currentImage, }, - setup() { + setup(props) { const { classPrefix, global: globalConfig } = useConfig('imageViewer'); + const imageUrl = computed(() => props.currentImage.mainImage); + + const { previewUrl } = useImagePreviewUrl(imageUrl); + return { classPrefix, globalConfig, + previewUrl, }; }, render() { @@ -83,7 +87,7 @@ export default defineComponent({ } clickHandler={() => { - downloadFile(this.currentImage.mainImage); + downloadFile(this.previewUrl); }} /> )} diff --git a/src/image-viewer/image-viewer.en-US.md b/src/image-viewer/image-viewer.en-US.md index 81e7ede93..de569daf9 100644 --- a/src/image-viewer/image-viewer.en-US.md +++ b/src/image-viewer/image-viewer.en-US.md @@ -9,24 +9,23 @@ closeBtn | Boolean / Slot / Function | true | Typescript:`boolean \| TNode`。 closeOnOverlay | Boolean | - | \- | N draggable | Boolean | undefined | \- | N imageScale | Object | - | Typescript:`ImageScale` `interface ImageScale { max: number; min: number; step: number }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N -images | Array | [] | Typescript:`Array` `interface ImageInfo { mainImage: string; thumbnail?: string; download?: boolean }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N +images | Array | [] | Typescript:`Array` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N index | Number | - | `.sync` is supported | N defaultIndex | Number | - | uncontrolled property | N -mode | String | modal | options:modal/modeless | N +mode | String | modal | options: modal/modeless | N navigationArrow | Boolean / Slot / Function | true | Typescript:`boolean \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N showOverlay | Boolean | undefined | \- | N title | String / Slot / Function | - | preview title。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N trigger | String / Slot / Function | - | trigger element。Typescript:`string \| TNode<{ open: () => void }>`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N viewerScale | Object | - | Typescript:`ImageViewerScale` `interface ImageViewerScale { minWidth: number; minHeight: number }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N visible | Boolean | false | `v-model` is supported | N -defaultVisible | Boolean | false | uncontrolled property | N zIndex | Number | - | \- | N onClose | Function | | Typescript:`(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent }) => void`
| N -onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`
| N +onIndexChange | Function | | Typescript:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`
| N ### ImageViewer Events name | params | description -- | -- | -- close | `(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent })` | \- -index-change | `(index: number, context: { trigger: 'prev' \| 'next' })` | \- +index-change | `(index: number, context: { trigger: 'prev' \| 'next' \| 'current' })` | \- diff --git a/src/image-viewer/image-viewer.md b/src/image-viewer/image-viewer.md index 9c26977a8..cfebade10 100644 --- a/src/image-viewer/image-viewer.md +++ b/src/image-viewer/image-viewer.md @@ -9,7 +9,7 @@ closeBtn | Boolean / Slot / Function | true | 是否展示关闭按钮,值为 closeOnOverlay | Boolean | - | 是否在点击遮罩层时,触发预览关闭 | N draggable | Boolean | undefined | 是否允许拖拽调整位置。`mode=modal` 时,默认不允许拖拽;`mode=modeless` 时,默认允许拖拽 | N imageScale | Object | - | 图片缩放相关配置。`imageScale.max` 缩放的最大比例;`imageScale.min` 缩放的最小比例;`imageScale.step` 缩放的步长速度。TS 类型:`ImageScale` `interface ImageScale { max: number; min: number; step: number }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N -images | Array | [] | 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']`,`[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`。TS 类型:`Array` `interface ImageInfo { mainImage: string; thumbnail?: string; download?: boolean }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N +images | Array | [] | 图片数组。`mainImage` 表示主图,必传;`thumbnail` 表示缩略图,如果不存在,则使用主图显示;`download` 是否允许下载图片,默认允许下载。示例: `['img_url_1', 'img_url_2']`,`[{ thumbnail: 'small_image_url', mainImage: 'big_image_url', download: false }]`。TS 类型:`Array` `interface ImageInfo { mainImage: string \| File; thumbnail?: string \| File; download?: boolean }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N index | Number | - | 当前预览图片所在的下标。支持语法糖 `.sync` | N defaultIndex | Number | - | 当前预览图片所在的下标。非受控属性 | N mode | String | modal | 模态预览(modal)和非模态预览(modeless)。可选项:modal/modeless | N @@ -19,14 +19,13 @@ title | String / Slot / Function | - | 预览标题。TS 类型:`string \| TNo trigger | String / Slot / Function | - | 触发图片预览的元素,可能是一个预览按钮,可能是一张缩略图,完全自定义。TS 类型:`string \| TNode<{ open: () => void }>`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N viewerScale | Object | - | 限制预览器缩放的最小宽度和最小高度,仅 `mode=modeless` 时有效。TS 类型:`ImageViewerScale` `interface ImageViewerScale { minWidth: number; minHeight: number }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image-viewer/type.ts) | N visible | Boolean | false | 隐藏/显示预览。支持语法糖 `v-model` | N -defaultVisible | Boolean | false | 隐藏/显示预览。非受控属性 | N zIndex | Number | - | 层级,默认为 2000 | N onClose | Function | | TS 类型:`(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent }) => void`
关闭时触发,事件参数包含触发关闭的来源:关闭按钮、遮罩层、ESC 键 | N -onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' }) => void`
预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N +onIndexChange | Function | | TS 类型:`(index: number, context: { trigger: 'prev' \| 'next' \| 'current' }) => void`
预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 | N ### ImageViewer Events 名称 | 参数 | 描述 -- | -- | -- close | `(context: { trigger: 'close-btn' \| 'overlay' \| 'esc'; e: MouseEvent \| KeyboardEvent })` | 关闭时触发,事件参数包含触发关闭的来源:关闭按钮、遮罩层、ESC 键 -index-change | `(index: number, context: { trigger: 'prev' \| 'next' })` | 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 +index-change | `(index: number, context: { trigger: 'prev' \| 'next' \| 'current' })` | 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 diff --git a/src/image-viewer/image-viewer.tsx b/src/image-viewer/image-viewer.tsx index c0d15c5c9..928250ed4 100644 --- a/src/image-viewer/image-viewer.tsx +++ b/src/image-viewer/image-viewer.tsx @@ -2,7 +2,6 @@ import { computed, defineComponent, ref, toRefs, watch, } from '@vue/composition-api'; import { ChevronLeftIcon, ChevronDownIcon, CloseIcon } from 'tdesign-icons-vue'; - import props from './props'; import Container from './base/Container'; import TImageViewerIcon from './base/ImageModalIcon'; @@ -14,11 +13,11 @@ import useDefaultValue from '../hooks/useDefaultValue'; import { usePrefixClass } from '../hooks/useConfig'; import { renderTNodeJSX } from '../utils/render-tnode'; import { setTransform } from '../utils/helper'; - import { TdImageViewerProps } from './type'; import { useMirror, useRotate, useScale } from './hooks'; import { formatImages, getOverlay } from './utils'; import { EVENT_CODE } from './const'; +import Image from '../image'; export default defineComponent({ name: 'TImageViewer', @@ -87,7 +86,7 @@ export default defineComponent({ }; const onImgClick = (i: number) => { - setIndexValue(i, { trigger: i > indexValue.value ? 'next' : 'prev' }); + setIndexValue(i, { trigger: 'current' }); }; const openHandler = () => { @@ -223,7 +222,7 @@ export default defineComponent({ }, ]} > - ; + images?: Array; /** * 当前预览图片所在的下标 */ @@ -84,7 +84,7 @@ export interface TdImageViewerProps { /** * 预览图片切换时触发,`context.prev` 切换到上一张图片,`context.next` 切换到下一张图片 */ - onIndexChange?: (index: number, context: { trigger: 'prev' | 'next' }) => void; + onIndexChange?: (index: number, context: { trigger: 'prev' | 'next' | 'current' }) => void; } export interface ImageScale { @@ -94,8 +94,8 @@ export interface ImageScale { } export interface ImageInfo { - mainImage: string; - thumbnail?: string; + mainImage: string | File; + thumbnail?: string | File; download?: boolean; } diff --git a/src/image-viewer/utils.ts b/src/image-viewer/utils.ts index 8c4bbe0a5..194d3dd05 100644 --- a/src/image-viewer/utils.ts +++ b/src/image-viewer/utils.ts @@ -26,7 +26,7 @@ export const downloadFile = function (imgSrc: string) { image.src = imgSrc; }; -const isImageInfo = (image: string | ImageInfo): image is ImageInfo => typeof image !== 'string'; +const isImageInfo = (image: string | File | ImageInfo): image is ImageInfo => typeof image !== 'string' && !(image instanceof File); export const formatImages = (images: TdImageViewerProps['images']): ImageInfo[] => { if (!Array.isArray(images)) return []; diff --git a/src/image/image.en-US.md b/src/image/image.en-US.md index 392de9324..103c38a91 100644 --- a/src/image/image.en-US.md +++ b/src/image/image.en-US.md @@ -7,17 +7,19 @@ name | type | default | description | required -- | -- | -- | -- | -- alt | String | - | \- | N error | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N -fit | String | fill | options:contain/cover/fill/none/scale-down | N +fallback | String | - | display `fallback` image on `src` loading failed. you can also use `error` to define more complex error content | N +fit | String | fill | options: contain/cover/fill/none/scale-down | N gallery | Boolean | false | \- | N lazy | Boolean | false | \- | N loading | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N overlayContent | String / Slot / Function | - | overlay on the top of image。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N -overlayTrigger | String | always | options:always/hover | N +overlayTrigger | String | always | options: always/hover | N placeholder | String / Slot / Function | - | Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N position | String | center | \- | N -shape | String | square | options:circle/round/square | N -src | String | - | \- | N -srcset | Object | - | for `.avif` and `.webp` image url。Typescript:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N +referrerpolicy | String | - | attribute of ``, [MDN Definition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。options: no-referrer/no-referrer-when-downgrade/origin/origin-when-cross-origin/same-origin/strict-origin/strict-origin-when-cross-origin/unsafe-url | N +shape | String | square | options: circle/round/square | N +src | String / File | - | src attribute of ``. image File can also be loaded。Typescript:`string \| File` | N +srcset | Object | - | for `.avif` and `.webp` image url, load `srcset` before `src`。Typescript:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N onError | Function | | Typescript:`(context: { e: ImageEvent }) => void`
trigger on image load failed | N onLoad | Function | | Typescript:`(context: { e: ImageEvent }) => void`
trigger on image loaded | N diff --git a/src/image/image.md b/src/image/image.md index b399e9ccc..d1ca82c12 100644 --- a/src/image/image.md +++ b/src/image/image.md @@ -7,6 +7,7 @@ -- | -- | -- | -- | -- alt | String | - | 图片描述 | N error | String / Slot / Function | - | 自定义图片加载失败状态下的显示内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N +fallback | String | - | 图片加载失败时,显示当前链接设置的图片地址。如果要使用组件图标或完全自定义加载失败时显示的内容,请更为使用 `error` | N fit | String | fill | 图片填充模式。可选项:contain/cover/fill/none/scale-down | N gallery | Boolean | false | 是否展示为图集样式 | N lazy | Boolean | false | 是否开启图片懒加载 | N @@ -15,9 +16,10 @@ overlayContent | String / Slot / Function | - | 图片上方的浮层内容。TS overlayTrigger | String | always | 浮层 `overlayContent` 出现的时机。可选项:always/hover | N placeholder | String / Slot / Function | - | 占位元素,展示层级低于 `loading` `error` 和图片本身,值类型为字符串时表示占位图片地址。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-vue/blob/develop/src/common.ts) | N position | String | center | 等同于原生的 object-position 属性,可选值为 top right bottom left 或 string,可以自定义任何单位,px 或者 百分比 | N +referrerpolicy | String | - | `` 标签的原生属性,[MDN 定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)。可选项:no-referrer/no-referrer-when-downgrade/origin/origin-when-cross-origin/same-origin/strict-origin/strict-origin-when-cross-origin/unsafe-url | N shape | String | square | 图片圆角类型。可选项:circle/round/square | N -src | String | - | 图片链接 | N -srcset | Object | - | 图片地址,支持特殊格式的图片,如 `.avif` 和 `.webp`。TS 类型:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N +src | String / File | - | 用于显示图片的链接或原始图片文件对象。TS 类型:`string \| File` | N +srcset | Object | - | 图片链接集合,用于支持特殊格式的图片,如 `.avif` 和 `.webp`。会优先加载 `srcset` 中的图片格式,浏览器不支持的情况下,加载 `src` 设置的图片地址。TS 类型:`ImageSrcset` `interface ImageSrcset { 'image/avif': string; 'image/webp': string; }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/image/type.ts) | N onError | Function | | TS 类型:`(context: { e: ImageEvent }) => void`
图片加载失败时触发 | N onLoad | Function | | TS 类型:`(context: { e: ImageEvent }) => void`
图片加载完成时触发 | N diff --git a/src/image/image.tsx b/src/image/image.tsx index bb9afb861..e1e3e66bd 100644 --- a/src/image/image.tsx +++ b/src/image/image.tsx @@ -1,5 +1,5 @@ import { - computed, defineComponent, ref, watch, + computed, defineComponent, ref, toRefs, watch, } from '@vue/composition-api'; import omit from 'lodash/omit'; import isFunction from 'lodash/isFunction'; @@ -10,15 +10,18 @@ import { TdImageProps } from './type'; import props from './props'; import { renderTNodeJSX } from '../utils/render-tnode'; import Space from '../space'; +import { useImagePreviewUrl } from '../hooks'; export default defineComponent({ name: 'TImage', components: { Space }, props, setup(props: TdImageProps, { emit }) { + const { onLoad, onError } = props; + const { - lazy, overlayTrigger, onLoad, onError, - } = props; + src, lazy, fallback, overlayTrigger, + } = toRefs(props); const rest = omit(props, [ 'className', @@ -43,17 +46,27 @@ export default defineComponent({ const imageRef = ref(null); // replace image url - const imageSrc = computed(() => isFunction(globalConfig.value.replaceImageSrc) ? globalConfig.value.replaceImageSrc(props) : props.src); + const imageStrSrc = ref(src.value); watch( - () => props.src, - () => { - hasError.value = false; - isLoaded.value = false; + [src, globalConfig], + ([src, globalConfig]) => { + const { replaceImageSrc } = globalConfig || {}; + const tmpUrl = isFunction(replaceImageSrc) ? replaceImageSrc(props) : src; + if (tmpUrl === src) return; + imageStrSrc.value = tmpUrl; }, + { immediate: true }, ); - const shouldLoad = ref(!lazy); + const { previewUrl } = useImagePreviewUrl(imageStrSrc); + + watch([previewUrl], () => { + hasError.value = false; + isLoaded.value = false; + }); + + const shouldLoad = ref(!lazy.value); const handleLoadImage = () => { shouldLoad.value = true; }; @@ -68,11 +81,15 @@ export default defineComponent({ const hasError = ref(false); const handleError = (e: Event) => { hasError.value = true; + // show fallback url if load failed + if (fallback.value) { + imageStrSrc.value = fallback.value; + } emit('error', { e }); onError?.({ e }); }; - const hasMouseEvent = overlayTrigger === 'hover'; + const hasMouseEvent = overlayTrigger.value === 'hover'; const shouldShowOverlay = ref(!hasMouseEvent); const handleToggleOverlay = () => { @@ -96,7 +113,8 @@ export default defineComponent({ hasMouseEvent, handleToggleOverlay, shouldShowOverlay, - imageSrc, + imageStrSrc, + previewUrl, hasError, shouldLoad, handleError, @@ -145,14 +163,23 @@ export default defineComponent({ {Object.entries(this.srcset).map(([type, url]) => ( ))} - {this.src && this.renderImage(this.src)} + {this.renderImage()} ); }, - renderImage(url: string) { + renderImage() { + // string / File + const url = typeof this.imageStrSrc === 'string' ? this.imageStrSrc : this.previewUrl; return ( - {this.alt} + {this.alt} ); }, }, @@ -171,19 +198,20 @@ export default defineComponent({ onMouseenter={this.handleToggleOverlay} onMouseleave={this.handleToggleOverlay} {...this.rest} + on={this.$listeners} > {this.renderPlaceholder()} {this.renderGalleryShadow()} {(this.hasError || !this.shouldLoad) &&
} {!(this.hasError || !this.shouldLoad) - && (this.srcset && Object.keys(this.srcset).length ? this.renderImageSrcset() : this.renderImage(this.imageSrc))} + && (this.srcset && Object.keys(this.srcset).length ? this.renderImageSrcset() : this.renderImage())} {!(this.hasError || !this.shouldLoad) && !this.isLoaded && (
{renderTNodeJSX(this, 'loading') || ( - {this.globalConfig.loadingText} + {typeof this.loading === 'string' ? this.loading : this.globalConfig.loadingText} )}
@@ -194,7 +222,7 @@ export default defineComponent({ {renderTNodeJSX(this, 'error') || ( - {this.globalConfig.errorText} + {typeof this.error === 'string' ? this.error : this.globalConfig.errorText} )}
diff --git a/src/image/props.ts b/src/image/props.ts index 3e745b897..4ea995c29 100644 --- a/src/image/props.ts +++ b/src/image/props.ts @@ -17,6 +17,11 @@ export default { error: { type: [String, Function] as PropType, }, + /** 图片加载失败时,显示当前链接设置的图片地址。如果要使用组件图标或完全自定义加载失败时显示的内容,请更为使用 `error` */ + fallback: { + type: String, + default: '', + }, /** 图片填充模式 */ fit: { type: String as PropType, @@ -56,6 +61,23 @@ export default { type: String, default: 'center', }, + /** `` 标签的原生属性,[MDN 定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) */ + referrerpolicy: { + type: String as PropType, + validator(val: TdImageProps['referrerpolicy']): boolean { + if (!val) return true; + return [ + 'no-referrer', + 'no-referrer-when-downgrade', + 'origin', + 'origin-when-cross-origin', + 'same-origin', + 'strict-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url', + ].includes(val); + }, + }, /** 图片圆角类型 */ shape: { type: String as PropType, @@ -65,12 +87,11 @@ export default { return ['circle', 'round', 'square'].includes(val); }, }, - /** 图片链接 */ + /** 用于显示图片的链接或原始图片文件对象 */ src: { - type: String, - default: '', + type: [String, File] as PropType, }, - /** 图片地址,支持特殊格式的图片,如 `.avif` 和 `.webp` */ + /** 图片链接集合,用于支持特殊格式的图片,如 `.avif` 和 `.webp`。会优先加载 `srcset` 中的图片格式,浏览器不支持的情况下,加载 `src` 设置的图片地址 */ srcset: { type: Object as PropType, }, diff --git a/src/image/type.ts b/src/image/type.ts index 1ae646f7a..e8fe3af3d 100644 --- a/src/image/type.ts +++ b/src/image/type.ts @@ -16,6 +16,11 @@ export interface TdImageProps { * 自定义图片加载失败状态下的显示内容 */ error?: string | TNode; + /** + * 图片加载失败时,显示当前链接设置的图片地址。如果要使用组件图标或完全自定义加载失败时显示的内容,请更为使用 `error` + * @default '' + */ + fallback?: string; /** * 图片填充模式 * @default fill @@ -53,18 +58,29 @@ export interface TdImageProps { * @default center */ position?: string; + /** + * `` 标签的原生属性,[MDN 定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) + */ + referrerpolicy?: + | 'no-referrer' + | 'no-referrer-when-downgrade' + | 'origin' + | 'origin-when-cross-origin' + | 'same-origin' + | 'strict-origin' + | 'strict-origin-when-cross-origin' + | 'unsafe-url'; /** * 图片圆角类型 * @default square */ shape?: 'circle' | 'round' | 'square'; /** - * 图片链接 - * @default '' + * 用于显示图片的链接或原始图片文件对象 */ - src?: string; + src?: string | File; /** - * 图片地址,支持特殊格式的图片,如 `.avif` 和 `.webp` + * 图片链接集合,用于支持特殊格式的图片,如 `.avif` 和 `.webp`。会优先加载 `srcset` 中的图片格式,浏览器不支持的情况下,加载 `src` 设置的图片地址 */ srcset?: ImageSrcset; /** diff --git a/src/upload/__tests__/__snapshots__/vitest-upload.test.jsx.snap b/src/upload/__tests__/__snapshots__/vitest-upload.test.jsx.snap index 58833bd48..238c9cd5b 100644 --- a/src/upload/__tests__/__snapshots__/vitest-upload.test.jsx.snap +++ b/src/upload/__tests__/__snapshots__/vitest-upload.test.jsx.snap @@ -18,10 +18,55 @@ exports[`Upload Component > props.draggable: theme=image & draggable=true, fail
- + > + +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
props.draggable: theme=image & draggable=true, progr
- + > + +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
props.draggable: theme=image & draggable=true, succe
- + > + +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
props.draggable: theme=image & draggable=true, succe
- + > + +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
props.draggable: theme=image & draggable=true, waiti
- + > + +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
props.theme: theme=file-flow works fine 1`] = ` +
`; @@ -761,10 +987,49 @@ exports[`Upload Component > props.theme: theme=image-flow works fine 1`] = `
- +
+ +
+
+
+ + + + +
+
+
+
@@ -804,16 +1069,73 @@ exports[`Upload Component > props.theme: theme=image-flow works fine 1`] = `
- +
+ +
+
+
+ + + + +
+
+
+
+ + + + props.theme: theme=image-flow works fine 1`] = `
- +
+ +
+
+
+ + + + +
+
+
+
+ + + + props.theme: theme=image-flow works fine 1`] = ` + + + + props.theme: theme=image-flow works fine 1`] = ` + + + + props.theme: theme=image-flow works fine 1`] = `
+
`; diff --git a/src/upload/_example/file-flow-list.vue b/src/upload/_example/file-flow-list.vue index aeebab8ab..98b778eb6 100644 --- a/src/upload/_example/file-flow-list.vue +++ b/src/upload/_example/file-flow-list.vue @@ -3,6 +3,7 @@ 禁用状态 自动上传 + 显示文件缩略图 允许上传同名文件 整体替换上传 多个文件一个请求上传 @@ -17,7 +18,9 @@ theme="file-flow" multiple :disabled="disabled" + :abridge-name="ABRIDGE_NAME" :auto-upload="autoUpload" + :show-thumbnail="showThumbnail" :max="10" :allow-upload-duplicate-file="allowUploadDuplicateFile" :is-batch-upload="isBatchUpload" @@ -42,6 +45,8 @@ export default { allowUploadDuplicateFile: false, isBatchUpload: false, uploadAllFilesInOneRequest: false, + ABRIDGE_NAME: [10, 7], + showThumbnail: false, }; }, diff --git a/src/upload/_example/img-flow-list.vue b/src/upload/_example/img-flow-list.vue index 54f3423d9..9f0e6702a 100644 --- a/src/upload/_example/img-flow-list.vue +++ b/src/upload/_example/img-flow-list.vue @@ -5,19 +5,37 @@

+ + + + + + @@ -29,15 +47,39 @@ export default { return { autoUpload: false, files: [ - // { - // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', - // name: 'loading.svg', - // }, + { + url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', + name: 'loading.svg', + status: 'success', + }, ], }; }, methods: { + requestMethod1() { + return new Promise((resolve) => { + resolve({ + status: 'success', + response: { + url: 'https://tdesign.gtimg.com/site/avatar.jpg', + }, + }); + }); + }, + requestMethod2() { + return new Promise((resolve) => { + resolve({ + status: 'success', + response: { + files: [ + { name: 'avatar1.jpg', url: 'https://tdesign.gtimg.com/site/avatar.jpg' }, + { name: 'avatar2.jpg', url: 'https://avatars.githubusercontent.com/u/11605702?v=4' }, + ], + }, + }); + }); + }, onDragenter(p) { console.log('dragenter', p); }, diff --git a/src/upload/props.ts b/src/upload/props.ts index e9f9e8ff0..71a92c1f1 100644 --- a/src/upload/props.ts +++ b/src/upload/props.ts @@ -74,7 +74,7 @@ export default { format: { type: Function as PropType, }, - /** 用于新增或修改文件上传请求参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`;
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名,也可以使用 `formatRequest` 自定义任意字段 */ + /** 用于新增或修改文件上传请求 参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`。
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名。
也可以使用 `formatRequest` 自定义任意字段,如添加一个字段 `fileList` ,存储文件数组 */ formatRequest: { type: Function as PropType, }, @@ -130,6 +130,8 @@ export default { requestMethod: { type: Function as PropType, }, + /** 是否在文件列表中显示缩略图,`theme=file-flow` 时有效 */ + showThumbnail: Boolean, /** 是否显示上传进度 */ showUploadProgress: { type: Boolean, diff --git a/src/upload/themes/dragger-file.tsx b/src/upload/themes/dragger-file.tsx index 96c4a5344..a51db8a56 100644 --- a/src/upload/themes/dragger-file.tsx +++ b/src/upload/themes/dragger-file.tsx @@ -17,6 +17,7 @@ import useDrag, { UploadDragEvents } from '../hooks/useDrag'; import useGlobalIcon from '../../hooks/useGlobalIcon'; import ImageViewer from '../../image-viewer'; import { renderTNodeJSX } from '../../utils/render-tnode'; +import Image from '../../image'; export interface DraggerProps extends CommonDisplayFileProps { trigger?: TdUploadProps['trigger']; @@ -82,7 +83,7 @@ export default defineComponent({ ( - + )} style={{ maxWidth: '120px', maxHeight: '120px' }} > diff --git a/src/upload/themes/image-card.tsx b/src/upload/themes/image-card.tsx index 2a5c2e281..3d40727d4 100644 --- a/src/upload/themes/image-card.tsx +++ b/src/upload/themes/image-card.tsx @@ -17,6 +17,7 @@ import { TdUploadProps, UploadFile } from '../type'; import { abridgeName } from '../../_common/js/upload/utils'; import { renderTNodeJSX } from '../../utils/render-tnode'; import Link from '../../link'; +import Image from '../../image'; export interface ImageCardUploadProps extends CommonDisplayFileProps { multiple: TdUploadProps['multiple']; @@ -69,7 +70,7 @@ export default defineComponent({ const { BrowseIcon, DeleteIcon } = this.icons; return (
- +
e.stopPropagation()}> void; @@ -29,6 +45,7 @@ export interface ImageFlowListProps extends CommonDisplayFileProps { disabled?: boolean; isBatchUpload?: boolean; draggable?: boolean; + showThumbnail?: boolean; } export default defineComponent({ @@ -42,9 +59,10 @@ export default defineComponent({ disabled: Boolean, isBatchUpload: Boolean, draggable: Boolean, + showThumbnail: Boolean, }, - setup(props: ImageFlowListProps) { + setup(props: ImageFlowListProps, context) { // locale 已经在 useUpload 中统一处理优先级 const { locale, uploading, classPrefix, accept, @@ -61,6 +79,9 @@ export default defineComponent({ const drag = useDrag(props.dragEvents, accept); + const currentPreviewFile = ref([]); + const previewIndex = ref(0); + const uploadText = computed(() => { if (uploading.value) return `${locale.value.progress.uploadingText}`; return locale.value.triggerUploadText.normal; @@ -78,12 +99,41 @@ export default defineComponent({ : {}; }); + const browseIconClick = ({ + e, + index, + file, + viewFiles, + }: { + e: MouseEvent; + index: number; + file: UploadFile; + viewFiles: UploadFile[]; + }) => { + previewIndex.value = index; + currentPreviewFile.value = viewFiles; + context.emit('preview', { file, index, e }); + }; + + const previewIndexChange = (index: number) => { + previewIndex.value = index; + }; + + const closePreview = () => { + currentPreviewFile.value = []; + }; + return { icons, dragActive: drag.dragActive, uploadPrefix, uploadText, innerDragEvents, + currentPreviewFile, + previewIndex, + browseIconClick, + closePreview, + previewIndexChange, }; }, @@ -139,14 +189,18 @@ export default defineComponent({
)} {(['waiting', 'success'].includes(file.status) || (!file.status && file.url)) && ( - + )}
{file.url && ( + { + this.browseIconClick({ + e, index, file, viewFiles: this.displayFiles, + }); + }} + /> t.url)} defaultIndex={index} @@ -252,17 +306,24 @@ export default defineComponent({ ? this.renderBatchActionCol(index) : this.renderNormalActionCol(file, index); const fileName = this.abridgeName?.length ? abridgeName(file.name, ...this.abridgeName) : file.name; + const thumbnailNode = this.showThumbnail ? ( +
+ {this.renderFileThumbnail(file)} + {fileName} +
+ ) : ( + fileName + ); + const fileNameNode = file.url ? ( + + {thumbnailNode} + + ) : ( + thumbnailNode + ); return ( - - {file.url ? ( - - {fileName} - - ) : ( - fileName - )} - + {fileNameNode} {returnFileSize(file.size)} {this.renderStatus(file)} {this.disabled ? null : deleteNode} @@ -288,6 +349,49 @@ export default defineComponent({ ); }, + + getFileThumbnailIcon(fileType: string) { + if (FILE_PDF_REGEXP.test(fileType)) { + return ; + } + if (FILE_EXCEL_REGEXP.test(fileType)) { + return ; + } + if (FILE_WORD_REGEXP.test(fileType)) { + return ; + } + if (FILE_PPT_REGEXP.test(fileType)) { + return ; + } + if (VIDEO_REGEXP.test(fileType)) { + return ; + } + return ; + }, + + renderFileThumbnail(file: UploadFile) { + if (!file || (!file.raw && file.url)) return null; + const fileType = file.raw.type; + const className = `${this.uploadPrefix}__file-thumbnail`; + if (IMAGE_REGEXP.test(fileType)) { + return ( + { + e.preventDefault(); + this.browseIconClick({ + e, index: 0, file, viewFiles: [file], + }); + }} + /> + ); + } + return
{this.getFileThumbnailIcon(fileType)}
; + }, }, render() { @@ -337,6 +441,14 @@ export default defineComponent({ >
)} + + t.url || t.raw)} + visible={!!this.currentPreviewFile.length} + onClose={this.closePreview} + index={this.previewIndex} + onIndexChange={this.previewIndexChange} + >
); }, diff --git a/src/upload/type.ts b/src/upload/type.ts index 50dd310fe..b63258536 100644 --- a/src/upload/type.ts +++ b/src/upload/type.ts @@ -80,7 +80,7 @@ export interface TdUploadProps { */ format?: (file: File) => UploadFile; /** - * 用于新增或修改文件上传请求参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`;
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名,也可以使用 `formatRequest` 自定义任意字段 + * 用于新增或修改文件上传请求 参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`。
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名。
也可以使用 `formatRequest` 自定义任意字段,如添加一个字段 `fileList` ,存储文件数组 */ formatRequest?: (requestData: { [key: string]: any }) => { [key: string]: any }; /** @@ -137,6 +137,11 @@ export interface TdUploadProps { * 自定义上传方法。返回值 `status` 表示上传成功或失败;`error` 或 `response.error` 表示上传失败的原因;
`response` 表示请求上传成功后的返回数据,`response.url` 表示上传成功后的图片/文件地址,`response.files` 表示一个请求上传多个文件/图片后的返回值。
示例一:`{ status: 'fail', error: '上传失败', response }`。
示例二:`{ status: 'success', response: { url: 'https://tdesign.gtimg.com/site/avatar.jpg' } }`。
示例三:`{ status: 'success', files: [{ url: 'https://xxx.png', name: 'xxx.png' }]}` */ requestMethod?: (files: UploadFile | UploadFile[]) => Promise; + /** + * 是否在文件列表中显示缩略图,`theme=file-flow` 时有效 + * @default false + */ + showThumbnail?: boolean; /** * 是否显示上传进度 * @default true diff --git a/src/upload/upload.en-US.md b/src/upload/upload.en-US.md index 900d14963..35cdce4aa 100644 --- a/src/upload/upload.en-US.md +++ b/src/upload/upload.en-US.md @@ -34,6 +34,7 @@ multiple | Boolean | false | multiple files uploading | N name | String | file | field name of files in upload request data | N placeholder | String | - | placeholder | N requestMethod | Function | - | custom upload request method。Typescript:`(files: UploadFile \| UploadFile[]) => Promise` `interface RequestMethodResponse { status: 'success' \| 'fail'; error?: string; response: { url?: string; files?: UploadFile[]; [key: string]: any } }`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N +showThumbnail | Boolean | false | show thumbnail before file name, only works on `theme=file-flow` | N showUploadProgress | Boolean | true | show upload progress nodes | N sizeLimit | Number / Object | - | files size limit。Typescript:`number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[see more ts definition](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N status | String | - | tips status。options: default/success/warning/error | N diff --git a/src/upload/upload.md b/src/upload/upload.md index ba10d3af0..41819d232 100644 --- a/src/upload/upload.md +++ b/src/upload/upload.md @@ -21,7 +21,7 @@ fileListDisplay | Slot / Function | - | 用于完全自定义文件列表界面 files | Array | [] | 已上传文件列表,同 `value`。TS 类型:`UploadFile`。支持语法糖 `.sync`。TS 类型:`Array` | N defaultFiles | Array | [] | 已上传文件列表,同 `value`。TS 类型:`UploadFile`。非受控属性。TS 类型:`Array` | N format | Function | - | 转换文件 `UploadFile` 的数据结构,可新增或修改 `UploadFile` 的属性,注意不能删除 `UploadFile` 属性。`action` 存在时有效。TS 类型:`(file: File) => UploadFile` | N -formatRequest | Function | - | 用于新增或修改文件上传请求参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`;
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名,也可以使用 `formatRequest` 自定义任意字段。TS 类型:`(requestData: { [key: string]: any }) => { [key: string]: any }` | N +formatRequest | Function | - | 用于新增或修改文件上传请求 参数。`action` 存在时有效。一个请求上传一个文件时,默认请求字段有 `file`。
一个请求上传多个文件时,默认字段有 `file[0]/file[1]/file[2]/.../length`,其中 `length` 表示本次上传的文件数量。
⚠️非常注意,此处的 `file[0]/file[1]` 仅仅是一个字段名,并非表示 `file` 是一个数组,接口获取字段时注意区分。
可以使用 `name` 定义 `file` 字段的别名。
也可以使用 `formatRequest` 自定义任意字段,如添加一个字段 `fileList` ,存储文件数组。TS 类型:`(requestData: { [key: string]: any }) => { [key: string]: any }` | N formatResponse | Function | - | 用于格式化文件上传后的接口响应数据,`response` 便是接口响应的原始数据。`action` 存在时有效。
此函数的返回值 `error` 或 `response.error` 会作为错误文本提醒,如果存在会判定为本次上传失败。
此函数的返回值 `url` 或 `response.url` 会作为上传成功后的链接。TS 类型:`(response: any, context: FormatResponseContext) => ResponseType ` `type ResponseType = { error?: string; url?: string } & Record` `interface FormatResponseContext { file: UploadFile; currentFiles?: UploadFile[] }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N headers | Object | - | 设置上传的请求头部,`action` 存在时有效。TS 类型:`{[key: string]: string}` | N inputAttributes | Object | - | 用于添加属性到 HTML 元素 `input`。TS 类型:`CSSProperties` | N @@ -34,6 +34,7 @@ multiple | Boolean | false | 支持多文件上传 | N name | String | file | 文件上传时的名称 | N placeholder | String | - | 占位符 | N requestMethod | Function | - | 自定义上传方法。返回值 `status` 表示上传成功或失败;`error` 或 `response.error` 表示上传失败的原因;
`response` 表示请求上传成功后的返回数据,`response.url` 表示上传成功后的图片/文件地址,`response.files` 表示一个请求上传多个文件/图片后的返回值。
示例一:`{ status: 'fail', error: '上传失败', response }`。
示例二:`{ status: 'success', response: { url: 'https://tdesign.gtimg.com/site/avatar.jpg' } }`。
示例三:`{ status: 'success', files: [{ url: 'https://xxx.png', name: 'xxx.png' }]}`。TS 类型:`(files: UploadFile \| UploadFile[]) => Promise` `interface RequestMethodResponse { status: 'success' \| 'fail'; error?: string; response: { url?: string; files?: UploadFile[]; [key: string]: any } }`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N +showThumbnail | Boolean | false | 是否在文件列表中显示缩略图,`theme=file-flow` 时有效 | N showUploadProgress | Boolean | true | 是否显示上传进度 | N sizeLimit | Number / Object | - | 图片文件大小限制,默认单位 KB。可选单位有:`'B' \| 'KB' \| 'MB' \| 'GB'`。示例一:`1000`。示例二:`{ size: 2, unit: 'MB', message: '图片大小不超过 {sizeLimit} MB' }`。TS 类型:`number \| SizeLimitObj` `interface SizeLimitObj { size: number; unit: SizeUnit ; message?: string }` `type SizeUnitArray = ['B', 'KB', 'MB', 'GB']` `type SizeUnit = SizeUnitArray[number]`。[详细类型定义](https://github.com/Tencent/tdesign-vue/tree/develop/src/upload/type.ts) | N status | String | - | 文件上传提示文本状态。可选项:default/success/warning/error | N diff --git a/src/upload/upload.tsx b/src/upload/upload.tsx index 8ebde3c63..f833fe5dc 100644 --- a/src/upload/upload.tsx +++ b/src/upload/upload.tsx @@ -189,6 +189,7 @@ export default defineComponent({ uploadFiles={this.uploadFiles} cancelUpload={this.cancelUpload} onPreview={this.onInnerPreview} + showThumbnail={this.showThumbnail} scopedSlots={{ fileListDisplay: this.$scopedSlots.fileListDisplay, 'file-list-display': this.$scopedSlots['file-list-display'], diff --git a/test/snap/__snapshots__/csr.test.js.snap b/test/snap/__snapshots__/csr.test.js.snap index b07b9213c..6b0cde67b 100644 --- a/test/snap/__snapshots__/csr.test.js.snap +++ b/test/snap/__snapshots__/csr.test.js.snap @@ -51272,10 +51272,54 @@ exports[`csr snapshot test > csr test ./src/form/_example/disabled.vue 1`] = `
- +
+ +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
@@ -162067,6 +162111,29 @@ exports[`csr snapshot test > csr test ./src/upload/_example/file-flow-list.vue 1
+
+ +
@@ -162234,6 +162301,7 @@ exports[`csr snapshot test > csr test ./src/upload/_example/file-flow-list.vue 1
+
@@ -162475,10 +162543,54 @@ exports[`csr snapshot test > csr test ./src/upload/_example/image.vue 1`] = `
- +
+ +
+
+
+ + + + +
+
+ 图片加载中 +
+
+
+
@@ -162678,7 +162790,7 @@ exports[`csr snapshot test > csr test ./src/upload/_example/img-flow-list.vue 1` - 选择文件 + 继续选择
@@ -162691,11 +162803,135 @@ exports[`csr snapshot test > csr test ./src/upload/_example/img-flow-list.vue 1`
-
- 点击上方“选择文件”或将文件拖拽到此区域 -
+
  • +
    +
    + +
    +
    +
    + + + + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + +
    +
    +

    + loading.svg +

    +
  • +
    csr test ./src/upload/_example/img-flow-list.vue 1`
    +
    diff --git a/test/snap/__snapshots__/ssr.test.js.snap b/test/snap/__snapshots__/ssr.test.js.snap index bc4f00f68..6699b83d3 100644 --- a/test/snap/__snapshots__/ssr.test.js.snap +++ b/test/snap/__snapshots__/ssr.test.js.snap @@ -402,7 +402,7 @@ exports[`ssr snapshot test > renders ./src/form/_example/clear-validate.vue corr exports[`ssr snapshot test > renders ./src/form/_example/custom-validator.vue correctly 1`] = `"
    同一个校验方法可输出不同的错误信息和类型,依次输入:1234 观察变化
    自定义异步校验方法
    "`; -exports[`ssr snapshot test > renders ./src/form/_example/disabled.vue correctly 1`] = `"
    接受
    请选择单张图片文件上传
    "`; +exports[`ssr snapshot test > renders ./src/form/_example/disabled.vue correctly 1`] = `"
    接受
    • \\"\\"
      图片加载中
    请选择单张图片文件上传
    "`; exports[`ssr snapshot test > renders ./src/form/_example/error-message.vue correctly 1`] = `"
    这里可以展示一段说明文字
    一句话介绍自己
    "`; @@ -1253,11 +1253,11 @@ exports[`ssr snapshot test > renders ./src/upload/_example/custom-drag.vue corre exports[`ssr snapshot test > renders ./src/upload/_example/draggable.vue correctly 1`] = `"
    是否自动上传:
    点击上传  /  拖拽到此区域
    默认文件
    文件大小:1.0 KB上传日期:2022-09-25
    "`; -exports[`ssr snapshot test > renders ./src/upload/_example/file-flow-list.vue correctly 1`] = `"

    支持批量上传文件,文件格式不限,最多只能上传 10 份文件
    点击上方“选择文件”或将文件拖拽到此区域
    "`; +exports[`ssr snapshot test > renders ./src/upload/_example/file-flow-list.vue correctly 1`] = `"

    支持批量上传文件,文件格式不限,最多只能上传 10 份文件
    点击上方“选择文件”或将文件拖拽到此区域
    "`; -exports[`ssr snapshot test > renders ./src/upload/_example/image.vue correctly 1`] = `"

    • 点击上传图片

    请选择单张图片文件上传(上传成功状态演示)
    • 点击上传图片

    请选择单张图片文件上传(上传失败状态演示)
    请选择单张图片文件上传(自定义预览图片地址)
    • 点击上传图片

    允许选择多张图片文件上传,最多只能上传 3 张图片
    "`; +exports[`ssr snapshot test > renders ./src/upload/_example/image.vue correctly 1`] = `"

    • 点击上传图片

    请选择单张图片文件上传(上传成功状态演示)
    • 点击上传图片

    请选择单张图片文件上传(上传失败状态演示)
    请选择单张图片文件上传(自定义预览图片地址)
    • 点击上传图片

    允许选择多张图片文件上传,最多只能上传 3 张图片
    "`; -exports[`ssr snapshot test > renders ./src/upload/_example/img-flow-list.vue correctly 1`] = `"
    是否自动上传:

    支持批量上传图片文件
    点击上方“选择文件”或将文件拖拽到此区域
    "`; +exports[`ssr snapshot test > renders ./src/upload/_example/img-flow-list.vue correctly 1`] = `"
    是否自动上传:

    支持批量上传图片文件
    • \\"\\"

      loading.svg

    "`; exports[`ssr snapshot test > renders ./src/upload/_example/request-method.vue correctly 1`] = `"
    自定义上传方法需要返回成功或失败信息
    "`;