Commit 661db0c767772bb7a30da9d3eeaf2b47858ccf0b

Authored by vben
1 parent a161bfa8

perf(upload): improve upload component

CHANGELOG.zh_CN.md
... ... @@ -3,6 +3,11 @@
3 3 ### ✨ Features
4 4  
5 5 - 新增 base64 文件流下载
  6 +- 优化上传组件及示例
  7 +
  8 +### 🎫 Chores
  9 +
  10 +- 更新 antdv 到`2.0.0-rc.1`
6 11  
7 12 ## 2.0.0-rc.10 (2020-11-13)
8 13  
... ...
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
... ... @@ -228,10 +228,10 @@ yarn clean:lib # 删除node_modules,兼容window系统
228 228 - [x] 系统性能优化
229 229 - [x] 全局错误处理
230 230 - [x] 富文本组件
  231 +- [x] 上传组件
231 232  
232 233 ## 正在开发的功能
233 234  
234   -- [ ] 上传组件
235 235 - [ ] 主题配置
236 236 - [ ] 黑暗主题
237 237 - [ ] 打包 CDN
... ...
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
... ... @@ -24,7 +24,7 @@ export const footerProps = {
24 24 okButtonProps: Object as PropType<any>,
25 25 okText: {
26 26 type: String as PropType<string>,
27   - default: '保存',
  27 + default: '确认',
28 28 },
29 29 okType: {
30 30 type: String as PropType<string>,
... ...
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
... ... @@ -18,7 +18,7 @@ export default defineComponent({
18 18 // icon size
19 19 size: {
20 20 type: [String, Number] as PropType<string | number>,
21   - default: 14,
  21 + default: 16,
22 22 },
23 23 prefix: {
24 24 type: String as PropType<string>,
... ...
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
... ... @@ -4,6 +4,7 @@
4 4 class="basic-table"
5 5 :class="{
6 6 'table-form-container': getBindValues.useSearchForm,
  7 + inset: getBindValues.inset,
7 8 }"
8 9 >
9 10 <BasicForm
... ...
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
... ... @@ -49,6 +49,12 @@
49 49 }
50 50 }
51 51  
  52 + &.inset {
  53 + .ant-table-wrapper {
  54 + padding: 0;
  55 + }
  56 + }
  57 +
52 58 //
53 59 .ant-table {
54 60 border: none;
... ...
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
1   -export { default as UploadContainer } from './src/UploadContainer.vue';
  1 +export { default as BasicUpload } from './src/BasicUpload.vue';
2 2 // export * from './src/types';
... ...
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
1   -import { UploadApiResult } from '/@/api/demo/model/uploadModel';
  1 +import { UploadApiResult } from '/@/api/sys/model/uploadModel';
2 2  
3 3 export enum UploadResultStatus {
4 4 SUCCESS = 'success',
... ...
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
... ... @@ -16,4 +16,4 @@
16 16 @page-loading-z-index: 10000;
17 17  
18 18 // left-menu
19   -@app-menu-item-height: 46px;
  19 +@app-menu-item-height: 44px;
... ...
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
1   -declare interface Fn<T = any> {
2   - (...arg: T[]): T;
  1 +declare interface Fn<T = any, R = T> {
  2 + (...arg: T[]): R;
  3 +}
  4 +
  5 +declare interface PromiseFn<T = any, R = T> {
  6 + (...arg: T[]): Promise<R>;
3 7 }
4 8  
5 9 // 任意对象
... ...
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
... ... @@ -38,4 +38,5 @@ export interface UploadFileParams {
38 38 file: File | Blob;
39 39 // 文件名
40 40 filename?: string;
  41 + [key: string]: any;
41 42 }
... ...
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"
... ...