diff --git a/packages/varlet-cli/lib/node/bin.js b/packages/varlet-cli/lib/node/bin.js old mode 100755 new mode 100644 diff --git a/packages/varlet-shared/src/index.ts b/packages/varlet-shared/src/index.ts index ba4e40a7d41..4d9cba10318 100644 --- a/packages/varlet-shared/src/index.ts +++ b/packages/varlet-shared/src/index.ts @@ -194,3 +194,14 @@ export const inViewport = (element: HTMLElement) => { return xInViewport && yInViewport } + +export const toDataURL = (file: File): Promise => + new Promise((resolve) => { + const fileReader = new FileReader() + + fileReader.onload = () => { + resolve(fileReader.result as string) + } + + fileReader.readAsDataURL(file) + }) diff --git a/packages/varlet-ui/src/uploader/Uploader.vue b/packages/varlet-ui/src/uploader/Uploader.vue index 912ed477188..b73633167c9 100644 --- a/packages/varlet-ui/src/uploader/Uploader.vue +++ b/packages/varlet-ui/src/uploader/Uploader.vue @@ -92,8 +92,8 @@ import ImagePreview from '../image-preview' import Ripple from '../ripple' import Hover from '../hover' import { defineComponent, nextTick, reactive, computed, watch, ref } from 'vue' -import { props, type VarFile, type ValidateTrigger } from './props' -import { isNumber, toNumber, isString, normalizeToArray } from '@varlet/shared' +import { props, type VarFile, type UploaderValidateTrigger } from './props' +import { isNumber, toNumber, isString, normalizeToArray, toDataURL } from '@varlet/shared' import { isHTMLSupportImage, isHTMLSupportVideo } from '../utils/shared' import { call, useValidation, createNamespace, formatElevation } from '../utils/components' import { useForm } from '../form/provide' @@ -217,27 +217,17 @@ export default defineComponent({ return Array.from(fileList as ArrayLike) } - function resolver(varFile: VarFile): Promise { - return new Promise((resolve) => { - // For performance, only file reader processing is performed on images - if (!varFile.file!.type.startsWith('image')) { - resolve(varFile) - return - } - - const fileReader = new FileReader() - - fileReader.onload = () => { - const base64 = fileReader.result as string - - varFile.cover = base64 - varFile.url = base64 - - resolve(varFile) - } + async function resolver(varFile: VarFile): Promise { + if ( + props.resolveType === 'data-url' || + (varFile.file!.type.startsWith('image') && props.resolveType === 'default') + ) { + const dataURL = await toDataURL(varFile.file!) + varFile.cover = dataURL + varFile.url = dataURL + } - fileReader.readAsDataURL(varFile.file as File) - }) + return varFile } function getResolvers(varFiles: VarFile[]) { @@ -370,7 +360,7 @@ export default defineComponent({ ImagePreview.close() } - function validateWithTrigger(trigger: ValidateTrigger) { + function validateWithTrigger(trigger: UploaderValidateTrigger) { nextTick(() => { const { validateTrigger, rules, modelValue } = props vt(validateTrigger, trigger, rules, modelValue, varFileUtils) diff --git a/packages/varlet-ui/src/uploader/__tests__/index.spec.js b/packages/varlet-ui/src/uploader/__tests__/index.spec.js index afbbe4ff0f5..3673ba61b32 100644 --- a/packages/varlet-ui/src/uploader/__tests__/index.spec.js +++ b/packages/varlet-ui/src/uploader/__tests__/index.spec.js @@ -105,7 +105,7 @@ test('test uploader preview', async () => { }, }) - await wrapper.vm.handleChange(createEvent('cat.jpg', 'image/jpg')) + await wrapper.vm.handleChange(createEvent('cat.png', 'image/png')) await delay(16) await wrapper.find('.var-uploader__file').trigger('click') await delay(100) @@ -365,3 +365,135 @@ test('test uploader extra slot', async () => { wrapper.unmount() }) + +test('test uploader resolve-type as default when file type is image', async () => { + const { mockRestore } = mockFileReader('data:image/png;base64,') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'default', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('cat.png', 'image/png')) + await delay(100) + expect(wrapper.vm.files[0].cover.includes('data:image/png;base64,')).toBe(true) + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) + +test('test uploader resolve-type as default when file type is not image', async () => { + const { mockRestore } = mockFileReader('data:') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'default', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('data.json', 'application/json')) + await delay(100) + expect(wrapper.vm.files[0].cover.includes('data:')).toBe(false) + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) + +test('test uploader resolve-type as file when file type is image', async () => { + const { mockRestore } = mockFileReader('data:image/png;base64,') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'file', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('cat.png', 'image/png')) + await delay(100) + expect(wrapper.vm.files[0].cover).toBe('') + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) + +test('test uploader resolve-type as file when file type is not image', async () => { + const { mockRestore } = mockFileReader('data:') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'file', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('data.json', 'application/json')) + await delay(100) + expect(wrapper.vm.files[0].cover).toBe('') + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) + +test('test uploader resolve-type as data-url when file type is image', async () => { + const { mockRestore } = mockFileReader('data:image/png;base64,') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'data-url', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('cat.png', 'image/png')) + await delay(100) + expect(wrapper.vm.files[0].cover.includes('data:image/png;base64,')).toBe(true) + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) + +test('test uploader resolve-type as data-url when file type is not image', async () => { + const { mockRestore } = mockFileReader('data:') + const { mockRestore: mockRestoreStubs } = mockStubs() + const onUpdateModelValue = vi.fn((value) => wrapper.setProps({ modelValue: value })) + + const wrapper = mount(VarUploader, { + props: { + modelValue: [], + resolveType: 'data-url', + 'onUpdate:modelValue': onUpdateModelValue, + }, + }) + + await wrapper.vm.handleChange(createEvent('data.json', 'application/json')) + await delay(100) + expect(wrapper.vm.files[0].cover.includes('data:')).toBe(true) + + mockRestoreStubs() + wrapper.unmount() + mockRestore() +}) diff --git a/packages/varlet-ui/src/uploader/docs/en-US.md b/packages/varlet-ui/src/uploader/docs/en-US.md index a946743696d..a233b3b44f4 100644 --- a/packages/varlet-ui/src/uploader/docs/en-US.md +++ b/packages/varlet-ui/src/uploader/docs/en-US.md @@ -375,6 +375,7 @@ const files = ref([ | `previewed` | Whether to allow preview | _boolean_ | `true` | | `ripple` | Whether to open ripple | _boolean_ | `true` | | `hide-list` | Whether to hide the file list | _boolean_ | `false` | +| `resolve-type` | The file read result type, Can be set to `default` `file` `data-url` (`default`, the image type contains base64 and File object, other types contain only File object. `file`, which contains only File object. `data-url`, which contains base64 and File object) | _string_ | `default` | | `validate-trigger` | Timing to trigger validation, The optional value is `onChange` `onRemove` | _ValidateTriggers[]_ | `['onChange', 'onRemove']` | | `rules` | The validation rules,Returns `true` to indicate that the validation passed,The remaining values are converted to text as user prompts | _Array<(v: VarFile, u: VarFileUtils) => any>_ | `-` | diff --git a/packages/varlet-ui/src/uploader/docs/zh-CN.md b/packages/varlet-ui/src/uploader/docs/zh-CN.md index 3c4c4f6e608..53ccbbb5dbc 100644 --- a/packages/varlet-ui/src/uploader/docs/zh-CN.md +++ b/packages/varlet-ui/src/uploader/docs/zh-CN.md @@ -374,6 +374,7 @@ const files = ref([ | `previewed` | 是否允许预览 | _boolean_ | `true` | | `ripple` | 是否开启水波纹 | _boolean_ | `true` | | `hide-list` | 是否隐藏文件列表 | _boolean_ | `false` | +| `resolve-type` | 文件读取结果类型,可选值为 `default` `file` `data-url`(`default`,图像包含 base64 编码和 File 对象,其他类型仅包含 File 对象。`file`,仅包含 File 对象。`data-url`,包含 base64 编码和 File 对象) | _string_ | `default` | | `validate-trigger` | 触发验证的时机, 可选值为 `onChange` `onRemove` | _ValidateTriggers[]_ | `['onChange', 'onRemove']` | | `rules` | 验证规则,返回 `true` 表示验证通过,其余的值则转换为文本作为用户提示 | _Array<(v: VarFile, u: VarFileUtils) => any>_ | `-` | diff --git a/packages/varlet-ui/src/uploader/props.ts b/packages/varlet-ui/src/uploader/props.ts index 749c47af12c..5f5510b4087 100644 --- a/packages/varlet-ui/src/uploader/props.ts +++ b/packages/varlet-ui/src/uploader/props.ts @@ -12,7 +12,9 @@ export interface VarFile { state?: 'loading' | 'success' | 'error' } -export type ValidateTrigger = 'onChange' | 'onRemove' +export type UploaderResolveType = 'default' | 'file' | 'data-url' + +export type UploaderValidateTrigger = 'onChange' | 'onRemove' export const props = { modelValue: { @@ -34,6 +36,10 @@ export const props = { type: [Boolean, Number, String], default: true, }, + resolveType: { + type: String as PropType, + default: 'default', + }, removable: { type: Boolean, default: true, @@ -49,7 +55,7 @@ export const props = { default: true, }, validateTrigger: { - type: Array as PropType>, + type: Array as PropType>, default: () => ['onChange', 'onRemove'], }, rules: Array as PropType any>>, diff --git a/packages/varlet-ui/types/uploader.d.ts b/packages/varlet-ui/types/uploader.d.ts index 33dd9ac859a..09709b5fa2f 100644 --- a/packages/varlet-ui/types/uploader.d.ts +++ b/packages/varlet-ui/types/uploader.d.ts @@ -7,6 +7,8 @@ export type VarFileFit = 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' export type VarFileState = 'loading' | 'success' | 'error' +export type VarFileResolveType = 'default' | 'file' | 'data-url' + export interface VarFile { file?: File name?: string @@ -41,6 +43,7 @@ export interface UploaderProps extends BasicAttributes { previewed?: boolean hideList?: boolean ripple?: boolean + resolveType?: VarFileResolveType validateTrigger?: Array rules?: Array<(v: VarFile[], u: UploaderVarFileUtils) => any> onBeforeFilter?: ListenerProp<(files: VarFile[]) => Promise | VarFile[]>