diff --git a/packages/components/config/src/defaultConfig.ts b/packages/components/config/src/defaultConfig.ts index b57b5ab38..53c430def 100644 --- a/packages/components/config/src/defaultConfig.ts +++ b/packages/components/config/src/defaultConfig.ts @@ -209,15 +209,14 @@ const upload: UploadConfig = { name: 'file', withCredentials: false, requestMethod: 'post', - strokeColor: '#20CC94', } const uploadList: UploadListConfig = { listType: 'text', icon: { - file: true, - remove: true, - retry: true, + file: 'paper-clip', + remove: 'delete', + retry: 'edit', }, } diff --git a/packages/components/config/src/types.ts b/packages/components/config/src/types.ts index 4e7ab802e..a38e9bf89 100644 --- a/packages/components/config/src/types.ts +++ b/packages/components/config/src/types.ts @@ -21,7 +21,7 @@ import type { MessageType } from '@idux/components/message' import type { ModalType } from '@idux/components/modal' import type { NotificationPlacement, NotificationType } from '@idux/components/notification' import type { PaginationSize } from '@idux/components/pagination' -import type { ProgressFormat, ProgressGradient, ProgressIcons, ProgressSize } from '@idux/components/progress' +import type { ProgressFormat, ProgressIcons, ProgressSize } from '@idux/components/progress' import type { ResultStatus } from '@idux/components/result' import type { SpaceSize } from '@idux/components/space' import type { SpinSize, SpinTipAlignType } from '@idux/components/spin' @@ -196,13 +196,12 @@ export interface UploadConfig { name: string withCredentials: boolean requestMethod: UploadRequestMethod - strokeColor: ProgressGradient | string customRequest?: (option: UploadRequestOption) => { abort: () => void } } export interface UploadListConfig { listType: UploadListType - icon: Partial> + icon: Partial> } // Data Display diff --git a/packages/components/default.less b/packages/components/default.less index 03a5daeab..b6222cfe8 100644 --- a/packages/components/default.less +++ b/packages/components/default.less @@ -15,7 +15,7 @@ @import './breadcrumb/style/themes/default.less'; @import './button/style/themes/default.less'; @import './card/style/themes/default.less'; -@import './carousel/style//themes/default.less'; +@import './carousel/style/themes/default.less'; @import './checkbox/style/themes/default.less'; @import './collapse/style/themes/default.less'; @import './date-picker/style/themes/default.less'; diff --git a/packages/components/types.d.ts b/packages/components/types.d.ts index b1df17dd6..91015c81b 100644 --- a/packages/components/types.d.ts +++ b/packages/components/types.d.ts @@ -69,6 +69,7 @@ import type { TimePickerComponent } from '@idux/components/time-picker' import type { TimelineComponent, TimelineItemComponent } from '@idux/components/timeline' import type { TooltipComponent } from '@idux/components/tooltip' import type { TreeComponent } from '@idux/components/tree' +import type { UploadComponent, UploadListComponent } from '@idux/components/upload' declare module 'vue' { export interface GlobalComponents { @@ -154,6 +155,8 @@ declare module 'vue' { IxTimePicker: TimePickerComponent IxTooltip: TooltipComponent IxTree: TreeComponent + IxUpload: UploadComponent + IxUploadList: UploadListComponent } } diff --git a/packages/components/upload/__tests__/__snapshots__/list.spec.ts.snap b/packages/components/upload/__tests__/__snapshots__/list.spec.ts.snap new file mode 100644 index 000000000..3ea1e16e7 --- /dev/null +++ b/packages/components/upload/__tests__/__snapshots__/list.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Upload list render render work 1`] = `""`; diff --git a/packages/components/upload/__tests__/__snapshots__/upload.spec.ts.snap b/packages/components/upload/__tests__/__snapshots__/upload.spec.ts.snap new file mode 100644 index 000000000..81b5e1a46 --- /dev/null +++ b/packages/components/upload/__tests__/__snapshots__/upload.spec.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Upload render render work 1`] = ` +"
+
+ +
+ +
+ +
+
" +`; diff --git a/packages/components/upload/__tests__/list.spec.ts b/packages/components/upload/__tests__/list.spec.ts index 821e0a523..a00bc048e 100644 --- a/packages/components/upload/__tests__/list.spec.ts +++ b/packages/components/upload/__tests__/list.spec.ts @@ -2,7 +2,7 @@ import type { UploadListProps } from '../src/types' import type { MountingOptions } from '@vue/test-utils' import { flushPromises, mount } from '@vue/test-utils' -import { h } from 'vue' +import { h, ref } from 'vue' import { renderWork } from '@tests' @@ -12,28 +12,46 @@ import UploadFilesListCpm from '../src/List' import { uploadToken } from '../src/token' const uploadListMount = (options?: MountingOptions>) => { - return mount(UploadFilesListCpm, (options ?? {}) as unknown as MountingOptions) + const { global: { provide: { [uploadToken as unknown as string]: provideObj } = {}, ...restGlobal } = {}, ...rest } = + options as MountingOptions + + return mount(UploadFilesListCpm, { + global: { + provide: { + [uploadToken as symbol]: { + props: {}, + files: { value: [] }, + setSelectorVisible: () => {}, + ...provideObj, + }, + }, + ...restGlobal, + }, + ...rest, + }) } describe('Upload list render', () => { - renderWork(UploadFilesListCpm) + renderWork(UploadFilesListCpm, { + global: { provide: { [uploadToken as symbol]: { props: {}, files: { value: [] }, setSelectorVisible: () => {} } } }, + }) test('type work', async () => { const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: [ + global: { + provide: { + [uploadToken as symbol]: { + files: ref([ { uid: 'test1', name: 'idux.svg', thumbUrl: '/icons/logo.svg', }, - ], + ]), }, }, }, - } as MountingOptions>) + }) await flushPromises() expect(wrapper.classes()).toContain('ix-upload-list-text') @@ -49,46 +67,44 @@ describe('Upload list render', () => { test('icon work', async () => { const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: [ + global: { + provide: { + [uploadToken as symbol]: { + files: ref([ { uid: 'test1', name: 'idux.svg', errorTip: 'error', status: 'error', }, - ], + ]), }, }, }, - } as MountingOptions>) + }) await flushPromises() expect(wrapper.find('.ix-icon-paper-clip').exists()).toBeTruthy() expect(wrapper.find('.ix-icon-delete').exists()).toBeTruthy() expect(wrapper.find('.ix-icon-edit').exists()).toBeTruthy() - expect(wrapper.find('.ix-icon-exclamation-circle').exists()).toBeTruthy() - expect(wrapper.find('.ix-icon-download').exists()).toBeFalsy() const wrapperFileSuccess = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: [ + global: { + provide: { + [uploadToken as symbol]: { + files: ref([ { uid: 'test1', name: 'idux.svg', status: 'success', }, - ], + ]), }, }, }, props: { icon: { - download: true, + download: 'download', remove: h(IxIcon, { name: 'close' }), file: 'left', }, @@ -112,16 +128,16 @@ describe('Upload list render', () => { ] const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: defaultFiles, + global: { + provide: { + [uploadToken as symbol]: { + files: ref(defaultFiles), }, }, }, props: { icon: { - download: true, + download: 'download', }, onDownload, }, @@ -143,16 +159,16 @@ describe('Upload list render', () => { ] const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: defaultFiles, + global: { + provide: { + [uploadToken as symbol]: { + files: ref(defaultFiles), }, }, }, props: { icon: { - preview: true, + preview: 'zoom-in', }, onPreview, }, @@ -180,17 +196,17 @@ describe('Upload list render', () => { ] const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: defaultFiles, + global: { + provide: { + [uploadToken as symbol]: { + files: ref(defaultFiles), + upload, }, - upload, }, }, props: { icon: { - retry: true, + retry: 'edit', }, onRetry, }, @@ -215,13 +231,13 @@ describe('Upload list render', () => { ] const wrapper = uploadListMount({ - provide: { - [uploadToken as symbol]: { - props: { - files: defaultFiles, + global: { + provide: { + [uploadToken as symbol]: { + files: ref(defaultFiles), + onUpdateFiles, + abort, }, - onUpdateFiles, - abort, }, }, props: { diff --git a/packages/components/upload/__tests__/upload.spec.ts b/packages/components/upload/__tests__/upload.spec.ts index 5c7837a53..eaa47f0ba 100644 --- a/packages/components/upload/__tests__/upload.spec.ts +++ b/packages/components/upload/__tests__/upload.spec.ts @@ -60,6 +60,21 @@ const uploadMount = (options?: MountingOptions>) => { } describe('Upload render', () => { + let xhrMock: Partial = {} + + beforeEach(() => { + xhrMock = { + abort: jest.fn(), + send: jest.fn(), + open: jest.fn(), + setRequestHeader: jest.fn(), + readyState: 4, + status: 200, + withCredentials: false, + } + jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock as XMLHttpRequest) + }) + renderWork(UploadCpm, { props: { action: '/upload', files: [] } }) test('slots work', async () => { @@ -190,7 +205,7 @@ describe('Upload render', () => { expect(wrapper.findAll('.ix-upload-file').length).toBe(1) }) - test('strokeColor work', async () => { + test('progress work', async () => { const wrapper = uploadMount({ props: { files: [ @@ -207,11 +222,12 @@ describe('Upload render', () => { expect(wrapper.findComponent(IxProgress).props()).toMatchObject(expect.objectContaining({ strokeColor: '#20CC94' })) - await wrapper.setProps({ strokeColor: { '0%': '#108ee9', '100%': '#87d068' } }) + await wrapper.setProps({ progress: { strokeColor: { '0%': '#108ee9', '100%': '#87d068' }, strokeWidth: 3 } }) expect(wrapper.findComponent(IxProgress).props()).toMatchObject( expect.objectContaining({ strokeColor: { '0%': '#108ee9', '100%': '#87d068' }, + strokeWidth: 3, }), ) }) diff --git a/packages/components/upload/demo/Icon.md b/packages/components/upload/demo/Icon.md index 22e30cca3..0fdb3486d 100644 --- a/packages/components/upload/demo/Icon.md +++ b/packages/components/upload/demo/Icon.md @@ -7,18 +7,8 @@ order: 10 ## zh -`IxUploadList`使用`icon`配置对应的图标,每个图标的取值说明如下: - -- 配置`true`展示该icon,采用默认图标 -- 配置`false`则不展示该icon -- 配置`string`使用新的icon -- 支持配置`VNode` +`IxUploadList`使用`icon`配置对应的图标,支持 `string` 和 `VNode` ## en -`IxUploadList` uses `icon` to configure the corresponding icons. The value of each icon is explained as follows: - -- Configure `true` to display the icon and use the default icon -- Configure `false` to not show the icon -- Configure `string` to use the new icon -- Support configuration of `VNode` +`IxUploadList` uses `icon` to configure the corresponding icon, and supports `string` and `VNode` diff --git a/packages/components/upload/demo/Icon.vue b/packages/components/upload/demo/Icon.vue index 3ca6803cc..c526d17c9 100644 --- a/packages/components/upload/demo/Icon.vue +++ b/packages/components/upload/demo/Icon.vue @@ -14,8 +14,9 @@ import { IxIcon } from '@idux/components/icon' const files = ref([]) const icon = ref({ - file: true, + file: 'paper-clip', download: 'thunderbolt', + retry: h(IxIcon, { name: 'reload' }), remove: h(IxIcon, { name: 'close' }), }) diff --git a/packages/components/upload/demo/Operation.vue b/packages/components/upload/demo/Operation.vue index f8efbcb52..1e4b522cc 100644 --- a/packages/components/upload/demo/Operation.vue +++ b/packages/components/upload/demo/Operation.vue @@ -23,11 +23,11 @@ import { ref } from 'vue' import { useMessage } from '@idux/components/message' const icon = ref({ - file: true, - download: true, - retry: true, - remove: true, - preview: true, + file: 'paper-clip', + download: 'download', + retry: 'edit', + remove: 'delete', + preview: 'zoom-in', }) const files: Ref = ref([ { diff --git a/packages/components/upload/demo/StrokeColor.md b/packages/components/upload/demo/StrokeColor.md index fb491c36b..df4b9a91c 100644 --- a/packages/components/upload/demo/StrokeColor.md +++ b/packages/components/upload/demo/StrokeColor.md @@ -7,8 +7,8 @@ order: 11 ## zh -使用`strokeColor`配置进度条色彩,配置同`IxProgress.strokeColor` +使用`progress`配置进度条,配置同 [ProgressProps](/components/progress/zh#ProgressProps) ## en -Use `strokeColor` to configure the color of the progress bar, the configuration is the same as that of `IxProgress.strokeColor`. +Use `progress` to configure the progress bar, the configuration is the same as [ProgressProps](/components/progress/zh#ProgressProps). diff --git a/packages/components/upload/demo/StrokeColor.vue b/packages/components/upload/demo/StrokeColor.vue index ac97ec854..0045d3533 100644 --- a/packages/components/upload/demo/StrokeColor.vue +++ b/packages/components/upload/demo/StrokeColor.vue @@ -2,9 +2,12 @@ Upload diff --git a/packages/components/upload/docs/Index.zh.md b/packages/components/upload/docs/Index.zh.md index a3b7e623a..25a293220 100644 --- a/packages/components/upload/docs/Index.zh.md +++ b/packages/components/upload/docs/Index.zh.md @@ -17,14 +17,14 @@ order: 0 | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | | --- | --- | --- | --- | --- | --- | | `v-model:files` | 文件列表,必选 | `UploadFile[]` | `[]` | - | - | -| `accept` | 允许上传的文件类型,详见[原生input accept](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input/file) | `string` | - | - | - | +| `accept` | 允许上传的文件类型,详见 [原生input accept](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input/file) | `string` | - | - | - | | `action` | 上传文件的地址,必选 | `string \| (file: UploadFile) => Promise` | - | - | - | | `dragable` | 是否启用拖拽上传 | `boolean` | `false` | ✅ | - | | `disabled` | 是否禁用 | `boolean` | `false` | - | 自定义的触发按钮和自定义的文件列表,disabled需单独处理 | | `maxCount` | 限制上传文件的数量。当为 1 时,始终用最新上传的文件代替当前文件 | `number` | - | - | - | | `multiple` | 是否支持多选文件,开启后按住 ctrl 可选择多个文件 | `boolean` | `false` | ✅ | - | | `directory` | 支持上传文件夹([caniuse](https://caniuse.com/#feat=input-file-directory)) | `boolean` | `false` | ✅ | - | -| `strokeColor` | 进度条色彩,同`IxProgress.strokeColor` | `ProgressGradient \| string` | `#20CC94` | ✅ | - | +| `progress` | 进度条配置,见 [ProgressProps](/components/progress/zh#ProgressProps) | `ProgressProps` | - | - | - | | `name` | 发到后台的文件参数名 | `string` | `file` | ✅ | - | | `withCredentials` | 请求是否携带cookie | `boolean` | `false` | ✅ | - | | `customRequest` | 覆盖内置的上传行为,自定义上传实现 | `(option: UploadRequestOption) => { abort: () => void }` | 基于XMLHttpRequest实现 | - | - | @@ -36,14 +36,14 @@ order: 0 | `onBeforeUpload` | 文件上传前的钩子,根据返回结果是否上传
返回`false`阻止上传
返回`Promise`对象`reject`时停止上传
返回`Promise`对象`resolve`时开始上传 | `(file: UploadFile) => boolean \| UploadFile \| Promise` | `() => true` | - | - | | `onRequestChange` | 请求状态改变的钩子 | `(option: UploadRequestChangeParam) => void` | - | - | - | -### IxUploadFiles 上传文件列表展示 +### IxUploadFiles #### UploadFilesProps | 名称 | 说明 | 类型 | 默认值 | 全局配置 | 备注 | | --- | --- | --- | --- | --- | --- | | `type` | 展示的形式 | `text \| image \| imageCard` | `text` | ✅ | - | -| `icon` | 展示的icon | `Record | `{file: true, remove: true, retry: true}` | ✅ | - | +| `icon` | 展示的icon | `Record | `{file: 'paper-clip', remove: 'delete', retry: 'edit'}` | ✅ | - | | `onDownload` | 点击下载文件时的回调 | `(file: UploadFile) => void` | - | - | - | | `onPreview` | 点击文件链接或预览图标时的回调 | `(file: UploadFile) => boolean \| Promise` | - | - | - | | `onRemove` | 点击移除文件时的回调,返回boolean表示是否允许移除,支持Promise | `(file: UploadFile) => boolean \| Promise` | `() => true` | - | - | diff --git a/packages/components/upload/index.ts b/packages/components/upload/index.ts index 4908ced39..25f21be56 100644 --- a/packages/components/upload/index.ts +++ b/packages/components/upload/index.ts @@ -27,7 +27,9 @@ export type { UploadRequestChangeOption, UploadIconType, UploadInstance, + UploadComponent, UploadPublicProps as UploadProps, UploadListInstance, + UploadListComponent, UploadListPublicProps as UploadListProps, } from './src/types' diff --git a/packages/components/upload/src/List.tsx b/packages/components/upload/src/List.tsx index 324901923..8be46f4eb 100644 --- a/packages/components/upload/src/List.tsx +++ b/packages/components/upload/src/List.tsx @@ -16,7 +16,7 @@ import IxUploadImageCardList from './component/ImageCardList' import IxUploadImageList from './component/ImageList' import IxUploadTextList from './component/TextList' import { useSelectorVisible } from './composables/useDisplay' -import { UploadToken, uploadToken } from './token' +import { uploadToken } from './token' import { uploadListProps } from './types' const cpmMap = { @@ -31,10 +31,7 @@ export default defineComponent({ setup(props) { const config = useGlobalConfig('uploadList') const listType = useListType(props, config) - const { props: uploadProps, setSelectorVisible } = inject(uploadToken, { - props: {}, - setSelectorVisible: () => {}, - } as unknown as UploadToken) + const { props: uploadProps, setSelectorVisible } = inject(uploadToken)! const [outerSelector] = useSelectorVisible(uploadProps, listType) watchEffect(() => setSelectorVisible(outerSelector.value)) diff --git a/packages/components/upload/src/Upload.tsx b/packages/components/upload/src/Upload.tsx index 857dc56a2..0b9d14b53 100644 --- a/packages/components/upload/src/Upload.tsx +++ b/packages/components/upload/src/Upload.tsx @@ -23,10 +23,11 @@ export default defineComponent({ setup(props, { slots }) { const cpmClasses = useCmpClasses() const [showSelector, setSelectorVisible] = useShowSelector() - const [, onUpdateFiles] = useControlledProp(props, 'files', []) - const { fileUploading, abort, startUpload, upload } = useRequest(props) + const [files, onUpdateFiles] = useControlledProp(props, 'files', []) + const { fileUploading, abort, startUpload, upload } = useRequest(props, files) provide(uploadToken, { props, + files, fileUploading, onUpdateFiles, abort, diff --git a/packages/components/upload/src/component/ImageCardList.tsx b/packages/components/upload/src/component/ImageCardList.tsx index a4b56265c..690cf11c1 100644 --- a/packages/components/upload/src/component/ImageCardList.tsx +++ b/packages/components/upload/src/component/ImageCardList.tsx @@ -5,9 +5,7 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { StrokeColorType } from '../composables/useDisplay' import type { FileOperation } from '../composables/useOperation' -import type { UploadToken } from '../token' import type { UploadFile, UploadFileStatus, UploadProps } from '../types' import type { IconsMap } from '../util/icon' import type { Locale } from '@idux/components/i18n' @@ -26,13 +24,11 @@ import { useIcon, useListClasses, useSelectorVisible, - useStrokeColor, useThumb, } from '../composables/useDisplay' import { useOperation } from '../composables/useOperation' import { uploadToken } from '../token' import { uploadListProps } from '../types' -// import { getThumbNode } from '../util/file' import { renderOprIcon } from '../util/icon' import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../util/visible' import FileSelector from './Selector' @@ -41,23 +37,11 @@ export default defineComponent({ name: 'IxUploadImageCardList', props: uploadListProps, setup(listProps) { - const { - props: uploadProps, - upload, - abort, - onUpdateFiles, - } = inject(uploadToken, { - props: { files: [] }, - upload: () => {}, - abort: () => {}, - onUpdateFiles: () => {}, - } as unknown as UploadToken) + const { props: uploadProps, files, upload, abort, onUpdateFiles } = inject(uploadToken)! const icons = useIcon(listProps) const cpmClasses = useCmpClasses() const listClasses = useListClasses(uploadProps, 'imageCard') - const files = computed(() => uploadProps.files) const locale = getLocale('upload') - const strokeColor = useStrokeColor(uploadProps) const [, imageCardVisible] = useSelectorVisible(uploadProps, 'imageCard') const showSelector = useShowSelector(uploadProps, files, imageCardVisible) const { getThumbNode, revokeAll } = useThumb() @@ -69,23 +53,23 @@ export default defineComponent({ return () => (
    {showSelector.value && selectorNode} - {files.value.map(file => renderItem(file, icons, cpmClasses, fileOperation, strokeColor, locale, getThumbNode))} + {files.value.map(file => renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale, getThumbNode))}
) }, }) function renderItem( + uploadProps: UploadProps, file: UploadFile, icons: ComputedRef, cpmClasses: ComputedRef, fileOperation: FileOperation, - strokeColor: ComputedRef, locale: ComputedRef, getThumbNode: UseThumb['getThumbNode'], ) { const fileClasses = normalizeClass([`${cpmClasses.value}-file`, `${cpmClasses.value}-file-${file.status}`]) - const uploadStatusNode = renderUploadStatus(file, locale, cpmClasses) + const uploadStatusNode = renderUploadStatus(uploadProps, file, locale, cpmClasses) const thumbNode = getThumbNode(file) const { retryNode, downloadNode, removeNode, previewNode } = renderOprIcon( file, @@ -109,7 +93,12 @@ function renderItem( ) } -function renderUploadStatus(file: UploadFile, locale: ComputedRef, cpmClasses: ComputedRef) { +function renderUploadStatus( + uploadProps: UploadProps, + file: UploadFile, + locale: ComputedRef, + cpmClasses: ComputedRef, +) { const statusTitle = { error: locale.value.error, uploading: locale.value.uploading, @@ -125,6 +114,7 @@ function renderUploadStatus(file: UploadFile, locale: ComputedRef )} diff --git a/packages/components/upload/src/component/ImageList.tsx b/packages/components/upload/src/component/ImageList.tsx index 137a1fc88..d82f9f2fc 100644 --- a/packages/components/upload/src/component/ImageList.tsx +++ b/packages/components/upload/src/component/ImageList.tsx @@ -5,25 +5,23 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { StrokeColorType, UseThumb } from '../composables/useDisplay' +import type { UseThumb } from '../composables/useDisplay' import type { FileOperation } from '../composables/useOperation' -import type { UploadToken } from '../token' -import type { UploadFile } from '../types' +import type { UploadFile, UploadProps } from '../types' import type { IconsMap } from '../util/icon' import type { Locale } from '@idux/components/i18n' import type { ComputedRef } from 'vue' -import { computed, defineComponent, inject, normalizeClass, onBeforeUnmount } from 'vue' +import { defineComponent, inject, normalizeClass, onBeforeUnmount } from 'vue' import { getLocale } from '@idux/components/i18n' import { IxProgress } from '@idux/components/progress' import { IxTooltip } from '@idux/components/tooltip' -import { useCmpClasses, useIcon, useListClasses, useStrokeColor, useThumb } from '../composables/useDisplay' +import { useCmpClasses, useIcon, useListClasses, useThumb } from '../composables/useDisplay' import { useOperation } from '../composables/useOperation' import { uploadToken } from '../token' import { uploadListProps } from '../types' -// import { getThumbNode } from '../util/file' import { renderIcon, renderOprIcon } from '../util/icon' import { showDownload, showErrorTip, showPreview, showProgress, showRetry } from '../util/visible' @@ -31,33 +29,21 @@ export default defineComponent({ name: 'IxUploadImageList', props: uploadListProps, setup(listProps) { - const { - props: uploadProps, - upload, - abort, - onUpdateFiles, - } = inject(uploadToken, { - props: { files: [] }, - upload: () => {}, - abort: () => {}, - onUpdateFiles: () => {}, - } as unknown as UploadToken) + const { props: uploadProps, files, upload, abort, onUpdateFiles } = inject(uploadToken)! const icons = useIcon(listProps) const cpmClasses = useCmpClasses() const listClasses = useListClasses(uploadProps, 'image') - const files = computed(() => uploadProps.files) const locale = getLocale('upload') - const strokeColor = useStrokeColor(uploadProps) const { getThumbNode, revokeAll } = useThumb() const fileOperation = useOperation(files, listProps, uploadProps, { abort, upload, onUpdateFiles }) onBeforeUnmount(revokeAll) return () => - uploadProps.files.length > 0 && ( + files.value.length > 0 && (
    - {uploadProps.files.map(file => - renderItem(file, icons, cpmClasses, fileOperation, strokeColor, locale, getThumbNode), + {files.value.map(file => + renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale, getThumbNode), )}
) @@ -65,11 +51,11 @@ export default defineComponent({ }) function renderItem( + uploadProps: UploadProps, file: UploadFile, icons: ComputedRef, cpmClasses: ComputedRef, fileOperation: FileOperation, - strokeColor: ComputedRef, locale: ComputedRef, getThumbNode: UseThumb['getThumbNode'], ) { @@ -99,9 +85,10 @@ function renderItem( )} diff --git a/packages/components/upload/src/component/Selector.tsx b/packages/components/upload/src/component/Selector.tsx index b36916892..5d08a04ab 100644 --- a/packages/components/upload/src/component/Selector.tsx +++ b/packages/components/upload/src/component/Selector.tsx @@ -7,7 +7,7 @@ import type { UploadDrag } from '../composables/useDrag' import type { UploadToken } from '../token' -import type { UploadFileStatus, UploadProps } from '../types' +import type { UploadFile, UploadFileStatus, UploadProps } from '../types' import type { UploadConfig } from '@idux/components/config' import type { ComputedRef, Ref, ShallowRef } from 'vue' @@ -24,15 +24,7 @@ import { getFileInfo, getFilesAcceptAllow, getFilesCountAllow } from '../util/fi export default defineComponent({ name: 'IxUploadSelector', setup(props, { slots }) { - const { - props: uploadProps, - onUpdateFiles, - startUpload, - } = inject(uploadToken, { - props: {}, - onUpdateFiles: () => {}, - startUpload: () => {}, - } as unknown as UploadToken) + const { props: uploadProps, files, onUpdateFiles, abort, startUpload } = inject(uploadToken)! const cpmClasses = useCmpClasses() const config = useGlobalConfig('upload') const dir = useDir(uploadProps, config) @@ -49,12 +41,12 @@ export default defineComponent({ onDragLeave, } = useDrag(uploadProps) const [filesSelected, updateFilesSelected] = useFilesSelected(dragFilesSelected, allowDrag) - const filesReady = useFilesAllowed(uploadProps, filesSelected, accept, maxCount) + const filesReady = useFilesAllowed(files, filesSelected, accept, maxCount) const fileInputRef: Ref = ref(null) const inputClasses = computed(() => `${cpmClasses.value}-input`) const selectorClasses = useSelectorClasses(uploadProps, cpmClasses, dragable, dragOver) - syncUploadHandle(uploadProps, filesReady, onUpdateFiles, startUpload) + syncUploadHandle(uploadProps, files, filesReady, onUpdateFiles, abort, startUpload) return () => { return ( @@ -140,16 +132,16 @@ function useFilesSelected( } function useFilesAllowed( - uploadProps: UploadProps, + files: ComputedRef, filesSelected: ShallowRef, accept: ComputedRef, maxCount: ComputedRef, ) { const filesAllowed: ShallowRef = shallowRef([]) - watch(filesSelected, files => { - const filesCheckAccept = getFilesAcceptAllow(files, accept.value) - filesAllowed.value = getFilesCountAllow(filesCheckAccept, uploadProps.files.length, maxCount.value) + watch(filesSelected, filesSelected$$ => { + const filesCheckAccept = getFilesAcceptAllow(filesSelected$$, accept.value) + filesAllowed.value = getFilesCountAllow(filesCheckAccept, files.value.length, maxCount.value) }) return filesAllowed @@ -158,27 +150,29 @@ function useFilesAllowed( // 选中文件变化就处理上传 function syncUploadHandle( uploadProps: UploadProps, + files: ComputedRef, filesReady: ShallowRef, onUpdateFiles: UploadToken['onUpdateFiles'], + abort: UploadToken['abort'], startUpload: UploadToken['startUpload'], ) { - watch(filesReady, async files => { - if (files.length === 0) { + watch(filesReady, async filesReady$$ => { + if (filesReady$$.length === 0) { return } - const filesAfterHandle = uploadProps.onSelect ? await callEmit(uploadProps.onSelect, files) : files - const filesReadyUpload = getFilesHandled(filesAfterHandle!, files) + const filesAfterHandle = uploadProps.onSelect ? await callEmit(uploadProps.onSelect, filesReady$$) : filesReady$$ + const filesReadyUpload = getFilesHandled(filesAfterHandle!, filesReady$$) const filesFormat = getFormatFiles(filesReadyUpload, uploadProps, 'selected') const filesIds = filesFormat.map(file => file.uid) if (uploadProps.maxCount === 1) { + files.value.forEach(file => abort(file)) callEmit(onUpdateFiles, filesFormat) } else { - callEmit(onUpdateFiles, uploadProps.files.concat(filesFormat)) + callEmit(onUpdateFiles, files.value.concat(filesFormat)) } - // 需要用props的files,已做了响应式处理,对status属性改变能够触发更新 await nextTick(() => { - uploadProps.files + files.value .filter(item => filesIds.includes(item.uid)) .forEach(file => { startUpload(file) diff --git a/packages/components/upload/src/component/TextList.tsx b/packages/components/upload/src/component/TextList.tsx index 1491f9f3e..dce42dc8c 100644 --- a/packages/components/upload/src/component/TextList.tsx +++ b/packages/components/upload/src/component/TextList.tsx @@ -5,21 +5,19 @@ * found in the LICENSE file at https://github.com/IDuxFE/idux/blob/main/LICENSE */ -import type { StrokeColorType } from '../composables/useDisplay' import type { FileOperation } from '../composables/useOperation' -import type { UploadToken } from '../token' -import type { UploadFile } from '../types' +import type { UploadFile, UploadProps } from '../types' import type { IconsMap } from '../util/icon' import type { Locale } from '@idux/components/i18n' import type { ComputedRef } from 'vue' -import { computed, defineComponent, inject, normalizeClass } from 'vue' +import { defineComponent, inject, normalizeClass } from 'vue' import { getLocale } from '@idux/components/i18n' import { IxProgress } from '@idux/components/progress' import { IxTooltip } from '@idux/components/tooltip' -import { useCmpClasses, useIcon, useListClasses, useStrokeColor } from '../composables/useDisplay' +import { useCmpClasses, useIcon, useListClasses } from '../composables/useDisplay' import { useOperation } from '../composables/useOperation' import { uploadToken } from '../token' import { uploadListProps } from '../types' @@ -30,40 +28,28 @@ export default defineComponent({ name: 'IxUploadTextList', props: uploadListProps, setup(listProps) { - const { - props: uploadProps, - upload, - abort, - onUpdateFiles, - } = inject(uploadToken, { - props: { files: [] }, - upload: () => {}, - abort: () => {}, - onUpdateFiles: () => {}, - } as unknown as UploadToken) + const { props: uploadProps, files, upload, abort, onUpdateFiles } = inject(uploadToken)! const icons = useIcon(listProps) const cpmClasses = useCmpClasses() const listClasses = useListClasses(uploadProps, 'text') - const files = computed(() => uploadProps.files) const locale = getLocale('upload') - const strokeColor = useStrokeColor(uploadProps) const fileOperation = useOperation(files, listProps, uploadProps, { abort, upload, onUpdateFiles }) return () => files.value.length > 0 && (
    - {files.value.map(file => renderItem(file, icons, cpmClasses, fileOperation, strokeColor, locale))} + {files.value.map(file => renderItem(uploadProps, file, icons, cpmClasses, fileOperation, locale))}
) }, }) function renderItem( + uploadProps: UploadProps, file: UploadFile, icons: ComputedRef, cpmClasses: ComputedRef, fileOperation: FileOperation, - strokeColor: ComputedRef, locale: ComputedRef, ) { const fileClasses = normalizeClass([`${cpmClasses.value}-file`, `${cpmClasses.value}-file-${file.status}`]) @@ -92,9 +78,10 @@ function renderItem( )} diff --git a/packages/components/upload/src/composables/useDisplay.ts b/packages/components/upload/src/composables/useDisplay.ts index 021ab5a12..0d36484ec 100644 --- a/packages/components/upload/src/composables/useDisplay.ts +++ b/packages/components/upload/src/composables/useDisplay.ts @@ -16,7 +16,6 @@ import { useGlobalConfig } from '@idux/components/config' import { isImage } from '@idux/components/upload/src/util/file' import { UploadFile } from '../types' -import { getIcons } from '../util/icon' export type StrokeColorType = ProgressGradient | string @@ -38,12 +37,7 @@ export function useListClasses(props: UploadProps, type: UploadListType): Comput export function useIcon(props: UploadListProps): ComputedRef { const uploadListConfig = useGlobalConfig('uploadList') - return computed(() => getIcons(props.icon ?? uploadListConfig.icon)) -} - -export function useStrokeColor(props: UploadProps): ComputedRef { - const uploadConfig = useGlobalConfig('upload') - return computed(() => props.strokeColor ?? uploadConfig.strokeColor) + return computed(() => props.icon ?? uploadListConfig.icon) } export function useSelectorVisible( diff --git a/packages/components/upload/src/composables/useRequest.ts b/packages/components/upload/src/composables/useRequest.ts index 1c8207d8f..8cebdf19b 100644 --- a/packages/components/upload/src/composables/useRequest.ts +++ b/packages/components/upload/src/composables/useRequest.ts @@ -7,7 +7,7 @@ import type { UploadFile, UploadProgressEvent, UploadProps, UploadRequestOption } from '../types' import type { VKey } from '@idux/cdk/utils' -import type { Ref } from 'vue' +import type { ComputedRef, Ref } from 'vue' import { ref } from 'vue' @@ -26,13 +26,13 @@ export interface UploadRequest { upload: (file: UploadFile) => void } -export function useRequest(props: UploadProps): UploadRequest { +export function useRequest(props: UploadProps, files: ComputedRef): UploadRequest { const fileUploading: Ref = ref([]) const aborts = new Map void>([]) const config = useGlobalConfig('upload') function abort(file: UploadFile): void { - const curFile = getTargetFile(file, props.files) + const curFile = getTargetFile(file, files.value) if (!curFile) { return } @@ -95,19 +95,19 @@ export function useRequest(props: UploadProps): UploadRequest { } as UploadRequestOption const uploadHttpRequest = props.customRequest ?? config.customRequest ?? defaultUpload - setFileStatus(file, 'uploading', props.onFileStatusChange) props.onRequestChange && callEmit(props.onRequestChange, { status: 'loadstart', file: { ...file }, }) + setFileStatus(file, 'uploading', props.onFileStatusChange) file.percent = 0 aborts.set(file.uid, uploadHttpRequest(requestOption)?.abort ?? (() => {})) fileUploading.value.push(file) } function _onProgress(e: UploadProgressEvent, file: UploadFile): void { - const curFile = getTargetFile(file, props.files) + const curFile = getTargetFile(file, files.value) if (!curFile) { return } @@ -121,22 +121,22 @@ export function useRequest(props: UploadProps): UploadRequest { } function _onError(error: Error, file: UploadFile): void { - const curFile = getTargetFile(file, props.files) + const curFile = getTargetFile(file, files.value) if (!curFile) { return } fileUploading.value.splice(getTargetFileIndex(curFile, fileUploading.value), 1) - setFileStatus(curFile, 'error', props.onFileStatusChange) - curFile.error = error props.onRequestChange && callEmit(props.onRequestChange, { file: { ...curFile }, status: 'error', }) + setFileStatus(curFile, 'error', props.onFileStatusChange) + curFile.error = error } function _onSuccess(res: Response, file: UploadFile): void { - const curFile = getTargetFile(file, props.files) + const curFile = getTargetFile(file, files.value) if (!curFile) { return } diff --git a/packages/components/upload/src/token.ts b/packages/components/upload/src/token.ts index 2b7af79f8..87f9ec785 100644 --- a/packages/components/upload/src/token.ts +++ b/packages/components/upload/src/token.ts @@ -7,10 +7,11 @@ import type { UploadRequest } from './composables/useRequest' import type { UploadFile, UploadProps } from './types' -import type { InjectionKey } from 'vue' +import type { ComputedRef, InjectionKey } from 'vue' export type UploadToken = { props: UploadProps + files: ComputedRef onUpdateFiles: (file: UploadFile[]) => void setSelectorVisible: (isShow: boolean) => void } & UploadRequest diff --git a/packages/components/upload/src/types.ts b/packages/components/upload/src/types.ts index 05a68b819..a36838d7d 100644 --- a/packages/components/upload/src/types.ts +++ b/packages/components/upload/src/types.ts @@ -6,7 +6,7 @@ */ import type { IxInnerPropTypes, IxPublicPropTypes, VKey } from '@idux/cdk/utils' -import type { ProgressGradient } from '@idux/components/progress' +import type { ProgressProps } from '@idux/components/progress' import type { DefineComponent, HTMLAttributes, VNode } from 'vue' import { IxPropTypes } from '@idux/cdk/utils' @@ -68,7 +68,7 @@ export const uploadProps = { disabled: IxPropTypes.bool, maxCount: IxPropTypes.number, multiple: IxPropTypes.bool, - strokeColor: IxPropTypes.oneOfType([String, IxPropTypes.object()]), + progress: IxPropTypes.object(), name: IxPropTypes.string, customRequest: IxPropTypes.func<(option: UploadRequestOption) => { abort: () => void }>(), withCredentials: IxPropTypes.bool, @@ -91,7 +91,7 @@ export type UploadInstance = InstanceType> export const uploadListProps = { type: IxPropTypes.oneOf(['text', 'image', 'imageCard']), - icon: IxPropTypes.object>>(), + icon: IxPropTypes.object>>(), onDownload: IxPropTypes.emit<(file: UploadFile) => void>(), onPreview: IxPropTypes.emit<(file: UploadFile) => void>(), onRemove: IxPropTypes.emit<(file: UploadFile) => boolean | Promise>(), diff --git a/packages/components/upload/src/util/icon.ts b/packages/components/upload/src/util/icon.ts index 1a01c895c..6375ef77a 100644 --- a/packages/components/upload/src/util/icon.ts +++ b/packages/components/upload/src/util/icon.ts @@ -16,23 +16,15 @@ import { isString } from 'lodash-es' import { IxIcon } from '@idux/components/icon' -const iconMap = { - file: 'paper-clip', - preview: 'zoom-in', - download: 'download', - remove: 'delete', - retry: 'edit', -} as const - -type IconNodeType = string | boolean | VNode +type IconNodeType = string | VNode type Opr = 'previewNode' | 'retryNode' | 'downloadNode' | 'removeNode' -export type IconsMap = Partial>> +export type IconsMap = Partial> export type OprIcons = Record export function getIconNode(icon: Exclude): VNode | null { - if (icon === false) { + if (!icon) { return null } if (isString(icon)) { @@ -41,20 +33,6 @@ export function getIconNode(icon: Exclude): VNode | null { return icon } -export function getIcons(iconProp: Partial>): IconsMap { - const iconFormat = {} as IconsMap - let icon: UploadIconType - for (icon in iconProp) { - // 默认值 - if (iconProp[icon] === true) { - iconFormat[icon] = iconMap[icon] - } else { - iconFormat[icon] = iconProp[icon] as Exclude - } - } - return iconFormat -} - export function renderIcon(icon: IconsMap[keyof IconsMap] | undefined, props?: Record): VNode | null { if (!icon) { return null diff --git a/packages/components/upload/src/util/request.ts b/packages/components/upload/src/util/request.ts index 7e7dccff4..ca9f4c274 100644 --- a/packages/components/upload/src/util/request.ts +++ b/packages/components/upload/src/util/request.ts @@ -55,11 +55,8 @@ export default function upload(option: UploadRequestOption): UploadReturnType { if (option.requestData) { Object.keys(option.requestData).forEach(key => { const value = option.requestData![key] - // support key-value array data if (Array.isArray(value)) { value.forEach(item => { - // { list: [ 11, 22 ] } - // formData.append('list[]', 11); formData.append(`${key}[]`, item) }) return @@ -76,7 +73,6 @@ export default function upload(option: UploadRequestOption): UploadReturnType { } xhr.onload = function onload() { - // allow success when 2xx status if (xhr.status < 200 || xhr.status >= 300) { return option.onError?.(getError(option, xhr), getBody(xhr)) } diff --git a/packages/components/upload/style/themes/default.less b/packages/components/upload/style/themes/default.less index 9a6507b70..a3d8a9472 100644 --- a/packages/components/upload/style/themes/default.less +++ b/packages/components/upload/style/themes/default.less @@ -1,34 +1,2 @@ -@import '../../../style/themes/default.less'; @import '../index.less'; - -@upload-list-margin: 8px 0 0; -@upload-border-radius: 2px; - -@upload-selector-drag-border: 1px dashed @color-graphite-l20; -@upload-selector-dragover-border: 1px dashed @color-blue; - -@upload-list-text-border: 1px solid @color-graphite-l30; -@upload-list-name-max-width: calc(100% - 74px); - -@upload-list-image-thumb-width: 48px; -@upload-list-image-thumb-height: 48px; -@upload-list-image-thumb-margin: 0 8px 0 0; -@upload-list-image-margin: 8px 0 0; - -@upload-list-image-card-height: 96px; -@upload-list-image-card-width: 96px; -@upload-list-image-card-margin: 0 8px 8px 0; -@upload-list-image-card-bg-color: @color-black; -@upload-list-image-card-icon-wrap-width: 100%; -@upload-list-image-card-selector-font-size: 24px; -@upload-list-image-card-selector-color-hover: #458FFF; -@upload-list-image-card-status-min-width: 60px; -@upload-list-image-card-status-progress-margin: 8px 0 0; - -@upload-file-border-bottom: 1px solid @color-graphite-l30; - -@upload-icon-wrap-max-width: 120px; -@upload-icon-margin: 0 0 0 16px; -@upload-icon-file-margin: 0 8px 0 0; - -@upload-tip-margin: 8px 0 0; +@import "./default.variable.less"; diff --git a/packages/components/upload/style/themes/default.variable.less b/packages/components/upload/style/themes/default.variable.less new file mode 100644 index 000000000..af8a75eb5 --- /dev/null +++ b/packages/components/upload/style/themes/default.variable.less @@ -0,0 +1,33 @@ +@import '../../../style/themes/default.less'; + +@upload-list-margin: 8px 0 0; +@upload-border-radius: 2px; + +@upload-selector-drag-border: 1px dashed @color-graphite-l20; +@upload-selector-dragover-border: 1px dashed @color-blue; + +@upload-list-text-border: 1px solid @color-graphite-l30; +@upload-list-name-max-width: calc(100% - 74px); + +@upload-list-image-thumb-width: 48px; +@upload-list-image-thumb-height: 48px; +@upload-list-image-thumb-margin: 0 8px 0 0; +@upload-list-image-margin: 8px 0 0; + +@upload-list-image-card-height: 96px; +@upload-list-image-card-width: 96px; +@upload-list-image-card-margin: 0 8px 8px 0; +@upload-list-image-card-bg-color: @color-black; +@upload-list-image-card-icon-wrap-width: 100%; +@upload-list-image-card-selector-font-size: 24px; +@upload-list-image-card-selector-color-hover: #458FFF; +@upload-list-image-card-status-min-width: 60px; +@upload-list-image-card-status-progress-margin: 8px 0 0; + +@upload-file-border-bottom: 1px solid @color-graphite-l30; + +@upload-icon-wrap-max-width: 120px; +@upload-icon-margin: 0 0 0 16px; +@upload-icon-file-margin: 0 8px 0 0; + +@upload-tip-margin: 8px 0 0;