Commit 661db0c767772bb7a30da9d3eeaf2b47858ccf0b
1 parent
a161bfa8
perf(upload): improve upload component
Showing
38 changed files
with
433 additions
and
185 deletions
CHANGELOG.zh_CN.md
README.en-US.md
... | ... | @@ -226,10 +226,10 @@ yarn clean:lib # Delete node_modules, supported window |
226 | 226 | - [x] Data import and export |
227 | 227 | - [x] Global error handling |
228 | 228 | - [x] Rich text component |
229 | +- [x] Upload component | |
229 | 230 | |
230 | 231 | ## Developing features |
231 | 232 | |
232 | -- [ ] Upload component | |
233 | 233 | - [ ] Theme configuration |
234 | 234 | - [ ] Dark theme |
235 | 235 | - [ ] Build CDN | ... | ... |
README.md
package.json
... | ... | @@ -22,8 +22,8 @@ |
22 | 22 | }, |
23 | 23 | "dependencies": { |
24 | 24 | "@iconify/iconify": "^2.0.0-rc.2", |
25 | - "@vueuse/core": "^4.0.0-beta.40", | |
26 | - "ant-design-vue": "^2.0.0-beta.15", | |
25 | + "@vueuse/core": "^4.0.0-beta.41", | |
26 | + "ant-design-vue": "^2.0.0-rc.1", | |
27 | 27 | "apexcharts": "3.22.0", |
28 | 28 | "axios": "^0.21.0", |
29 | 29 | "echarts": "^4.9.0", |
... | ... | @@ -33,10 +33,10 @@ |
33 | 33 | "nprogress": "^0.2.0", |
34 | 34 | "path-to-regexp": "^6.2.0", |
35 | 35 | "qrcode": "^1.4.4", |
36 | - "vditor": "^3.6.0", | |
36 | + "vditor": "^3.6.2", | |
37 | 37 | "vue": "^3.0.2", |
38 | 38 | "vue-i18n": "^9.0.0-beta.6", |
39 | - "vue-router": "^4.0.0-rc.2", | |
39 | + "vue-router": "^4.0.0-rc.3", | |
40 | 40 | "vuex": "^4.0.0-rc.1", |
41 | 41 | "vuex-module-decorators": "^1.0.1", |
42 | 42 | "xlsx": "^0.16.8", |
... | ... | @@ -45,11 +45,11 @@ |
45 | 45 | "devDependencies": { |
46 | 46 | "@commitlint/cli": "^11.0.0", |
47 | 47 | "@commitlint/config-conventional": "^11.0.0", |
48 | - "@iconify/json": "^1.1.254", | |
48 | + "@iconify/json": "^1.1.258", | |
49 | 49 | "@ls-lint/ls-lint": "^1.9.2", |
50 | 50 | "@purge-icons/generated": "^0.4.1", |
51 | 51 | "@types/echarts": "^4.9.0", |
52 | - "@types/fs-extra": "^9.0.2", | |
52 | + "@types/fs-extra": "^9.0.4", | |
53 | 53 | "@types/koa-static": "^4.0.1", |
54 | 54 | "@types/lodash-es": "^4.17.3", |
55 | 55 | "@types/mockjs": "^1.0.3", | ... | ... |
src/api/demo/model/uploadModel.ts renamed to src/api/sys/model/uploadModel.ts
src/api/demo/upload.ts renamed to src/api/sys/upload.ts
src/components/Drawer/src/props.ts
src/components/Form/src/BasicForm.vue
... | ... | @@ -44,7 +44,6 @@ |
44 | 44 | import { useFormValues } from './hooks/useFormValues'; |
45 | 45 | import useAdvanced from './hooks/useAdvanced'; |
46 | 46 | import { useFormAction } from './hooks/useFormAction'; |
47 | - | |
48 | 47 | export default defineComponent({ |
49 | 48 | name: 'BasicForm', |
50 | 49 | components: { FormItem, Form, Row, FormAction }, | ... | ... |
src/components/Icon/index.tsx
src/components/Modal/src/props.ts
1 | 1 | import type { PropType } from 'vue'; |
2 | +import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes'; | |
2 | 3 | export const modalProps = { |
3 | 4 | visible: Boolean as PropType<boolean>, |
4 | 5 | // open drag |
... | ... | @@ -16,7 +17,7 @@ export const modalProps = { |
16 | 17 | }, |
17 | 18 | okText: { |
18 | 19 | type: String as PropType<string>, |
19 | - default: '保存', | |
20 | + default: '确认', | |
20 | 21 | }, |
21 | 22 | closeFunc: Function as PropType<() => Promise<boolean>>, |
22 | 23 | }; |
... | ... | @@ -100,9 +101,9 @@ export const basicProps = Object.assign({}, modalProps, { |
100 | 101 | default: 'primary', |
101 | 102 | }, |
102 | 103 | |
103 | - okButtonProps: Object as PropType<any>, | |
104 | + okButtonProps: Object as PropType<ButtonProps>, | |
104 | 105 | |
105 | - cancelButtonProps: Object as PropType<any>, | |
106 | + cancelButtonProps: Object as PropType<ButtonProps>, | |
106 | 107 | |
107 | 108 | title: { |
108 | 109 | type: String as PropType<string>, | ... | ... |
src/components/Qrcode/src/index.vue
... | ... | @@ -7,7 +7,7 @@ |
7 | 7 | import { defineComponent, watchEffect, PropType, ref, unref } from 'vue'; |
8 | 8 | import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus'; |
9 | 9 | import { toDataURL } from 'qrcode'; |
10 | - import { downloadByUrl } from '/@/utils/file/FileDownload'; | |
10 | + import { downloadByUrl } from '/@/utils/file/download'; | |
11 | 11 | |
12 | 12 | export default defineComponent({ |
13 | 13 | name: 'QrCode', | ... | ... |
src/components/Table/src/BasicTable.vue
src/components/Table/src/hooks/useDataSource.ts
... | ... | @@ -84,7 +84,7 @@ export function useDataSource( |
84 | 84 | const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref( |
85 | 85 | propsRef |
86 | 86 | ); |
87 | - if (!api && !isFunction(api)) return; | |
87 | + if (!api || !isFunction(api)) return; | |
88 | 88 | try { |
89 | 89 | loadingRef.value = true; |
90 | 90 | const { pageField, sizeField, listField, totalField } = fetchSetting || FETCH_SETTING; | ... | ... |
src/components/Table/src/props.ts
... | ... | @@ -16,7 +16,10 @@ export const basicProps = { |
16 | 16 | tableSetting: { |
17 | 17 | type: Object as PropType<TableSetting>, |
18 | 18 | }, |
19 | - | |
19 | + inset: { | |
20 | + type: Boolean as PropType<boolean>, | |
21 | + default: false, | |
22 | + }, | |
20 | 23 | sortFn: { |
21 | 24 | type: Function as PropType<(sortInfo: SorterResult) => any>, |
22 | 25 | default: DEFAULT_SORT_FN, | ... | ... |
src/components/Table/src/style/index.less
src/components/Table/src/types/table.ts
... | ... | @@ -126,6 +126,8 @@ export interface TableSetting { |
126 | 126 | export interface BasicTableProps<T = any> { |
127 | 127 | // 自定义排序方法 |
128 | 128 | sortFn?: (sortInfo: SorterResult) => any; |
129 | + // 取消表格的默认padding | |
130 | + inset?: boolean; | |
129 | 131 | // 显示表格设置 |
130 | 132 | showTableSetting?: boolean; |
131 | 133 | tableSetting?: TableSetting; | ... | ... |
src/components/Upload/index.ts
src/components/Upload/src/UploadContainer.vue renamed to src/components/Upload/src/BasicUpload.vue
1 | 1 | <template> |
2 | 2 | <div> |
3 | 3 | <a-button-group> |
4 | - <a-button type="primary" @click="openUploadModal">上传</a-button> | |
5 | - <a-button @click="openPreviewModal"> | |
6 | - <Icon icon="ant-design:eye-outlined" /> | |
4 | + <a-button type="primary" @click="openUploadModal" preIcon="ant-design:cloud-upload-outlined"> | |
5 | + 上传 | |
7 | 6 | </a-button> |
7 | + <Tooltip placement="bottom" v-if="showPreview"> | |
8 | + <template #title> | |
9 | + 已上传 | |
10 | + <template v-if="fileListRef.length">{{ fileListRef.length }}</template> | |
11 | + </template> | |
12 | + <a-button @click="openPreviewModal"> | |
13 | + <Icon icon="ant-design:eye-outlined" /> | |
14 | + <template v-if="fileListRef.length && showPreviewNumber"> | |
15 | + {{ fileListRef.length }} | |
16 | + </template> | |
17 | + </a-button> | |
18 | + </Tooltip> | |
8 | 19 | </a-button-group> |
9 | - <UploadModal v-bind="$props" @register="registerUploadModal" @change="handleChange" /> | |
20 | + | |
21 | + <UploadModal v-bind="bindValue" @register="registerUploadModal" @change="handleChange" /> | |
22 | + | |
10 | 23 | <UploadPreviewModal |
11 | 24 | :value="fileListRef" |
12 | 25 | @register="registerPreviewModal" |
13 | - @change="handlePreviewChange" | |
26 | + @list-change="handlePreviewChange" | |
14 | 27 | /> |
15 | 28 | </div> |
16 | 29 | </template> |
17 | 30 | <script lang="ts"> |
18 | - import { defineComponent, ref, watch, unref } from 'vue'; | |
19 | - import { useModal } from '/@/components/Modal'; | |
31 | + import { defineComponent, ref, watch, unref, computed } from 'vue'; | |
32 | + | |
20 | 33 | import UploadModal from './UploadModal.vue'; |
21 | - import { uploadContainerProps } from './props'; | |
22 | 34 | import UploadPreviewModal from './UploadPreviewModal.vue'; |
23 | - import Icon from '/@/components/Icon/index'; | |
35 | + import Icon from '/@/components/Icon'; | |
36 | + import { Tooltip } from 'ant-design-vue'; | |
37 | + | |
38 | + import { useModal } from '/@/components/Modal'; | |
39 | + | |
40 | + import { uploadContainerProps } from './props'; | |
41 | + import { omit } from 'lodash-es'; | |
42 | + | |
24 | 43 | export default defineComponent({ |
25 | - components: { UploadModal, UploadPreviewModal, Icon }, | |
44 | + components: { UploadModal, UploadPreviewModal, Icon, Tooltip }, | |
26 | 45 | props: uploadContainerProps, |
27 | - setup(props, { emit }) { | |
46 | + setup(props, { emit, attrs }) { | |
28 | 47 | // 上传modal |
29 | 48 | const [registerUploadModal, { openModal: openUploadModal }] = useModal(); |
49 | + | |
30 | 50 | // 预览modal |
31 | 51 | const [registerPreviewModal, { openModal: openPreviewModal }] = useModal(); |
32 | 52 | |
33 | 53 | const fileListRef = ref<string[]>([]); |
54 | + | |
55 | + const showPreview = computed(() => { | |
56 | + const { emptyHidePreview } = props; | |
57 | + if (!emptyHidePreview) return true; | |
58 | + return emptyHidePreview ? fileListRef.value.length > 0 : true; | |
59 | + }); | |
60 | + | |
61 | + const bindValue = computed(() => { | |
62 | + const value = { ...attrs, ...props }; | |
63 | + return omit(value, 'onChange'); | |
64 | + }); | |
65 | + | |
34 | 66 | watch( |
35 | 67 | () => props.value, |
36 | - (value) => { | |
37 | - fileListRef.value = [...(value || [])]; | |
68 | + (value = []) => { | |
69 | + fileListRef.value = value; | |
38 | 70 | }, |
39 | 71 | { immediate: true } |
40 | 72 | ); |
73 | + | |
41 | 74 | // 上传modal保存操作 |
42 | 75 | function handleChange(urls: string[]) { |
43 | 76 | fileListRef.value = [...unref(fileListRef), ...(urls || [])]; |
44 | 77 | emit('change', fileListRef.value); |
45 | 78 | } |
79 | + | |
46 | 80 | // 预览modal保存操作 |
47 | 81 | function handlePreviewChange(urls: string[]) { |
48 | 82 | fileListRef.value = [...(urls || [])]; |
49 | 83 | emit('change', fileListRef.value); |
50 | 84 | } |
85 | + | |
51 | 86 | return { |
52 | 87 | registerUploadModal, |
53 | 88 | openUploadModal, |
... | ... | @@ -56,6 +91,8 @@ |
56 | 91 | registerPreviewModal, |
57 | 92 | openPreviewModal, |
58 | 93 | fileListRef, |
94 | + showPreview, | |
95 | + bindValue, | |
59 | 96 | }; |
60 | 97 | }, |
61 | 98 | }); | ... | ... |
src/components/Upload/src/ThumnUrl.vue
... | ... | @@ -5,25 +5,22 @@ |
5 | 5 | </span> |
6 | 6 | </template> |
7 | 7 | <script lang="ts"> |
8 | - import { defineComponent } from 'vue'; | |
8 | + import { defineComponent, PropType } from 'vue'; | |
9 | 9 | |
10 | 10 | export default defineComponent({ |
11 | 11 | props: { |
12 | 12 | fileUrl: { |
13 | - type: String, | |
13 | + type: String as PropType<string>, | |
14 | 14 | default: '', |
15 | 15 | }, |
16 | 16 | fileType: { |
17 | - type: String, | |
17 | + type: String as PropType<string>, | |
18 | 18 | default: '', |
19 | 19 | }, |
20 | 20 | fileName: { |
21 | - type: String, | |
21 | + type: String as PropType<string>, | |
22 | 22 | default: '', |
23 | 23 | }, |
24 | 24 | }, |
25 | - setup() { | |
26 | - return {}; | |
27 | - }, | |
28 | 25 | }); |
29 | 26 | </script> | ... | ... |
src/components/Upload/src/UploadModal.vue
1 | 1 | <template> |
2 | 2 | <BasicModal |
3 | + width="800px" | |
4 | + title="上传" | |
5 | + okText="保存" | |
3 | 6 | v-bind="$attrs" |
4 | 7 | @register="register" |
5 | 8 | @ok="handleOk" |
6 | 9 | :closeFunc="handleCloseFunc" |
7 | 10 | :maskClosable="false" |
8 | - width="800px" | |
9 | - title="上传组件" | |
11 | + :keyboard="false" | |
10 | 12 | wrapClassName="upload-modal" |
11 | - :okButtonProps="{ disabled: isUploadingRef }" | |
13 | + :okButtonProps="getOkButtonProps" | |
12 | 14 | :cancelButtonProps="{ disabled: isUploadingRef }" |
13 | 15 | > |
14 | 16 | <template #centerdFooter> |
15 | - <a-button @click="handleStartUpload" color="success" :loading="isUploadingRef"> | |
16 | - {{ isUploadingRef ? '上传中' : '开始上传' }} | |
17 | + <a-button | |
18 | + @click="handleStartUpload" | |
19 | + color="success" | |
20 | + :disabled="!getIsSelectFile" | |
21 | + :loading="isUploadingRef" | |
22 | + > | |
23 | + {{ getUploadBtnText }} | |
17 | 24 | </a-button> |
18 | 25 | </template> |
19 | - <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload"> | |
20 | - <a-button type="primary"> 选择文件 </a-button> | |
21 | - <span class="px-2">{{ getHelpText }}</span> | |
22 | - </Upload> | |
23 | - <BasicTable @register="registerTable" :dataSource="fileListRef" /> | |
26 | + | |
27 | + <BasicTable @register="registerTable" :dataSource="fileListRef"> | |
28 | + <template #toolbar> | |
29 | + <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload"> | |
30 | + <a-button type="primary"> 选择文件 </a-button> | |
31 | + </Upload> | |
32 | + </template> | |
33 | + <template #tableTitle> | |
34 | + <Alert :message="getHelpText" type="info" banner></Alert> | |
35 | + </template> | |
36 | + </BasicTable> | |
24 | 37 | </BasicModal> |
25 | 38 | </template> |
26 | 39 | <script lang="ts"> |
27 | - import { defineComponent, reactive, ref, toRef, unref } from 'vue'; | |
28 | - import { Upload } from 'ant-design-vue'; | |
40 | + import { defineComponent, reactive, ref, toRefs, unref, computed } from 'vue'; | |
41 | + import { Upload, Alert } from 'ant-design-vue'; | |
29 | 42 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
30 | 43 | import { BasicTable, useTable } from '/@/components/Table'; |
31 | 44 | // hooks |
... | ... | @@ -39,23 +52,56 @@ |
39 | 52 | import { checkFileType, checkImgType, getBase64WithFile } from './utils'; |
40 | 53 | import { buildUUID } from '/@/utils/uuid'; |
41 | 54 | import { createImgPreview } from '/@/components/Preview/index'; |
42 | - import { uploadApi } from '/@/api/demo/upload'; | |
55 | + import { uploadApi } from '/@/api/sys/upload'; | |
56 | + import { isFunction } from '/@/utils/is'; | |
57 | + import { warn } from '/@/utils/log'; | |
43 | 58 | |
44 | 59 | export default defineComponent({ |
45 | - components: { BasicModal, Upload, BasicTable }, | |
60 | + components: { BasicModal, Upload, BasicTable, Alert }, | |
46 | 61 | props: basicProps, |
47 | 62 | setup(props, { emit }) { |
63 | + // 是否正在上传 | |
64 | + const isUploadingRef = ref(false); | |
65 | + const fileListRef = ref<FileItem[]>([]); | |
66 | + const state = reactive<{ fileList: FileItem[] }>({ | |
67 | + fileList: [], | |
68 | + }); | |
69 | + | |
48 | 70 | const [register, { closeModal }] = useModalInner(); |
71 | + | |
72 | + const { accept, helpText, maxNumber, maxSize } = toRefs(props); | |
49 | 73 | const { getAccept, getStringAccept, getHelpText } = useUploadType({ |
50 | - acceptRef: toRef(props, 'accept'), | |
51 | - helpTextRef: toRef(props, 'helpText'), | |
52 | - maxNumberRef: toRef(props, 'maxNumber'), | |
53 | - maxSizeRef: toRef(props, 'maxSize'), | |
74 | + acceptRef: accept, | |
75 | + helpTextRef: helpText, | |
76 | + maxNumberRef: maxNumber, | |
77 | + maxSizeRef: maxSize, | |
54 | 78 | }); |
55 | 79 | |
56 | - const fileListRef = ref<FileItem[]>([]); | |
57 | - const state = reactive<{ fileList: FileItem[] }>({ fileList: [] }); | |
58 | 80 | const { createMessage } = useMessage(); |
81 | + | |
82 | + const getIsSelectFile = computed(() => { | |
83 | + return ( | |
84 | + fileListRef.value.length > 0 && | |
85 | + !fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS) | |
86 | + ); | |
87 | + }); | |
88 | + | |
89 | + const getOkButtonProps = computed(() => { | |
90 | + const someSuccess = fileListRef.value.some( | |
91 | + (item) => item.status === UploadResultStatus.SUCCESS | |
92 | + ); | |
93 | + return { | |
94 | + disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess, | |
95 | + }; | |
96 | + }); | |
97 | + | |
98 | + const getUploadBtnText = computed(() => { | |
99 | + const someError = fileListRef.value.some( | |
100 | + (item) => item.status === UploadResultStatus.ERROR | |
101 | + ); | |
102 | + return isUploadingRef.value ? '上传中' : someError ? '重新上传失败文件' : '开始上传'; | |
103 | + }); | |
104 | + | |
59 | 105 | // 上传前校验 |
60 | 106 | function beforeUpload(file: File) { |
61 | 107 | const { size, name } = file; |
... | ... | @@ -73,6 +119,14 @@ |
73 | 119 | createMessage.error!(`只能上传${accept.join(',')}格式文件`); |
74 | 120 | return false; |
75 | 121 | } |
122 | + const commonItem = { | |
123 | + uuid: buildUUID(), | |
124 | + file, | |
125 | + size, | |
126 | + name, | |
127 | + percent: 0, | |
128 | + type: name.split('.').pop(), | |
129 | + }; | |
76 | 130 | // 生成图片缩略图 |
77 | 131 | if (checkImgType(file)) { |
78 | 132 | // beforeUpload,如果异步会调用自带上传方法 |
... | ... | @@ -81,29 +135,13 @@ |
81 | 135 | fileListRef.value = [ |
82 | 136 | ...unref(fileListRef), |
83 | 137 | { |
84 | - uuid: buildUUID(), | |
85 | - file, | |
86 | 138 | thumbUrl, |
87 | - size, | |
88 | - name, | |
89 | - percent: 0, | |
90 | - type: name.split('.').pop(), | |
139 | + ...commonItem, | |
91 | 140 | }, |
92 | 141 | ]; |
93 | 142 | }); |
94 | 143 | } else { |
95 | - fileListRef.value = [ | |
96 | - ...unref(fileListRef), | |
97 | - { | |
98 | - uuid: buildUUID(), | |
99 | - | |
100 | - file, | |
101 | - size, | |
102 | - name, | |
103 | - percent: 0, | |
104 | - type: name.split('.').pop(), | |
105 | - }, | |
106 | - ]; | |
144 | + fileListRef.value = [...unref(fileListRef), commonItem]; | |
107 | 145 | } |
108 | 146 | return false; |
109 | 147 | } |
... | ... | @@ -112,6 +150,7 @@ |
112 | 150 | const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid); |
113 | 151 | index !== -1 && fileListRef.value.splice(index, 1); |
114 | 152 | } |
153 | + | |
115 | 154 | // 预览 |
116 | 155 | function handlePreview(record: FileItem) { |
117 | 156 | const { thumbUrl = '' } = record; |
... | ... | @@ -119,19 +158,18 @@ |
119 | 158 | imageList: [thumbUrl], |
120 | 159 | }); |
121 | 160 | } |
122 | - const [registerTable] = useTable({ | |
123 | - columns: createTableColumns(), | |
124 | - actionColumn: createActionColumn(handleRemove, handlePreview), | |
125 | - pagination: false, | |
126 | - }); | |
127 | - // 是否正在上传 | |
128 | - const isUploadingRef = ref(false); | |
161 | + | |
129 | 162 | async function uploadApiByItem(item: FileItem) { |
163 | + const { api } = props; | |
164 | + if (!api || !isFunction(api)) { | |
165 | + return warn('upload api must exist and be a function'); | |
166 | + } | |
130 | 167 | try { |
131 | 168 | item.status = UploadResultStatus.UPLOADING; |
132 | 169 | |
133 | 170 | const { data } = await uploadApi( |
134 | 171 | { |
172 | + ...(props.uploadParams || {}), | |
135 | 173 | file: item.file, |
136 | 174 | }, |
137 | 175 | function onUploadProgress(progressEvent: ProgressEvent) { |
... | ... | @@ -154,32 +192,42 @@ |
154 | 192 | }; |
155 | 193 | } |
156 | 194 | } |
195 | + | |
157 | 196 | // 点击开始上传 |
158 | 197 | async function handleStartUpload() { |
198 | + const { maxNumber } = props; | |
199 | + if (fileListRef.value.length > maxNumber) { | |
200 | + return createMessage.warning(`最多只能上传${maxNumber}个文件`); | |
201 | + } | |
159 | 202 | try { |
160 | 203 | isUploadingRef.value = true; |
204 | + // 只上传不是成功状态的 | |
205 | + const uploadFileList = | |
206 | + fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || []; | |
161 | 207 | const data = await Promise.all( |
162 | - unref(fileListRef).map((item) => { | |
208 | + uploadFileList.map((item) => { | |
163 | 209 | return uploadApiByItem(item); |
164 | 210 | }) |
165 | 211 | ); |
166 | 212 | isUploadingRef.value = false; |
167 | 213 | // 生产环境:抛出错误 |
168 | - const errorList = data.filter((item) => !item.success); | |
169 | - if (errorList.length > 0) { | |
170 | - throw errorList; | |
171 | - } | |
214 | + const errorList = data.filter((item: any) => !item.success); | |
215 | + if (errorList.length > 0) throw errorList; | |
172 | 216 | } catch (e) { |
173 | 217 | isUploadingRef.value = false; |
174 | 218 | throw e; |
175 | 219 | } |
176 | 220 | } |
221 | + | |
177 | 222 | // 点击保存 |
178 | 223 | function handleOk() { |
179 | - // TODO: 没起作用:okButtonProps={{ disabled: state.isUploading }} | |
224 | + const { maxNumber } = props; | |
225 | + | |
226 | + if (fileListRef.value.length > maxNumber) { | |
227 | + return createMessage.warning(`最多只能上传${maxNumber}个文件`); | |
228 | + } | |
180 | 229 | if (isUploadingRef.value) { |
181 | - createMessage.warning('请等待文件上传后,保存'); | |
182 | - return; | |
230 | + return createMessage.warning('请等待文件上传后,保存'); | |
183 | 231 | } |
184 | 232 | const fileList: string[] = []; |
185 | 233 | |
... | ... | @@ -189,18 +237,15 @@ |
189 | 237 | fileList.push(responseData.url); |
190 | 238 | } |
191 | 239 | } |
192 | - | |
193 | 240 | // 存在一个上传成功的即可保存 |
194 | - | |
195 | 241 | if (fileList.length <= 0) { |
196 | - createMessage.warning('没有上传成功的文件,无法保存'); | |
197 | - return; | |
242 | + return createMessage.warning('没有上传成功的文件,无法保存'); | |
198 | 243 | } |
199 | - console.log(fileList); | |
200 | - emit('change', fileList); | |
201 | 244 | fileListRef.value = []; |
202 | 245 | closeModal(); |
246 | + emit('change', fileList); | |
203 | 247 | } |
248 | + | |
204 | 249 | // 点击关闭:则所有操作不保存,包括上传的 |
205 | 250 | function handleCloseFunc() { |
206 | 251 | if (!isUploadingRef.value) { |
... | ... | @@ -211,11 +256,22 @@ |
211 | 256 | return false; |
212 | 257 | } |
213 | 258 | } |
259 | + | |
260 | + const [registerTable] = useTable({ | |
261 | + columns: createTableColumns(), | |
262 | + actionColumn: createActionColumn(handleRemove, handlePreview), | |
263 | + pagination: false, | |
264 | + inset: true, | |
265 | + scroll: { | |
266 | + y: 3000, | |
267 | + }, | |
268 | + }); | |
214 | 269 | return { |
215 | 270 | register, |
216 | 271 | closeModal, |
217 | 272 | getHelpText, |
218 | 273 | getStringAccept, |
274 | + getOkButtonProps, | |
219 | 275 | beforeUpload, |
220 | 276 | registerTable, |
221 | 277 | fileListRef, |
... | ... | @@ -224,14 +280,13 @@ |
224 | 280 | handleStartUpload, |
225 | 281 | handleOk, |
226 | 282 | handleCloseFunc, |
283 | + getIsSelectFile, | |
284 | + getUploadBtnText, | |
227 | 285 | }; |
228 | 286 | }, |
229 | 287 | }); |
230 | 288 | </script> |
231 | 289 | <style lang="less"> |
232 | - // /deep/ .ant-upload-list { | |
233 | - // display: none; | |
234 | - // } | |
235 | 290 | .upload-modal { |
236 | 291 | .ant-upload-list { |
237 | 292 | display: none; | ... | ... |
src/components/Upload/src/UploadPreviewModal.vue
1 | 1 | <template> |
2 | 2 | <BasicModal |
3 | + width="800px" | |
4 | + title="预览" | |
3 | 5 | wrapClassName="upload-preview-modal" |
4 | 6 | v-bind="$attrs" |
5 | - width="800px" | |
6 | 7 | @register="register" |
7 | - title="预览" | |
8 | 8 | :showOkBtn="false" |
9 | 9 | > |
10 | 10 | <BasicTable @register="registerTable" :dataSource="fileListRef" /> |
... | ... | @@ -12,17 +12,18 @@ |
12 | 12 | </template> |
13 | 13 | <script lang="ts"> |
14 | 14 | import { defineComponent, watch, ref, unref } from 'vue'; |
15 | + | |
15 | 16 | import { BasicTable, useTable } from '/@/components/Table'; |
16 | - import { createPreviewColumns, createPreviewActionColumn } from './data'; | |
17 | 17 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
18 | - import { priviewProps } from './props'; | |
18 | + import { previewProps } from './props'; | |
19 | 19 | import { PreviewFileItem } from './types'; |
20 | 20 | import { createImgPreview } from '/@/components/Preview/index'; |
21 | - import { downloadByUrl } from '/@/utils/file/FileDownload'; | |
21 | + import { downloadByUrl } from '/@/utils/file/download'; | |
22 | 22 | |
23 | + import { createPreviewColumns, createPreviewActionColumn } from './data'; | |
23 | 24 | export default defineComponent({ |
24 | 25 | components: { BasicModal, BasicTable }, |
25 | - props: priviewProps, | |
26 | + props: previewProps, | |
26 | 27 | setup(props, { emit }) { |
27 | 28 | const [register, { closeModal }] = useModalInner(); |
28 | 29 | const fileListRef = ref<PreviewFileItem[]>([]); |
... | ... | @@ -43,17 +44,19 @@ |
43 | 44 | }, |
44 | 45 | { immediate: true } |
45 | 46 | ); |
47 | + | |
46 | 48 | // 删除 |
47 | 49 | function handleRemove(record: PreviewFileItem) { |
48 | 50 | const index = fileListRef.value.findIndex((item) => item.url === record.url); |
49 | 51 | if (index !== -1) { |
50 | 52 | fileListRef.value.splice(index, 1); |
51 | 53 | emit( |
52 | - 'change', | |
54 | + 'list-change', | |
53 | 55 | fileListRef.value.map((item) => item.url) |
54 | 56 | ); |
55 | 57 | } |
56 | 58 | } |
59 | + | |
57 | 60 | // 预览 |
58 | 61 | function handlePreview(record: PreviewFileItem) { |
59 | 62 | const { url = '' } = record; |
... | ... | @@ -61,16 +64,19 @@ |
61 | 64 | imageList: [url], |
62 | 65 | }); |
63 | 66 | } |
67 | + | |
64 | 68 | // 下载 |
65 | 69 | function handleDownload(record: PreviewFileItem) { |
66 | 70 | const { url = '' } = record; |
67 | 71 | downloadByUrl({ url }); |
68 | 72 | } |
73 | + | |
69 | 74 | const [registerTable] = useTable({ |
70 | 75 | columns: createPreviewColumns(), |
71 | 76 | pagination: false, |
72 | 77 | actionColumn: createPreviewActionColumn({ handleRemove, handlePreview, handleDownload }), |
73 | 78 | }); |
79 | + | |
74 | 80 | return { |
75 | 81 | register, |
76 | 82 | closeModal, | ... | ... |
src/components/Upload/src/data.tsx
1 | -// import { BasicColumn, TableAction, ActionItem } from '@/components/table'; | |
2 | 1 | import { checkImgType, isImgTypeByName } from './utils'; |
3 | -// import ThumnUrl from './ThumbUrl.vue'; | |
4 | -import { Progress } from 'ant-design-vue'; | |
2 | +import { Progress, Tag } from 'ant-design-vue'; | |
5 | 3 | import { FileItem, PreviewFileItem, UploadResultStatus } from './types'; |
6 | -// import { ElecArchivesSaveResult } from '@/api/biz/file/model/fileModel'; | |
7 | -// import { quryFile } from '@/api/biz/file/file'; | |
8 | 4 | import { BasicColumn, ActionItem, TableAction } from '/@/components/Table/index'; |
9 | 5 | |
10 | 6 | // 文件上传列表 |
... | ... | @@ -16,8 +12,7 @@ export function createTableColumns(): BasicColumn[] { |
16 | 12 | width: 100, |
17 | 13 | customRender: ({ record }) => { |
18 | 14 | const { thumbUrl, type } = (record as FileItem) || {}; |
19 | - return <span>{thumbUrl ? <img src={thumbUrl} style={{ width: '50px' }} /> : type}</span>; | |
20 | - // return <ThumnUrl fileUrl={thumbUrl} fileType={type} fileName={type} />; | |
15 | + return <span>{thumbUrl ? <img style={{ maxWidth: '60px' }} src={thumbUrl} /> : type}</span>; | |
21 | 16 | }, |
22 | 17 | }, |
23 | 18 | { |
... | ... | @@ -26,7 +21,7 @@ export function createTableColumns(): BasicColumn[] { |
26 | 21 | align: 'left', |
27 | 22 | customRender: ({ text, record }) => { |
28 | 23 | const { percent, status: uploadStatus } = (record as FileItem) || {}; |
29 | - let status = 'normal'; | |
24 | + let status: 'normal' | 'exception' | 'active' | 'success' = 'normal'; | |
30 | 25 | if (uploadStatus === UploadResultStatus.ERROR) { |
31 | 26 | status = 'exception'; |
32 | 27 | } else if (uploadStatus === UploadResultStatus.UPLOADING) { |
... | ... | @@ -63,11 +58,11 @@ export function createTableColumns(): BasicColumn[] { |
63 | 58 | width: 100, |
64 | 59 | customRender: ({ text }) => { |
65 | 60 | if (text === UploadResultStatus.SUCCESS) { |
66 | - return '上传成功'; | |
61 | + return <Tag color="green">{() => '上传成功'}</Tag>; | |
67 | 62 | } else if (text === UploadResultStatus.ERROR) { |
68 | - return '上传失败'; | |
63 | + return <Tag color="red">{() => '上传失败'}</Tag>; | |
69 | 64 | } else if (text === UploadResultStatus.UPLOADING) { |
70 | - return '上传中'; | |
65 | + return <Tag color="blue">{() => '上传中'}</Tag>; | |
71 | 66 | } |
72 | 67 | |
73 | 68 | return text; |
... | ... | @@ -85,6 +80,7 @@ export function createActionColumn(handleRemove: Function, handlePreview: Functi |
85 | 80 | const actions: ActionItem[] = [ |
86 | 81 | { |
87 | 82 | label: '删除', |
83 | + color: 'error', | |
88 | 84 | onClick: handleRemove.bind(null, record), |
89 | 85 | }, |
90 | 86 | ]; |
... | ... | @@ -125,9 +121,9 @@ export function createPreviewActionColumn({ |
125 | 121 | handlePreview, |
126 | 122 | handleDownload, |
127 | 123 | }: { |
128 | - handleRemove: Function; | |
129 | - handlePreview: Function; | |
130 | - handleDownload: Function; | |
124 | + handleRemove: Fn; | |
125 | + handlePreview: Fn; | |
126 | + handleDownload: Fn; | |
131 | 127 | }): BasicColumn { |
132 | 128 | return { |
133 | 129 | width: 160, |
... | ... | @@ -135,11 +131,12 @@ export function createPreviewActionColumn({ |
135 | 131 | dataIndex: 'action', |
136 | 132 | fixed: false, |
137 | 133 | customRender: ({ record }) => { |
138 | - const { url } = (record as PreviewFileItem) || {}; | |
134 | + const { url } = (record || {}) as PreviewFileItem; | |
139 | 135 | |
140 | 136 | const actions: ActionItem[] = [ |
141 | 137 | { |
142 | 138 | label: '删除', |
139 | + color: 'error', | |
143 | 140 | onClick: handleRemove.bind(null, record), |
144 | 141 | }, |
145 | 142 | { | ... | ... |
src/components/Upload/src/props.ts
... | ... | @@ -10,10 +10,10 @@ export const basicProps = { |
10 | 10 | type: Number as PropType<number>, |
11 | 11 | default: 2, |
12 | 12 | }, |
13 | - // 最大数量的文件,0不限制 | |
13 | + // 最大数量的文件,Infinity不限制 | |
14 | 14 | maxNumber: { |
15 | 15 | type: Number as PropType<number>, |
16 | - default: 0, | |
16 | + default: Infinity, | |
17 | 17 | }, |
18 | 18 | // 根据后缀,或者其他 |
19 | 19 | accept: { |
... | ... | @@ -21,9 +21,18 @@ export const basicProps = { |
21 | 21 | default: () => [], |
22 | 22 | }, |
23 | 23 | multiple: { |
24 | - type: Boolean, | |
24 | + type: Boolean as PropType<boolean>, | |
25 | 25 | default: true, |
26 | 26 | }, |
27 | + uploadParams: { | |
28 | + type: Object as PropType<any>, | |
29 | + default: {}, | |
30 | + }, | |
31 | + api: { | |
32 | + type: Function as PropType<PromiseFn>, | |
33 | + default: null, | |
34 | + required: true, | |
35 | + }, | |
27 | 36 | }; |
28 | 37 | |
29 | 38 | export const uploadContainerProps = { |
... | ... | @@ -32,9 +41,17 @@ export const uploadContainerProps = { |
32 | 41 | default: () => [], |
33 | 42 | }, |
34 | 43 | ...basicProps, |
44 | + showPreviewNumber: { | |
45 | + type: Boolean as PropType<boolean>, | |
46 | + default: true, | |
47 | + }, | |
48 | + emptyHidePreview: { | |
49 | + type: Boolean as PropType<boolean>, | |
50 | + default: false, | |
51 | + }, | |
35 | 52 | }; |
36 | 53 | |
37 | -export const priviewProps = { | |
54 | +export const previewProps = { | |
38 | 55 | value: { |
39 | 56 | type: Array as PropType<string[]>, |
40 | 57 | default: () => [], | ... | ... |
src/components/Upload/src/types.ts
src/components/Upload/src/useUpload.ts
... | ... | @@ -42,12 +42,12 @@ export function useUploadType({ |
42 | 42 | |
43 | 43 | const maxSize = unref(maxSizeRef); |
44 | 44 | if (maxSize) { |
45 | - helpTexts.push(`不超过${maxSize}MB`); | |
45 | + helpTexts.push(`单个文件不超过${maxSize}MB`); | |
46 | 46 | } |
47 | 47 | |
48 | 48 | const maxNumber = unref(maxNumberRef); |
49 | - if (maxNumber) { | |
50 | - helpTexts.push(`最多可选择${maxNumber}个文件`); | |
49 | + if (maxNumber && maxNumber !== Infinity) { | |
50 | + helpTexts.push(`最多只能上传${maxNumber}个文件`); | |
51 | 51 | } |
52 | 52 | return helpTexts.join(','); |
53 | 53 | }); | ... | ... |
src/components/Upload/src/utils.ts
... | ... | @@ -3,18 +3,17 @@ export function checkFileType(file: File, accepts: string[]) { |
3 | 3 | // const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i; |
4 | 4 | const reg = new RegExp('\\.(' + newTypes + ')$', 'i'); |
5 | 5 | |
6 | - if (!reg.test(file.name)) { | |
7 | - return false; | |
8 | - } else { | |
9 | - return true; | |
10 | - } | |
6 | + return reg.test(file.name); | |
11 | 7 | } |
8 | + | |
12 | 9 | export function checkImgType(file: File) { |
13 | - return /\.(jpg|jpeg|png|gif)$/i.test(file.name); | |
10 | + return isImgTypeByName(file.name); | |
14 | 11 | } |
12 | + | |
15 | 13 | export function isImgTypeByName(name: string) { |
16 | 14 | return /\.(jpg|jpeg|png|gif)$/i.test(name); |
17 | 15 | } |
16 | + | |
18 | 17 | export function getBase64WithFile(file: File) { |
19 | 18 | return new Promise<{ |
20 | 19 | result: string; | ... | ... |
src/design/ant/btn.less
... | ... | @@ -6,6 +6,11 @@ |
6 | 6 | // &.ant-btn-primary:not(.ant-btn-link) { |
7 | 7 | // box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08) !important; |
8 | 8 | // } |
9 | + // &-group { | |
10 | + // .ant-btn:not(:first-child) { | |
11 | + // bottom: 1px; | |
12 | + // } | |
13 | + // } | |
9 | 14 | |
10 | 15 | &-primary { |
11 | 16 | color: @white; | ... | ... |
src/design/var/index.less
src/router/menus/modules/demo/comp.ts
... | ... | @@ -4,6 +4,9 @@ const menu: MenuModule = { |
4 | 4 | menu: { |
5 | 5 | name: '组件', |
6 | 6 | path: '/comp', |
7 | + tag: { | |
8 | + dot: true, | |
9 | + }, | |
7 | 10 | children: [ |
8 | 11 | { |
9 | 12 | path: 'basic', |
... | ... | @@ -38,10 +41,13 @@ const menu: MenuModule = { |
38 | 41 | path: 'strength-meter', |
39 | 42 | name: '密码强度组件', |
40 | 43 | }, |
41 | - // { | |
42 | - // path: 'upload', | |
43 | - // name: '上传组件', | |
44 | - // }, | |
44 | + { | |
45 | + path: 'upload', | |
46 | + name: '上传组件', | |
47 | + tag: { | |
48 | + content: 'new', | |
49 | + }, | |
50 | + }, | |
45 | 51 | { |
46 | 52 | path: 'scroll', |
47 | 53 | name: '滚动组件', | ... | ... |
src/types/global.d.ts
src/utils/file/FileDownload.ts renamed to src/utils/file/download.ts
1 | -import { dataURLtoBlob } from './stream'; | |
1 | +import { dataURLtoBlob, urlToBase64 } from './stream'; | |
2 | 2 | |
3 | +/** | |
4 | + * Download online pictures | |
5 | + * @param url | |
6 | + * @param filename | |
7 | + * @param mime | |
8 | + * @param bom | |
9 | + */ | |
10 | +export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) { | |
11 | + urlToBase64(url).then((base64) => { | |
12 | + downloadByBase64(base64, filename, mime, bom); | |
13 | + }); | |
14 | +} | |
15 | + | |
16 | +/** | |
17 | + * Download pictures based on base64 | |
18 | + * @param buf | |
19 | + * @param filename | |
20 | + * @param mime | |
21 | + * @param bom | |
22 | + */ | |
3 | 23 | export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) { |
4 | 24 | const base64Buf = dataURLtoBlob(buf); |
5 | 25 | downloadByData(base64Buf, filename, mime, bom); | ... | ... |
src/utils/file/stream.ts
... | ... | @@ -13,3 +13,29 @@ export function dataURLtoBlob(base64Buf: string): Blob { |
13 | 13 | } |
14 | 14 | return new Blob([u8arr], { type: mime }); |
15 | 15 | } |
16 | + | |
17 | +/** | |
18 | + * img url to base64 | |
19 | + * @param url | |
20 | + */ | |
21 | +export function urlToBase64(url: string, mineType?: string): Promise<string> { | |
22 | + return new Promise((resolve, reject) => { | |
23 | + let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>; | |
24 | + const ctx = canvas!.getContext('2d'); | |
25 | + | |
26 | + const img = new Image(); | |
27 | + img.crossOrigin = ''; | |
28 | + img.onload = function () { | |
29 | + if (!canvas || !ctx) { | |
30 | + return reject(); | |
31 | + } | |
32 | + canvas.height = img.height; | |
33 | + canvas.width = img.width; | |
34 | + ctx.drawImage(img, 0, 0); | |
35 | + const dataURL = canvas.toDataURL(mineType || 'image/png'); | |
36 | + canvas = null; | |
37 | + resolve(dataURL); | |
38 | + }; | |
39 | + img.src = url; | |
40 | + }); | |
41 | +} | ... | ... |
src/utils/http/axios/Axios.ts
... | ... | @@ -118,11 +118,8 @@ export class VAxios { |
118 | 118 | Object.keys(params.data).forEach((key) => { |
119 | 119 | if (!params.data) return; |
120 | 120 | const value = params.data[key]; |
121 | - // support key-value array data | |
122 | 121 | if (Array.isArray(value)) { |
123 | 122 | value.forEach((item) => { |
124 | - // { list: [ 11, 22 ] } | |
125 | - // formData.append('list[]', 11); | |
126 | 123 | formData.append(`${key}[]`, item); |
127 | 124 | }); |
128 | 125 | return; | ... | ... |
src/utils/http/axios/index.ts
... | ... | @@ -160,20 +160,18 @@ const transform: AxiosTransform = { |
160 | 160 | try { |
161 | 161 | if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { |
162 | 162 | createMessage.error('接口请求超时,请刷新页面重试!'); |
163 | - return; | |
164 | 163 | } |
165 | 164 | if (err && err.includes('Network Error')) { |
166 | 165 | createErrorModal({ |
167 | 166 | title: '网络异常', |
168 | 167 | content: '请检查您的网络连接是否正常!', |
169 | 168 | }); |
170 | - return; | |
171 | 169 | } |
172 | 170 | } catch (error) { |
173 | 171 | throw new Error(error); |
174 | 172 | } |
175 | 173 | checkStatus(error.response && error.response.status, msg); |
176 | - return error; | |
174 | + return Promise.reject(error); | |
177 | 175 | }, |
178 | 176 | }; |
179 | 177 | ... | ... |
src/utils/http/axios/types.ts
src/views/demo/comp/upload/index.vue
1 | 1 | <template> |
2 | 2 | <div class="p-4"> |
3 | - <UploadContainer :maxSize="5" /> | |
3 | + <a-alert message="基础示例" class="my-5"></a-alert> | |
4 | + <BasicUpload :maxSize="20" :maxNumber="10" @change="handleChange" :api="uploadApi" /> | |
5 | + | |
6 | + <a-alert message="嵌入表单,加入表单校验" class="my-5"></a-alert> | |
7 | + | |
8 | + <BasicForm @register="register" /> | |
4 | 9 | </div> |
5 | 10 | </template> |
6 | 11 | <script lang="ts"> |
7 | - import { defineComponent } from 'vue'; | |
8 | - import { UploadContainer } from '/@/components/Upload/index'; | |
12 | + import { defineComponent, h } from 'vue'; | |
13 | + import { BasicUpload } from '/@/components/Upload'; | |
14 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
15 | + import { BasicForm, FormSchema, useForm } from '/@/components/Form/index'; | |
9 | 16 | |
17 | + import { uploadApi } from '/@/api/sys/upload'; | |
10 | 18 | // import { Alert } from 'ant-design-vue'; |
19 | + | |
20 | + const schemas: FormSchema[] = [ | |
21 | + { | |
22 | + field: 'field1', | |
23 | + component: 'Input', | |
24 | + label: '字段1', | |
25 | + colProps: { | |
26 | + span: 8, | |
27 | + }, | |
28 | + rules: [{ required: true, type: 'array', message: '请选择上传文件' }], | |
29 | + render: ({ model, field }) => { | |
30 | + return h(BasicUpload, { | |
31 | + value: model[field], | |
32 | + api: uploadApi, | |
33 | + onChange: (val: string[]) => { | |
34 | + model[field] = val; | |
35 | + }, | |
36 | + }); | |
37 | + }, | |
38 | + }, | |
39 | + ]; | |
11 | 40 | export default defineComponent({ |
12 | - components: { UploadContainer }, | |
41 | + components: { BasicUpload, BasicForm }, | |
13 | 42 | setup() { |
14 | - return {}; | |
43 | + const { createMessage } = useMessage(); | |
44 | + const [register] = useForm({ | |
45 | + labelWidth: 120, | |
46 | + schemas, | |
47 | + actionColOptions: { | |
48 | + span: 16, | |
49 | + }, | |
50 | + }); | |
51 | + return { | |
52 | + handleChange: (list: string[]) => { | |
53 | + createMessage.info(`已上传文件${JSON.stringify(list)}`); | |
54 | + }, | |
55 | + uploadApi, | |
56 | + register, | |
57 | + }; | |
15 | 58 | }, |
16 | 59 | }); |
17 | 60 | </script> | ... | ... |
src/views/demo/feat/download/index.vue
... | ... | @@ -8,11 +8,21 @@ |
8 | 8 | |
9 | 9 | <a-alert message="base64流下载" /> |
10 | 10 | <a-button type="primary" class="my-4" @click="handleDownloadByBase64"> base64流下载 </a-button> |
11 | + | |
12 | + <a-alert message="图片Url下载,如果有跨域问题,需要处理图片跨域" /> | |
13 | + <a-button type="primary" class="my-4" @click="handleDownloadByOnlineUrl"> | |
14 | + 图片Url下载 | |
15 | + </a-button> | |
11 | 16 | </div> |
12 | 17 | </template> |
13 | 18 | <script lang="ts"> |
14 | 19 | import { defineComponent } from 'vue'; |
15 | - import { downloadByUrl, downloadByData, downloadByBase64 } from '/@/utils/file/FileDownload'; | |
20 | + import { | |
21 | + downloadByUrl, | |
22 | + downloadByData, | |
23 | + downloadByBase64, | |
24 | + downloadByOnlineUrl, | |
25 | + } from '/@/utils/file/download'; | |
16 | 26 | import imgBase64 from './imgBase64'; |
17 | 27 | export default defineComponent({ |
18 | 28 | setup() { |
... | ... | @@ -24,15 +34,28 @@ |
24 | 34 | url: 'https://codeload.github.com/anncwb/vue-vben-admin-doc/zip/master', |
25 | 35 | target: '_self', |
26 | 36 | }); |
37 | + | |
38 | + downloadByUrl({ | |
39 | + url: 'https://vebn.oss-cn-beijing.aliyuncs.com/vben/logo.png', | |
40 | + target: '_self', | |
41 | + }); | |
27 | 42 | } |
28 | 43 | |
29 | 44 | function handleDownloadByBase64() { |
30 | 45 | downloadByBase64(imgBase64, 'logo.png'); |
31 | 46 | } |
47 | + | |
48 | + function handleDownloadByOnlineUrl() { | |
49 | + downloadByOnlineUrl( | |
50 | + 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5944817f47b8408e9f1442ece49d68ca~tplv-k3u1fbpfcp-watermark.image', | |
51 | + 'logo.png' | |
52 | + ); | |
53 | + } | |
32 | 54 | return { |
33 | 55 | handleDownloadByUrl, |
34 | 56 | handleDownByData, |
35 | 57 | handleDownloadByBase64, |
58 | + handleDownloadByOnlineUrl, | |
36 | 59 | }; |
37 | 60 | }, |
38 | 61 | }); | ... | ... |
yarn.lock
... | ... | @@ -1050,10 +1050,10 @@ |
1050 | 1050 | resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b" |
1051 | 1051 | integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw== |
1052 | 1052 | |
1053 | -"@iconify/json@^1.1.254": | |
1054 | - version "1.1.256" | |
1055 | - resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.256.tgz#0f138d421ab12faca2fdd49aaf4fbc0122db08e3" | |
1056 | - integrity sha512-CeLKbKL3lvq8afhR3LEyaBqXZDC52fgU0Ij3LbTRCwPUsumLNzhXA7MzN/f0JDYfXm9LShkfpgMcm00wQaANgg== | |
1053 | +"@iconify/json@^1.1.258": | |
1054 | + version "1.1.258" | |
1055 | + resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.258.tgz#392064ae8fd4c6d542c21bb4d0d57d5860f38abb" | |
1056 | + integrity sha512-x5DKhRrg8v1NWmClWa8zA80gWQ9xevivsUAF4s8CyAl/ZplBsEE1funKuuVcIKjexyE1UXb7uFWrUKt1fB5n1A== | |
1057 | 1057 | |
1058 | 1058 | "@koa/cors@^3.1.0": |
1059 | 1059 | version "3.1.0" |
... | ... | @@ -1316,10 +1316,10 @@ |
1316 | 1316 | "@types/qs" "*" |
1317 | 1317 | "@types/serve-static" "*" |
1318 | 1318 | |
1319 | -"@types/fs-extra@^9.0.2": | |
1320 | - version "9.0.3" | |
1321 | - resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.3.tgz#9996e5cce993508c32325380b429f04a1327523e" | |
1322 | - integrity sha512-NKdGoXLTFTRED3ENcfCsH8+ekV4gbsysanx2OPbstXVV6fZMgUCqTxubs6I9r7pbOJbFgVq1rpFtLURjKCZWUw== | |
1319 | +"@types/fs-extra@^9.0.4": | |
1320 | + version "9.0.4" | |
1321 | + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa" | |
1322 | + integrity sha512-50GO5ez44lxK5MDH90DYHFFfqxH7+fTqEEnvguQRzJ/tY9qFrMSHLiYHite+F3SNmf7+LHC1eMXojuD+E3Qcyg== | |
1323 | 1323 | dependencies: |
1324 | 1324 | "@types/node" "*" |
1325 | 1325 | |
... | ... | @@ -1725,18 +1725,18 @@ |
1725 | 1725 | vscode-languageserver-textdocument "^1.0.1" |
1726 | 1726 | vscode-uri "^2.1.2" |
1727 | 1727 | |
1728 | -"@vueuse/core@^4.0.0-beta.40": | |
1729 | - version "4.0.0-beta.40" | |
1730 | - resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.40.tgz#7efdc15c1b994647dff7ae65c0ca573d96ce9b28" | |
1731 | - integrity sha512-FOTOUrXAAp0NOmy8hMlP1HpUhnB8LeRJZDOEUl/A9gKMDwWvPTEvxKsDAIzSa4s7I0MapVzfeP3soNCNfl9+vQ== | |
1728 | +"@vueuse/core@^4.0.0-beta.41": | |
1729 | + version "4.0.0-beta.41" | |
1730 | + resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.41.tgz#0058aed5ade75ae2866283498009ad5172cbae84" | |
1731 | + integrity sha512-CgUih65PzYScorm1S4F93e6XXm+qxA8GrRLOSB1kXaqtP6vXedwkBxKkNEYNACx4reL4VEHqM/BrM6FajXkQUg== | |
1732 | 1732 | dependencies: |
1733 | - "@vueuse/shared" "4.0.0-beta.40" | |
1733 | + "@vueuse/shared" "4.0.0-beta.41" | |
1734 | 1734 | vue-demi latest |
1735 | 1735 | |
1736 | -"@vueuse/shared@4.0.0-beta.40": | |
1737 | - version "4.0.0-beta.40" | |
1738 | - resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.40.tgz#76e9b52228159e7ec88df2c8f4eea8fce1a42ec3" | |
1739 | - integrity sha512-Ay71viUTXs0XX2hQ04kEExhpsCrw3KankBMP7euorsPjuQmIZjUA4NNOb45UAudg+uF5HXLpgWLvwb4cMOLHnQ== | |
1736 | +"@vueuse/shared@4.0.0-beta.41": | |
1737 | + version "4.0.0-beta.41" | |
1738 | + resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.41.tgz#395782ea2e580f1fc9488d25c89bd09f70170b25" | |
1739 | + integrity sha512-dqnuEPPC3OUJ6L6rhMiOCuPWIR698DtdwOydwCZBISsG2V6gZ2QFND6xtRwLib6/lhUMYVYPwIz3hPjlx7BIzw== | |
1740 | 1740 | dependencies: |
1741 | 1741 | vue-demi latest |
1742 | 1742 | |
... | ... | @@ -1850,10 +1850,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: |
1850 | 1850 | dependencies: |
1851 | 1851 | color-convert "^2.0.1" |
1852 | 1852 | |
1853 | -ant-design-vue@^2.0.0-beta.15: | |
1854 | - version "2.0.0-beta.15" | |
1855 | - resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-beta.15.tgz#3c787dabb70a33885d0e751e58f9a5610ed06134" | |
1856 | - integrity sha512-OxZy+ZYU3LauIL4Rhqwy441K/iD++Cit6upnQy5+LVUrX0PSObPqPqMWVpncbAmJJYTEz88gkvgGeYqBdzouWA== | |
1853 | +ant-design-vue@^2.0.0-rc.1: | |
1854 | + version "2.0.0-rc.1" | |
1855 | + resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-rc.1.tgz#2ef02475f3aa4c1474f2fe3cf44a52c34787be02" | |
1856 | + integrity sha512-iKXkFtTHarvLHV7LWmYh6g/Cmkv+xK+vS621A1Qvg37Z6lCGg3K9BGAizmklAYzOTiPz0Ltt63eSiNqYMGh52g== | |
1857 | 1857 | dependencies: |
1858 | 1858 | "@ant-design-vue/use" "^0.0.1-0" |
1859 | 1859 | "@ant-design/icons-vue" "^5.1.5" |
... | ... | @@ -8109,10 +8109,10 @@ vary@^1.1.2: |
8109 | 8109 | resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" |
8110 | 8110 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= |
8111 | 8111 | |
8112 | -vditor@^3.6.0: | |
8113 | - version "3.6.1" | |
8114 | - resolved "https://registry.npmjs.org/vditor/-/vditor-3.6.1.tgz#b0b510f23d0cf0e5d8b3d36924e40400de96f692" | |
8115 | - integrity sha512-83GdGLIWrV1x04aK8DO9aZidqQfmuGXXUbxSCuQxRla+T9KfnFRmJwfqIxXQm8h+4jUBCFL38e8PqLa3fBOf9w== | |
8112 | +vditor@^3.6.2: | |
8113 | + version "3.6.2" | |
8114 | + resolved "https://registry.npmjs.org/vditor/-/vditor-3.6.2.tgz#ee6011efa3ec563c6356ed82efbf2e00ba2e35c6" | |
8115 | + integrity sha512-HPHHun5+IXmYGMKDWcUD83VfP1Qfncz7DmaIKoWpluJgE8ve7s+4RbFBcaEpYPXYzIuL2UTHoMnIjmTPbenOCA== | |
8116 | 8116 | dependencies: |
8117 | 8117 | diff-match-patch "^1.0.5" |
8118 | 8118 | |
... | ... | @@ -8272,10 +8272,10 @@ vue-i18n@^9.0.0-beta.6: |
8272 | 8272 | dependencies: |
8273 | 8273 | source-map "^0.6.1" |
8274 | 8274 | |
8275 | -vue-router@^4.0.0-rc.2: | |
8276 | - version "4.0.0-rc.2" | |
8277 | - resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.2.tgz#8545cab76a05ca4f6dffbe6c6a671a4dbf585ab2" | |
8278 | - integrity sha512-51mBp39rzBFpk1nyU9SkhPcwR67gBzWIH8p3pyeDmtNYgWzGF3q8MneD/xbMwsfTQkw2H1qBk6uwRaVy3M8Nxw== | |
8275 | +vue-router@^4.0.0-rc.3: | |
8276 | + version "4.0.0-rc.3" | |
8277 | + resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.3.tgz#70d18e90030bc6a25e81a30401d673223998ec6b" | |
8278 | + integrity sha512-NnPqWIfanEhJC4wu8BEFBmnEDIrx9ST0/HtmBiE+oV2MQlhyRk1TmdttWwVqx6Sh7kONsrI10GQV9l3YEkcWXg== | |
8279 | 8279 | |
8280 | 8280 | vue-types@^3.0.0: |
8281 | 8281 | version "3.0.1" | ... | ... |