perf(upload): improve upload component

This commit is contained in:
vben 2020-11-15 13:22:34 +08:00
parent a161bfa818
commit 661db0c767
39 changed files with 484 additions and 236 deletions

View File

@ -3,6 +3,11 @@
### ✨ Features ### ✨ Features
- 新增 base64 文件流下载 - 新增 base64 文件流下载
- 优化上传组件及示例
### 🎫 Chores
- 更新 antdv 到`2.0.0-rc.1`
## 2.0.0-rc.10 (2020-11-13) ## 2.0.0-rc.10 (2020-11-13)

View File

@ -226,10 +226,10 @@ yarn clean:lib # Delete node_modules, supported window
- [x] Data import and export - [x] Data import and export
- [x] Global error handling - [x] Global error handling
- [x] Rich text component - [x] Rich text component
- [x] Upload component
## Developing features ## Developing features
- [ ] Upload component
- [ ] Theme configuration - [ ] Theme configuration
- [ ] Dark theme - [ ] Dark theme
- [ ] Build CDN - [ ] Build CDN

View File

@ -228,10 +228,10 @@ yarn clean:lib # 删除node_modules兼容window系统
- [x] 系统性能优化 - [x] 系统性能优化
- [x] 全局错误处理 - [x] 全局错误处理
- [x] 富文本组件 - [x] 富文本组件
- [x] 上传组件
## 正在开发的功能 ## 正在开发的功能
- [ ] 上传组件
- [ ] 主题配置 - [ ] 主题配置
- [ ] 黑暗主题 - [ ] 黑暗主题
- [ ] 打包 CDN - [ ] 打包 CDN

View File

@ -22,8 +22,8 @@
}, },
"dependencies": { "dependencies": {
"@iconify/iconify": "^2.0.0-rc.2", "@iconify/iconify": "^2.0.0-rc.2",
"@vueuse/core": "^4.0.0-beta.40", "@vueuse/core": "^4.0.0-beta.41",
"ant-design-vue": "^2.0.0-beta.15", "ant-design-vue": "^2.0.0-rc.1",
"apexcharts": "3.22.0", "apexcharts": "3.22.0",
"axios": "^0.21.0", "axios": "^0.21.0",
"echarts": "^4.9.0", "echarts": "^4.9.0",
@ -33,10 +33,10 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"vditor": "^3.6.0", "vditor": "^3.6.2",
"vue": "^3.0.2", "vue": "^3.0.2",
"vue-i18n": "^9.0.0-beta.6", "vue-i18n": "^9.0.0-beta.6",
"vue-router": "^4.0.0-rc.2", "vue-router": "^4.0.0-rc.3",
"vuex": "^4.0.0-rc.1", "vuex": "^4.0.0-rc.1",
"vuex-module-decorators": "^1.0.1", "vuex-module-decorators": "^1.0.1",
"xlsx": "^0.16.8", "xlsx": "^0.16.8",
@ -45,11 +45,11 @@
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^11.0.0", "@commitlint/cli": "^11.0.0",
"@commitlint/config-conventional": "^11.0.0", "@commitlint/config-conventional": "^11.0.0",
"@iconify/json": "^1.1.254", "@iconify/json": "^1.1.258",
"@ls-lint/ls-lint": "^1.9.2", "@ls-lint/ls-lint": "^1.9.2",
"@purge-icons/generated": "^0.4.1", "@purge-icons/generated": "^0.4.1",
"@types/echarts": "^4.9.0", "@types/echarts": "^4.9.0",
"@types/fs-extra": "^9.0.2", "@types/fs-extra": "^9.0.4",
"@types/koa-static": "^4.0.1", "@types/koa-static": "^4.0.1",
"@types/lodash-es": "^4.17.3", "@types/lodash-es": "^4.17.3",
"@types/mockjs": "^1.0.3", "@types/mockjs": "^1.0.3",

View File

@ -24,7 +24,7 @@ export const footerProps = {
okButtonProps: Object as PropType<any>, okButtonProps: Object as PropType<any>,
okText: { okText: {
type: String as PropType<string>, type: String as PropType<string>,
default: '保存', default: '确认',
}, },
okType: { okType: {
type: String as PropType<string>, type: String as PropType<string>,

View File

@ -44,7 +44,6 @@
import { useFormValues } from './hooks/useFormValues'; import { useFormValues } from './hooks/useFormValues';
import useAdvanced from './hooks/useAdvanced'; import useAdvanced from './hooks/useAdvanced';
import { useFormAction } from './hooks/useFormAction'; import { useFormAction } from './hooks/useFormAction';
export default defineComponent({ export default defineComponent({
name: 'BasicForm', name: 'BasicForm',
components: { FormItem, Form, Row, FormAction }, components: { FormItem, Form, Row, FormAction },

View File

@ -18,7 +18,7 @@ export default defineComponent({
// icon size // icon size
size: { size: {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: 14, default: 16,
}, },
prefix: { prefix: {
type: String as PropType<string>, type: String as PropType<string>,

View File

@ -1,4 +1,5 @@
import type { PropType } from 'vue'; import type { PropType } from 'vue';
import { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
export const modalProps = { export const modalProps = {
visible: Boolean as PropType<boolean>, visible: Boolean as PropType<boolean>,
// open drag // open drag
@ -16,7 +17,7 @@ export const modalProps = {
}, },
okText: { okText: {
type: String as PropType<string>, type: String as PropType<string>,
default: '保存', default: '确认',
}, },
closeFunc: Function as PropType<() => Promise<boolean>>, closeFunc: Function as PropType<() => Promise<boolean>>,
}; };
@ -100,9 +101,9 @@ export const basicProps = Object.assign({}, modalProps, {
default: 'primary', default: 'primary',
}, },
okButtonProps: Object as PropType<any>, okButtonProps: Object as PropType<ButtonProps>,
cancelButtonProps: Object as PropType<any>, cancelButtonProps: Object as PropType<ButtonProps>,
title: { title: {
type: String as PropType<string>, type: String as PropType<string>,

View File

@ -7,7 +7,7 @@
import { defineComponent, watchEffect, PropType, ref, unref } from 'vue'; import { defineComponent, watchEffect, PropType, ref, unref } from 'vue';
import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus'; import { toCanvas, QRCodeRenderersOptions, LogoType } from './qrcodePlus';
import { toDataURL } from 'qrcode'; import { toDataURL } from 'qrcode';
import { downloadByUrl } from '/@/utils/file/FileDownload'; import { downloadByUrl } from '/@/utils/file/download';
export default defineComponent({ export default defineComponent({
name: 'QrCode', name: 'QrCode',

View File

@ -4,6 +4,7 @@
class="basic-table" class="basic-table"
:class="{ :class="{
'table-form-container': getBindValues.useSearchForm, 'table-form-container': getBindValues.useSearchForm,
inset: getBindValues.inset,
}" }"
> >
<BasicForm <BasicForm

View File

@ -84,7 +84,7 @@ export function useDataSource(
const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref( const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm } = unref(
propsRef propsRef
); );
if (!api && !isFunction(api)) return; if (!api || !isFunction(api)) return;
try { try {
loadingRef.value = true; loadingRef.value = true;
const { pageField, sizeField, listField, totalField } = fetchSetting || FETCH_SETTING; const { pageField, sizeField, listField, totalField } = fetchSetting || FETCH_SETTING;

View File

@ -16,7 +16,10 @@ export const basicProps = {
tableSetting: { tableSetting: {
type: Object as PropType<TableSetting>, type: Object as PropType<TableSetting>,
}, },
inset: {
type: Boolean as PropType<boolean>,
default: false,
},
sortFn: { sortFn: {
type: Function as PropType<(sortInfo: SorterResult) => any>, type: Function as PropType<(sortInfo: SorterResult) => any>,
default: DEFAULT_SORT_FN, default: DEFAULT_SORT_FN,

View File

@ -49,6 +49,12 @@
} }
} }
&.inset {
.ant-table-wrapper {
padding: 0;
}
}
// //
.ant-table { .ant-table {
border: none; border: none;

View File

@ -126,6 +126,8 @@ export interface TableSetting {
export interface BasicTableProps<T = any> { export interface BasicTableProps<T = any> {
// 自定义排序方法 // 自定义排序方法
sortFn?: (sortInfo: SorterResult) => any; sortFn?: (sortInfo: SorterResult) => any;
// 取消表格的默认padding
inset?: boolean;
// 显示表格设置 // 显示表格设置
showTableSetting?: boolean; showTableSetting?: boolean;
tableSetting?: TableSetting; tableSetting?: TableSetting;

View File

@ -1,2 +1,2 @@
export { default as UploadContainer } from './src/UploadContainer.vue'; export { default as BasicUpload } from './src/BasicUpload.vue';
// export * from './src/types'; // export * from './src/types';

View File

@ -0,0 +1,99 @@
<template>
<div>
<a-button-group>
<a-button type="primary" @click="openUploadModal" preIcon="ant-design:cloud-upload-outlined">
上传
</a-button>
<Tooltip placement="bottom" v-if="showPreview">
<template #title>
已上传
<template v-if="fileListRef.length">{{ fileListRef.length }}</template>
</template>
<a-button @click="openPreviewModal">
<Icon icon="ant-design:eye-outlined" />
<template v-if="fileListRef.length && showPreviewNumber">
{{ fileListRef.length }}
</template>
</a-button>
</Tooltip>
</a-button-group>
<UploadModal v-bind="bindValue" @register="registerUploadModal" @change="handleChange" />
<UploadPreviewModal
:value="fileListRef"
@register="registerPreviewModal"
@list-change="handlePreviewChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, unref, computed } from 'vue';
import UploadModal from './UploadModal.vue';
import UploadPreviewModal from './UploadPreviewModal.vue';
import Icon from '/@/components/Icon';
import { Tooltip } from 'ant-design-vue';
import { useModal } from '/@/components/Modal';
import { uploadContainerProps } from './props';
import { omit } from 'lodash-es';
export default defineComponent({
components: { UploadModal, UploadPreviewModal, Icon, Tooltip },
props: uploadContainerProps,
setup(props, { emit, attrs }) {
// modal
const [registerUploadModal, { openModal: openUploadModal }] = useModal();
// modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileListRef = ref<string[]>([]);
const showPreview = computed(() => {
const { emptyHidePreview } = props;
if (!emptyHidePreview) return true;
return emptyHidePreview ? fileListRef.value.length > 0 : true;
});
const bindValue = computed(() => {
const value = { ...attrs, ...props };
return omit(value, 'onChange');
});
watch(
() => props.value,
(value = []) => {
fileListRef.value = value;
},
{ immediate: true }
);
// modal
function handleChange(urls: string[]) {
fileListRef.value = [...unref(fileListRef), ...(urls || [])];
emit('change', fileListRef.value);
}
// modal
function handlePreviewChange(urls: string[]) {
fileListRef.value = [...(urls || [])];
emit('change', fileListRef.value);
}
return {
registerUploadModal,
openUploadModal,
handleChange,
handlePreviewChange,
registerPreviewModal,
openPreviewModal,
fileListRef,
showPreview,
bindValue,
};
},
});
</script>

View File

@ -5,25 +5,22 @@
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, PropType } from 'vue';
export default defineComponent({ export default defineComponent({
props: { props: {
fileUrl: { fileUrl: {
type: String, type: String as PropType<string>,
default: '', default: '',
}, },
fileType: { fileType: {
type: String, type: String as PropType<string>,
default: '', default: '',
}, },
fileName: { fileName: {
type: String, type: String as PropType<string>,
default: '', default: '',
}, },
}, },
setup() {
return {};
},
}); });
</script> </script>

View File

@ -1,62 +0,0 @@
<template>
<div>
<a-button-group>
<a-button type="primary" @click="openUploadModal">上传</a-button>
<a-button @click="openPreviewModal">
<Icon icon="ant-design:eye-outlined" />
</a-button>
</a-button-group>
<UploadModal v-bind="$props" @register="registerUploadModal" @change="handleChange" />
<UploadPreviewModal
:value="fileListRef"
@register="registerPreviewModal"
@change="handlePreviewChange"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watch, unref } from 'vue';
import { useModal } from '/@/components/Modal';
import UploadModal from './UploadModal.vue';
import { uploadContainerProps } from './props';
import UploadPreviewModal from './UploadPreviewModal.vue';
import Icon from '/@/components/Icon/index';
export default defineComponent({
components: { UploadModal, UploadPreviewModal, Icon },
props: uploadContainerProps,
setup(props, { emit }) {
// modal
const [registerUploadModal, { openModal: openUploadModal }] = useModal();
// modal
const [registerPreviewModal, { openModal: openPreviewModal }] = useModal();
const fileListRef = ref<string[]>([]);
watch(
() => props.value,
(value) => {
fileListRef.value = [...(value || [])];
},
{ immediate: true }
);
// modal
function handleChange(urls: string[]) {
fileListRef.value = [...unref(fileListRef), ...(urls || [])];
emit('change', fileListRef.value);
}
// modal
function handlePreviewChange(urls: string[]) {
fileListRef.value = [...(urls || [])];
emit('change', fileListRef.value);
}
return {
registerUploadModal,
openUploadModal,
handleChange,
handlePreviewChange,
registerPreviewModal,
openPreviewModal,
fileListRef,
};
},
});
</script>

View File

@ -1,31 +1,44 @@
<template> <template>
<BasicModal <BasicModal
width="800px"
title="上传"
okText="保存"
v-bind="$attrs" v-bind="$attrs"
@register="register" @register="register"
@ok="handleOk" @ok="handleOk"
:closeFunc="handleCloseFunc" :closeFunc="handleCloseFunc"
:maskClosable="false" :maskClosable="false"
width="800px" :keyboard="false"
title="上传组件"
wrapClassName="upload-modal" wrapClassName="upload-modal"
:okButtonProps="{ disabled: isUploadingRef }" :okButtonProps="getOkButtonProps"
:cancelButtonProps="{ disabled: isUploadingRef }" :cancelButtonProps="{ disabled: isUploadingRef }"
> >
<template #centerdFooter> <template #centerdFooter>
<a-button @click="handleStartUpload" color="success" :loading="isUploadingRef"> <a-button
{{ isUploadingRef ? '上传中' : '开始上传' }} @click="handleStartUpload"
color="success"
:disabled="!getIsSelectFile"
:loading="isUploadingRef"
>
{{ getUploadBtnText }}
</a-button> </a-button>
</template> </template>
<Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload">
<a-button type="primary"> 选择文件 </a-button> <BasicTable @register="registerTable" :dataSource="fileListRef">
<span class="px-2">{{ getHelpText }}</span> <template #toolbar>
</Upload> <Upload :accept="getStringAccept" :multiple="multiple" :before-upload="beforeUpload">
<BasicTable @register="registerTable" :dataSource="fileListRef" /> <a-button type="primary"> 选择文件 </a-button>
</Upload>
</template>
<template #tableTitle>
<Alert :message="getHelpText" type="info" banner></Alert>
</template>
</BasicTable>
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, reactive, ref, toRef, unref } from 'vue'; import { defineComponent, reactive, ref, toRefs, unref, computed } from 'vue';
import { Upload } from 'ant-design-vue'; import { Upload, Alert } from 'ant-design-vue';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicTable, useTable } from '/@/components/Table'; import { BasicTable, useTable } from '/@/components/Table';
// hooks // hooks
@ -39,23 +52,56 @@
import { checkFileType, checkImgType, getBase64WithFile } from './utils'; import { checkFileType, checkImgType, getBase64WithFile } from './utils';
import { buildUUID } from '/@/utils/uuid'; import { buildUUID } from '/@/utils/uuid';
import { createImgPreview } from '/@/components/Preview/index'; import { createImgPreview } from '/@/components/Preview/index';
import { uploadApi } from '/@/api/demo/upload'; import { uploadApi } from '/@/api/sys/upload';
import { isFunction } from '/@/utils/is';
import { warn } from '/@/utils/log';
export default defineComponent({ export default defineComponent({
components: { BasicModal, Upload, BasicTable }, components: { BasicModal, Upload, BasicTable, Alert },
props: basicProps, props: basicProps,
setup(props, { emit }) { setup(props, { emit }) {
const [register, { closeModal }] = useModalInner(); //
const { getAccept, getStringAccept, getHelpText } = useUploadType({ const isUploadingRef = ref(false);
acceptRef: toRef(props, 'accept'), const fileListRef = ref<FileItem[]>([]);
helpTextRef: toRef(props, 'helpText'), const state = reactive<{ fileList: FileItem[] }>({
maxNumberRef: toRef(props, 'maxNumber'), fileList: [],
maxSizeRef: toRef(props, 'maxSize'), });
const [register, { closeModal }] = useModalInner();
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
const { getAccept, getStringAccept, getHelpText } = useUploadType({
acceptRef: accept,
helpTextRef: helpText,
maxNumberRef: maxNumber,
maxSizeRef: maxSize,
}); });
const fileListRef = ref<FileItem[]>([]);
const state = reactive<{ fileList: FileItem[] }>({ fileList: [] });
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const getIsSelectFile = computed(() => {
return (
fileListRef.value.length > 0 &&
!fileListRef.value.every((item) => item.status === UploadResultStatus.SUCCESS)
);
});
const getOkButtonProps = computed(() => {
const someSuccess = fileListRef.value.some(
(item) => item.status === UploadResultStatus.SUCCESS
);
return {
disabled: isUploadingRef.value || fileListRef.value.length === 0 || !someSuccess,
};
});
const getUploadBtnText = computed(() => {
const someError = fileListRef.value.some(
(item) => item.status === UploadResultStatus.ERROR
);
return isUploadingRef.value ? '上传中' : someError ? '重新上传失败文件' : '开始上传';
});
// //
function beforeUpload(file: File) { function beforeUpload(file: File) {
const { size, name } = file; const { size, name } = file;
@ -73,6 +119,14 @@
createMessage.error!(`只能上传${accept.join(',')}格式文件`); createMessage.error!(`只能上传${accept.join(',')}格式文件`);
return false; return false;
} }
const commonItem = {
uuid: buildUUID(),
file,
size,
name,
percent: 0,
type: name.split('.').pop(),
};
// //
if (checkImgType(file)) { if (checkImgType(file)) {
// beforeUpload // beforeUpload
@ -81,29 +135,13 @@
fileListRef.value = [ fileListRef.value = [
...unref(fileListRef), ...unref(fileListRef),
{ {
uuid: buildUUID(),
file,
thumbUrl, thumbUrl,
size, ...commonItem,
name,
percent: 0,
type: name.split('.').pop(),
}, },
]; ];
}); });
} else { } else {
fileListRef.value = [ fileListRef.value = [...unref(fileListRef), commonItem];
...unref(fileListRef),
{
uuid: buildUUID(),
file,
size,
name,
percent: 0,
type: name.split('.').pop(),
},
];
} }
return false; return false;
} }
@ -112,6 +150,7 @@
const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid); const index = fileListRef.value.findIndex((item) => item.uuid === record.uuid);
index !== -1 && fileListRef.value.splice(index, 1); index !== -1 && fileListRef.value.splice(index, 1);
} }
// //
function handlePreview(record: FileItem) { function handlePreview(record: FileItem) {
const { thumbUrl = '' } = record; const { thumbUrl = '' } = record;
@ -119,19 +158,18 @@
imageList: [thumbUrl], imageList: [thumbUrl],
}); });
} }
const [registerTable] = useTable({
columns: createTableColumns(),
actionColumn: createActionColumn(handleRemove, handlePreview),
pagination: false,
});
//
const isUploadingRef = ref(false);
async function uploadApiByItem(item: FileItem) { async function uploadApiByItem(item: FileItem) {
const { api } = props;
if (!api || !isFunction(api)) {
return warn('upload api must exist and be a function');
}
try { try {
item.status = UploadResultStatus.UPLOADING; item.status = UploadResultStatus.UPLOADING;
const { data } = await uploadApi( const { data } = await uploadApi(
{ {
...(props.uploadParams || {}),
file: item.file, file: item.file,
}, },
function onUploadProgress(progressEvent: ProgressEvent) { function onUploadProgress(progressEvent: ProgressEvent) {
@ -154,32 +192,42 @@
}; };
} }
} }
// //
async function handleStartUpload() { async function handleStartUpload() {
const { maxNumber } = props;
if (fileListRef.value.length > maxNumber) {
return createMessage.warning(`最多只能上传${maxNumber}个文件`);
}
try { try {
isUploadingRef.value = true; isUploadingRef.value = true;
//
const uploadFileList =
fileListRef.value.filter((item) => item.status !== UploadResultStatus.SUCCESS) || [];
const data = await Promise.all( const data = await Promise.all(
unref(fileListRef).map((item) => { uploadFileList.map((item) => {
return uploadApiByItem(item); return uploadApiByItem(item);
}) })
); );
isUploadingRef.value = false; isUploadingRef.value = false;
// : // :
const errorList = data.filter((item) => !item.success); const errorList = data.filter((item: any) => !item.success);
if (errorList.length > 0) { if (errorList.length > 0) throw errorList;
throw errorList;
}
} catch (e) { } catch (e) {
isUploadingRef.value = false; isUploadingRef.value = false;
throw e; throw e;
} }
} }
// //
function handleOk() { function handleOk() {
// TODO okButtonProps={{ disabled: state.isUploading }} const { maxNumber } = props;
if (fileListRef.value.length > maxNumber) {
return createMessage.warning(`最多只能上传${maxNumber}个文件`);
}
if (isUploadingRef.value) { if (isUploadingRef.value) {
createMessage.warning('请等待文件上传后,保存'); return createMessage.warning('请等待文件上传后,保存');
return;
} }
const fileList: string[] = []; const fileList: string[] = [];
@ -189,18 +237,15 @@
fileList.push(responseData.url); fileList.push(responseData.url);
} }
} }
// //
if (fileList.length <= 0) { if (fileList.length <= 0) {
createMessage.warning('没有上传成功的文件,无法保存'); return createMessage.warning('没有上传成功的文件,无法保存');
return;
} }
console.log(fileList);
emit('change', fileList);
fileListRef.value = []; fileListRef.value = [];
closeModal(); closeModal();
emit('change', fileList);
} }
// //
function handleCloseFunc() { function handleCloseFunc() {
if (!isUploadingRef.value) { if (!isUploadingRef.value) {
@ -211,11 +256,22 @@
return false; return false;
} }
} }
const [registerTable] = useTable({
columns: createTableColumns(),
actionColumn: createActionColumn(handleRemove, handlePreview),
pagination: false,
inset: true,
scroll: {
y: 3000,
},
});
return { return {
register, register,
closeModal, closeModal,
getHelpText, getHelpText,
getStringAccept, getStringAccept,
getOkButtonProps,
beforeUpload, beforeUpload,
registerTable, registerTable,
fileListRef, fileListRef,
@ -224,14 +280,13 @@
handleStartUpload, handleStartUpload,
handleOk, handleOk,
handleCloseFunc, handleCloseFunc,
getIsSelectFile,
getUploadBtnText,
}; };
}, },
}); });
</script> </script>
<style lang="less"> <style lang="less">
// /deep/ .ant-upload-list {
// display: none;
// }
.upload-modal { .upload-modal {
.ant-upload-list { .ant-upload-list {
display: none; display: none;

View File

@ -1,10 +1,10 @@
<template> <template>
<BasicModal <BasicModal
width="800px"
title="预览"
wrapClassName="upload-preview-modal" wrapClassName="upload-preview-modal"
v-bind="$attrs" v-bind="$attrs"
width="800px"
@register="register" @register="register"
title="预览"
:showOkBtn="false" :showOkBtn="false"
> >
<BasicTable @register="registerTable" :dataSource="fileListRef" /> <BasicTable @register="registerTable" :dataSource="fileListRef" />
@ -12,17 +12,18 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, watch, ref, unref } from 'vue'; import { defineComponent, watch, ref, unref } from 'vue';
import { BasicTable, useTable } from '/@/components/Table'; import { BasicTable, useTable } from '/@/components/Table';
import { createPreviewColumns, createPreviewActionColumn } from './data';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, useModalInner } from '/@/components/Modal';
import { priviewProps } from './props'; import { previewProps } from './props';
import { PreviewFileItem } from './types'; import { PreviewFileItem } from './types';
import { createImgPreview } from '/@/components/Preview/index'; import { createImgPreview } from '/@/components/Preview/index';
import { downloadByUrl } from '/@/utils/file/FileDownload'; import { downloadByUrl } from '/@/utils/file/download';
import { createPreviewColumns, createPreviewActionColumn } from './data';
export default defineComponent({ export default defineComponent({
components: { BasicModal, BasicTable }, components: { BasicModal, BasicTable },
props: priviewProps, props: previewProps,
setup(props, { emit }) { setup(props, { emit }) {
const [register, { closeModal }] = useModalInner(); const [register, { closeModal }] = useModalInner();
const fileListRef = ref<PreviewFileItem[]>([]); const fileListRef = ref<PreviewFileItem[]>([]);
@ -43,17 +44,19 @@
}, },
{ immediate: true } { immediate: true }
); );
// //
function handleRemove(record: PreviewFileItem) { function handleRemove(record: PreviewFileItem) {
const index = fileListRef.value.findIndex((item) => item.url === record.url); const index = fileListRef.value.findIndex((item) => item.url === record.url);
if (index !== -1) { if (index !== -1) {
fileListRef.value.splice(index, 1); fileListRef.value.splice(index, 1);
emit( emit(
'change', 'list-change',
fileListRef.value.map((item) => item.url) fileListRef.value.map((item) => item.url)
); );
} }
} }
// //
function handlePreview(record: PreviewFileItem) { function handlePreview(record: PreviewFileItem) {
const { url = '' } = record; const { url = '' } = record;
@ -61,16 +64,19 @@
imageList: [url], imageList: [url],
}); });
} }
// //
function handleDownload(record: PreviewFileItem) { function handleDownload(record: PreviewFileItem) {
const { url = '' } = record; const { url = '' } = record;
downloadByUrl({ url }); downloadByUrl({ url });
} }
const [registerTable] = useTable({ const [registerTable] = useTable({
columns: createPreviewColumns(), columns: createPreviewColumns(),
pagination: false, pagination: false,
actionColumn: createPreviewActionColumn({ handleRemove, handlePreview, handleDownload }), actionColumn: createPreviewActionColumn({ handleRemove, handlePreview, handleDownload }),
}); });
return { return {
register, register,
closeModal, closeModal,

View File

@ -1,10 +1,6 @@
// import { BasicColumn, TableAction, ActionItem } from '@/components/table';
import { checkImgType, isImgTypeByName } from './utils'; import { checkImgType, isImgTypeByName } from './utils';
// import ThumnUrl from './ThumbUrl.vue'; import { Progress, Tag } from 'ant-design-vue';
import { Progress } from 'ant-design-vue';
import { FileItem, PreviewFileItem, UploadResultStatus } from './types'; import { FileItem, PreviewFileItem, UploadResultStatus } from './types';
// import { ElecArchivesSaveResult } from '@/api/biz/file/model/fileModel';
// import { quryFile } from '@/api/biz/file/file';
import { BasicColumn, ActionItem, TableAction } from '/@/components/Table/index'; import { BasicColumn, ActionItem, TableAction } from '/@/components/Table/index';
// 文件上传列表 // 文件上传列表
@ -16,8 +12,7 @@ export function createTableColumns(): BasicColumn[] {
width: 100, width: 100,
customRender: ({ record }) => { customRender: ({ record }) => {
const { thumbUrl, type } = (record as FileItem) || {}; const { thumbUrl, type } = (record as FileItem) || {};
return <span>{thumbUrl ? <img src={thumbUrl} style={{ width: '50px' }} /> : type}</span>; return <span>{thumbUrl ? <img style={{ maxWidth: '60px' }} src={thumbUrl} /> : type}</span>;
// return <ThumnUrl fileUrl={thumbUrl} fileType={type} fileName={type} />;
}, },
}, },
{ {
@ -26,7 +21,7 @@ export function createTableColumns(): BasicColumn[] {
align: 'left', align: 'left',
customRender: ({ text, record }) => { customRender: ({ text, record }) => {
const { percent, status: uploadStatus } = (record as FileItem) || {}; const { percent, status: uploadStatus } = (record as FileItem) || {};
let status = 'normal'; let status: 'normal' | 'exception' | 'active' | 'success' = 'normal';
if (uploadStatus === UploadResultStatus.ERROR) { if (uploadStatus === UploadResultStatus.ERROR) {
status = 'exception'; status = 'exception';
} else if (uploadStatus === UploadResultStatus.UPLOADING) { } else if (uploadStatus === UploadResultStatus.UPLOADING) {
@ -63,11 +58,11 @@ export function createTableColumns(): BasicColumn[] {
width: 100, width: 100,
customRender: ({ text }) => { customRender: ({ text }) => {
if (text === UploadResultStatus.SUCCESS) { if (text === UploadResultStatus.SUCCESS) {
return '上传成功'; return <Tag color="green">{() => '上传成功'}</Tag>;
} else if (text === UploadResultStatus.ERROR) { } else if (text === UploadResultStatus.ERROR) {
return '上传失败'; return <Tag color="red">{() => '上传失败'}</Tag>;
} else if (text === UploadResultStatus.UPLOADING) { } else if (text === UploadResultStatus.UPLOADING) {
return '上传中'; return <Tag color="blue">{() => '上传中'}</Tag>;
} }
return text; return text;
@ -85,6 +80,7 @@ export function createActionColumn(handleRemove: Function, handlePreview: Functi
const actions: ActionItem[] = [ const actions: ActionItem[] = [
{ {
label: '删除', label: '删除',
color: 'error',
onClick: handleRemove.bind(null, record), onClick: handleRemove.bind(null, record),
}, },
]; ];
@ -125,9 +121,9 @@ export function createPreviewActionColumn({
handlePreview, handlePreview,
handleDownload, handleDownload,
}: { }: {
handleRemove: Function; handleRemove: Fn;
handlePreview: Function; handlePreview: Fn;
handleDownload: Function; handleDownload: Fn;
}): BasicColumn { }): BasicColumn {
return { return {
width: 160, width: 160,
@ -135,11 +131,12 @@ export function createPreviewActionColumn({
dataIndex: 'action', dataIndex: 'action',
fixed: false, fixed: false,
customRender: ({ record }) => { customRender: ({ record }) => {
const { url } = (record as PreviewFileItem) || {}; const { url } = (record || {}) as PreviewFileItem;
const actions: ActionItem[] = [ const actions: ActionItem[] = [
{ {
label: '删除', label: '删除',
color: 'error',
onClick: handleRemove.bind(null, record), onClick: handleRemove.bind(null, record),
}, },
{ {

View File

@ -10,10 +10,10 @@ export const basicProps = {
type: Number as PropType<number>, type: Number as PropType<number>,
default: 2, default: 2,
}, },
// 最大数量的文件,0不限制 // 最大数量的文件,Infinity不限制
maxNumber: { maxNumber: {
type: Number as PropType<number>, type: Number as PropType<number>,
default: 0, default: Infinity,
}, },
// 根据后缀,或者其他 // 根据后缀,或者其他
accept: { accept: {
@ -21,9 +21,18 @@ export const basicProps = {
default: () => [], default: () => [],
}, },
multiple: { multiple: {
type: Boolean, type: Boolean as PropType<boolean>,
default: true, default: true,
}, },
uploadParams: {
type: Object as PropType<any>,
default: {},
},
api: {
type: Function as PropType<PromiseFn>,
default: null,
required: true,
},
}; };
export const uploadContainerProps = { export const uploadContainerProps = {
@ -32,9 +41,17 @@ export const uploadContainerProps = {
default: () => [], default: () => [],
}, },
...basicProps, ...basicProps,
showPreviewNumber: {
type: Boolean as PropType<boolean>,
default: true,
},
emptyHidePreview: {
type: Boolean as PropType<boolean>,
default: false,
},
}; };
export const priviewProps = { export const previewProps = {
value: { value: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => [],

View File

@ -1,4 +1,4 @@
import { UploadApiResult } from '/@/api/demo/model/uploadModel'; import { UploadApiResult } from '/@/api/sys/model/uploadModel';
export enum UploadResultStatus { export enum UploadResultStatus {
SUCCESS = 'success', SUCCESS = 'success',

View File

@ -42,12 +42,12 @@ export function useUploadType({
const maxSize = unref(maxSizeRef); const maxSize = unref(maxSizeRef);
if (maxSize) { if (maxSize) {
helpTexts.push(`不超过${maxSize}MB`); helpTexts.push(`单个文件不超过${maxSize}MB`);
} }
const maxNumber = unref(maxNumberRef); const maxNumber = unref(maxNumberRef);
if (maxNumber) { if (maxNumber && maxNumber !== Infinity) {
helpTexts.push(`最多可选择${maxNumber}个文件`); helpTexts.push(`最多只能上传${maxNumber}个文件`);
} }
return helpTexts.join(''); return helpTexts.join('');
}); });

View File

@ -3,18 +3,17 @@ export function checkFileType(file: File, accepts: string[]) {
// const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i; // const reg = /\.(jpg|jpeg|png|gif|txt|doc|docx|xls|xlsx|xml)$/i;
const reg = new RegExp('\\.(' + newTypes + ')$', 'i'); const reg = new RegExp('\\.(' + newTypes + ')$', 'i');
if (!reg.test(file.name)) { return reg.test(file.name);
return false;
} else {
return true;
}
} }
export function checkImgType(file: File) { export function checkImgType(file: File) {
return /\.(jpg|jpeg|png|gif)$/i.test(file.name); return isImgTypeByName(file.name);
} }
export function isImgTypeByName(name: string) { export function isImgTypeByName(name: string) {
return /\.(jpg|jpeg|png|gif)$/i.test(name); return /\.(jpg|jpeg|png|gif)$/i.test(name);
} }
export function getBase64WithFile(file: File) { export function getBase64WithFile(file: File) {
return new Promise<{ return new Promise<{
result: string; result: string;

View File

@ -6,6 +6,11 @@
// &.ant-btn-primary:not(.ant-btn-link) { // &.ant-btn-primary:not(.ant-btn-link) {
// box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08) !important; // box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08) !important;
// } // }
// &-group {
// .ant-btn:not(:first-child) {
// bottom: 1px;
// }
// }
&-primary { &-primary {
color: @white; color: @white;

View File

@ -16,4 +16,4 @@
@page-loading-z-index: 10000; @page-loading-z-index: 10000;
// left-menu // left-menu
@app-menu-item-height: 46px; @app-menu-item-height: 44px;

View File

@ -4,6 +4,9 @@ const menu: MenuModule = {
menu: { menu: {
name: '组件', name: '组件',
path: '/comp', path: '/comp',
tag: {
dot: true,
},
children: [ children: [
{ {
path: 'basic', path: 'basic',
@ -38,10 +41,13 @@ const menu: MenuModule = {
path: 'strength-meter', path: 'strength-meter',
name: '密码强度组件', name: '密码强度组件',
}, },
// { {
// path: 'upload', path: 'upload',
// name: '上传组件', name: '上传组件',
// }, tag: {
content: 'new',
},
},
{ {
path: 'scroll', path: 'scroll',
name: '滚动组件', name: '滚动组件',

View File

@ -1,5 +1,9 @@
declare interface Fn<T = any> { declare interface Fn<T = any, R = T> {
(...arg: T[]): T; (...arg: T[]): R;
}
declare interface PromiseFn<T = any, R = T> {
(...arg: T[]): Promise<R>;
} }
// 任意对象 // 任意对象

View File

@ -1,5 +1,25 @@
import { dataURLtoBlob } from './stream'; import { dataURLtoBlob, urlToBase64 } from './stream';
/**
* Download online pictures
* @param url
* @param filename
* @param mime
* @param bom
*/
export function downloadByOnlineUrl(url: string, filename: string, mime?: string, bom?: BlobPart) {
urlToBase64(url).then((base64) => {
downloadByBase64(base64, filename, mime, bom);
});
}
/**
* Download pictures based on base64
* @param buf
* @param filename
* @param mime
* @param bom
*/
export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) { export function downloadByBase64(buf: string, filename: string, mime?: string, bom?: BlobPart) {
const base64Buf = dataURLtoBlob(buf); const base64Buf = dataURLtoBlob(buf);
downloadByData(base64Buf, filename, mime, bom); downloadByData(base64Buf, filename, mime, bom);

View File

@ -13,3 +13,29 @@ export function dataURLtoBlob(base64Buf: string): Blob {
} }
return new Blob([u8arr], { type: mime }); return new Blob([u8arr], { type: mime });
} }
/**
* img url to base64
* @param url
*/
export function urlToBase64(url: string, mineType?: string): Promise<string> {
return new Promise((resolve, reject) => {
let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>;
const ctx = canvas!.getContext('2d');
const img = new Image();
img.crossOrigin = '';
img.onload = function () {
if (!canvas || !ctx) {
return reject();
}
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL(mineType || 'image/png');
canvas = null;
resolve(dataURL);
};
img.src = url;
});
}

View File

@ -118,11 +118,8 @@ export class VAxios {
Object.keys(params.data).forEach((key) => { Object.keys(params.data).forEach((key) => {
if (!params.data) return; if (!params.data) return;
const value = params.data[key]; const value = params.data[key];
// support key-value array data
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((item) => { value.forEach((item) => {
// { list: [ 11, 22 ] }
// formData.append('list[]', 11);
formData.append(`${key}[]`, item); formData.append(`${key}[]`, item);
}); });
return; return;

View File

@ -160,20 +160,18 @@ const transform: AxiosTransform = {
try { try {
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
createMessage.error('接口请求超时,请刷新页面重试!'); createMessage.error('接口请求超时,请刷新页面重试!');
return;
} }
if (err && err.includes('Network Error')) { if (err && err.includes('Network Error')) {
createErrorModal({ createErrorModal({
title: '网络异常', title: '网络异常',
content: '请检查您的网络连接是否正常!', content: '请检查您的网络连接是否正常!',
}); });
return;
} }
} catch (error) { } catch (error) {
throw new Error(error); throw new Error(error);
} }
checkStatus(error.response && error.response.status, msg); checkStatus(error.response && error.response.status, msg);
return error; return Promise.reject(error);
}, },
}; };

View File

@ -38,4 +38,5 @@ export interface UploadFileParams {
file: File | Blob; file: File | Blob;
// 文件名 // 文件名
filename?: string; filename?: string;
[key: string]: any;
} }

View File

@ -1,17 +1,60 @@
<template> <template>
<div class="p-4"> <div class="p-4">
<UploadContainer :maxSize="5" /> <a-alert message="基础示例" class="my-5"></a-alert>
<BasicUpload :maxSize="20" :maxNumber="10" @change="handleChange" :api="uploadApi" />
<a-alert message="嵌入表单,加入表单校验" class="my-5"></a-alert>
<BasicForm @register="register" />
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, h } from 'vue';
import { UploadContainer } from '/@/components/Upload/index'; import { BasicUpload } from '/@/components/Upload';
import { useMessage } from '/@/hooks/web/useMessage';
import { BasicForm, FormSchema, useForm } from '/@/components/Form/index';
import { uploadApi } from '/@/api/sys/upload';
// import { Alert } from 'ant-design-vue'; // import { Alert } from 'ant-design-vue';
const schemas: FormSchema[] = [
{
field: 'field1',
component: 'Input',
label: '字段1',
colProps: {
span: 8,
},
rules: [{ required: true, type: 'array', message: '请选择上传文件' }],
render: ({ model, field }) => {
return h(BasicUpload, {
value: model[field],
api: uploadApi,
onChange: (val: string[]) => {
model[field] = val;
},
});
},
},
];
export default defineComponent({ export default defineComponent({
components: { UploadContainer }, components: { BasicUpload, BasicForm },
setup() { setup() {
return {}; const { createMessage } = useMessage();
const [register] = useForm({
labelWidth: 120,
schemas,
actionColOptions: {
span: 16,
},
});
return {
handleChange: (list: string[]) => {
createMessage.info(`已上传文件${JSON.stringify(list)}`);
},
uploadApi,
register,
};
}, },
}); });
</script> </script>

View File

@ -8,11 +8,21 @@
<a-alert message="base64流下载" /> <a-alert message="base64流下载" />
<a-button type="primary" class="my-4" @click="handleDownloadByBase64"> base64流下载 </a-button> <a-button type="primary" class="my-4" @click="handleDownloadByBase64"> base64流下载 </a-button>
<a-alert message="图片Url下载,如果有跨域问题,需要处理图片跨域" />
<a-button type="primary" class="my-4" @click="handleDownloadByOnlineUrl">
图片Url下载
</a-button>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { downloadByUrl, downloadByData, downloadByBase64 } from '/@/utils/file/FileDownload'; import {
downloadByUrl,
downloadByData,
downloadByBase64,
downloadByOnlineUrl,
} from '/@/utils/file/download';
import imgBase64 from './imgBase64'; import imgBase64 from './imgBase64';
export default defineComponent({ export default defineComponent({
setup() { setup() {
@ -24,15 +34,28 @@
url: 'https://codeload.github.com/anncwb/vue-vben-admin-doc/zip/master', url: 'https://codeload.github.com/anncwb/vue-vben-admin-doc/zip/master',
target: '_self', target: '_self',
}); });
downloadByUrl({
url: 'https://vebn.oss-cn-beijing.aliyuncs.com/vben/logo.png',
target: '_self',
});
} }
function handleDownloadByBase64() { function handleDownloadByBase64() {
downloadByBase64(imgBase64, 'logo.png'); downloadByBase64(imgBase64, 'logo.png');
} }
function handleDownloadByOnlineUrl() {
downloadByOnlineUrl(
'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5944817f47b8408e9f1442ece49d68ca~tplv-k3u1fbpfcp-watermark.image',
'logo.png'
);
}
return { return {
handleDownloadByUrl, handleDownloadByUrl,
handleDownByData, handleDownByData,
handleDownloadByBase64, handleDownloadByBase64,
handleDownloadByOnlineUrl,
}; };
}, },
}); });

View File

@ -1050,10 +1050,10 @@
resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b" resolved "https://registry.npmjs.org/@iconify/iconify/-/iconify-2.0.0-rc.2.tgz#c4a95ddc06ca9b9496df03604e66fdefb39f4c4b"
integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw== integrity sha512-BybEHU5/I9EQ0CcwKAqmreZ2bMnAXrqLCTptAc6vPetHMbrXdZfejP5mt57e/8PNSt/qE7BHniU5PCYA+PGIHw==
"@iconify/json@^1.1.254": "@iconify/json@^1.1.258":
version "1.1.256" version "1.1.258"
resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.256.tgz#0f138d421ab12faca2fdd49aaf4fbc0122db08e3" resolved "https://registry.npmjs.org/@iconify/json/-/json-1.1.258.tgz#392064ae8fd4c6d542c21bb4d0d57d5860f38abb"
integrity sha512-CeLKbKL3lvq8afhR3LEyaBqXZDC52fgU0Ij3LbTRCwPUsumLNzhXA7MzN/f0JDYfXm9LShkfpgMcm00wQaANgg== integrity sha512-x5DKhRrg8v1NWmClWa8zA80gWQ9xevivsUAF4s8CyAl/ZplBsEE1funKuuVcIKjexyE1UXb7uFWrUKt1fB5n1A==
"@koa/cors@^3.1.0": "@koa/cors@^3.1.0":
version "3.1.0" version "3.1.0"
@ -1316,10 +1316,10 @@
"@types/qs" "*" "@types/qs" "*"
"@types/serve-static" "*" "@types/serve-static" "*"
"@types/fs-extra@^9.0.2": "@types/fs-extra@^9.0.4":
version "9.0.3" version "9.0.4"
resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.3.tgz#9996e5cce993508c32325380b429f04a1327523e" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.4.tgz#12553138cf0438db9a31cdc8b0a3aa9332eb67aa"
integrity sha512-NKdGoXLTFTRED3ENcfCsH8+ekV4gbsysanx2OPbstXVV6fZMgUCqTxubs6I9r7pbOJbFgVq1rpFtLURjKCZWUw== integrity sha512-50GO5ez44lxK5MDH90DYHFFfqxH7+fTqEEnvguQRzJ/tY9qFrMSHLiYHite+F3SNmf7+LHC1eMXojuD+E3Qcyg==
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
@ -1725,18 +1725,18 @@
vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-textdocument "^1.0.1"
vscode-uri "^2.1.2" vscode-uri "^2.1.2"
"@vueuse/core@^4.0.0-beta.40": "@vueuse/core@^4.0.0-beta.41":
version "4.0.0-beta.40" version "4.0.0-beta.41"
resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.40.tgz#7efdc15c1b994647dff7ae65c0ca573d96ce9b28" resolved "https://registry.npmjs.org/@vueuse/core/-/core-4.0.0-beta.41.tgz#0058aed5ade75ae2866283498009ad5172cbae84"
integrity sha512-FOTOUrXAAp0NOmy8hMlP1HpUhnB8LeRJZDOEUl/A9gKMDwWvPTEvxKsDAIzSa4s7I0MapVzfeP3soNCNfl9+vQ== integrity sha512-CgUih65PzYScorm1S4F93e6XXm+qxA8GrRLOSB1kXaqtP6vXedwkBxKkNEYNACx4reL4VEHqM/BrM6FajXkQUg==
dependencies: dependencies:
"@vueuse/shared" "4.0.0-beta.40" "@vueuse/shared" "4.0.0-beta.41"
vue-demi latest vue-demi latest
"@vueuse/shared@4.0.0-beta.40": "@vueuse/shared@4.0.0-beta.41":
version "4.0.0-beta.40" version "4.0.0-beta.41"
resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.40.tgz#76e9b52228159e7ec88df2c8f4eea8fce1a42ec3" resolved "https://registry.npmjs.org/@vueuse/shared/-/shared-4.0.0-beta.41.tgz#395782ea2e580f1fc9488d25c89bd09f70170b25"
integrity sha512-Ay71viUTXs0XX2hQ04kEExhpsCrw3KankBMP7euorsPjuQmIZjUA4NNOb45UAudg+uF5HXLpgWLvwb4cMOLHnQ== integrity sha512-dqnuEPPC3OUJ6L6rhMiOCuPWIR698DtdwOydwCZBISsG2V6gZ2QFND6xtRwLib6/lhUMYVYPwIz3hPjlx7BIzw==
dependencies: dependencies:
vue-demi latest vue-demi latest
@ -1850,10 +1850,10 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies: dependencies:
color-convert "^2.0.1" color-convert "^2.0.1"
ant-design-vue@^2.0.0-beta.15: ant-design-vue@^2.0.0-rc.1:
version "2.0.0-beta.15" version "2.0.0-rc.1"
resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-beta.15.tgz#3c787dabb70a33885d0e751e58f9a5610ed06134" resolved "https://registry.npmjs.org/ant-design-vue/-/ant-design-vue-2.0.0-rc.1.tgz#2ef02475f3aa4c1474f2fe3cf44a52c34787be02"
integrity sha512-OxZy+ZYU3LauIL4Rhqwy441K/iD++Cit6upnQy5+LVUrX0PSObPqPqMWVpncbAmJJYTEz88gkvgGeYqBdzouWA== integrity sha512-iKXkFtTHarvLHV7LWmYh6g/Cmkv+xK+vS621A1Qvg37Z6lCGg3K9BGAizmklAYzOTiPz0Ltt63eSiNqYMGh52g==
dependencies: dependencies:
"@ant-design-vue/use" "^0.0.1-0" "@ant-design-vue/use" "^0.0.1-0"
"@ant-design/icons-vue" "^5.1.5" "@ant-design/icons-vue" "^5.1.5"
@ -8109,10 +8109,10 @@ vary@^1.1.2:
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
vditor@^3.6.0: vditor@^3.6.2:
version "3.6.1" version "3.6.2"
resolved "https://registry.npmjs.org/vditor/-/vditor-3.6.1.tgz#b0b510f23d0cf0e5d8b3d36924e40400de96f692" resolved "https://registry.npmjs.org/vditor/-/vditor-3.6.2.tgz#ee6011efa3ec563c6356ed82efbf2e00ba2e35c6"
integrity sha512-83GdGLIWrV1x04aK8DO9aZidqQfmuGXXUbxSCuQxRla+T9KfnFRmJwfqIxXQm8h+4jUBCFL38e8PqLa3fBOf9w== integrity sha512-HPHHun5+IXmYGMKDWcUD83VfP1Qfncz7DmaIKoWpluJgE8ve7s+4RbFBcaEpYPXYzIuL2UTHoMnIjmTPbenOCA==
dependencies: dependencies:
diff-match-patch "^1.0.5" diff-match-patch "^1.0.5"
@ -8272,10 +8272,10 @@ vue-i18n@^9.0.0-beta.6:
dependencies: dependencies:
source-map "^0.6.1" source-map "^0.6.1"
vue-router@^4.0.0-rc.2: vue-router@^4.0.0-rc.3:
version "4.0.0-rc.2" version "4.0.0-rc.3"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.2.tgz#8545cab76a05ca4f6dffbe6c6a671a4dbf585ab2" resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.0.0-rc.3.tgz#70d18e90030bc6a25e81a30401d673223998ec6b"
integrity sha512-51mBp39rzBFpk1nyU9SkhPcwR67gBzWIH8p3pyeDmtNYgWzGF3q8MneD/xbMwsfTQkw2H1qBk6uwRaVy3M8Nxw== integrity sha512-NnPqWIfanEhJC4wu8BEFBmnEDIrx9ST0/HtmBiE+oV2MQlhyRk1TmdttWwVqx6Sh7kONsrI10GQV9l3YEkcWXg==
vue-types@^3.0.0: vue-types@^3.0.0:
version "3.0.1" version "3.0.1"