优化Upload组件目录结构,以及图片上传组件 (#3241)
* fix(vxe-table): theme dark is not work * perf(ImageUpload): 优化Upload组件目录结构,以及图片上传组件
This commit is contained in:
parent
031d613b18
commit
dccc8f625d
|
|
@ -13,6 +13,5 @@ export { default as ApiTree } from './src/components/ApiTree.vue';
|
||||||
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
|
export { default as ApiRadioGroup } from './src/components/ApiRadioGroup.vue';
|
||||||
export { default as ApiCascader } from './src/components/ApiCascader.vue';
|
export { default as ApiCascader } from './src/components/ApiCascader.vue';
|
||||||
export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
|
export { default as ApiTransfer } from './src/components/ApiTransfer.vue';
|
||||||
export { default as ImageUpload } from './src/components/ImageUpload.vue';
|
|
||||||
|
|
||||||
export { BasicForm };
|
export { BasicForm };
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ import ApiTree from './components/ApiTree.vue';
|
||||||
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
import ApiTreeSelect from './components/ApiTreeSelect.vue';
|
||||||
import ApiCascader from './components/ApiCascader.vue';
|
import ApiCascader from './components/ApiCascader.vue';
|
||||||
import ApiTransfer from './components/ApiTransfer.vue';
|
import ApiTransfer from './components/ApiTransfer.vue';
|
||||||
import ImageUpload from './components/ImageUpload.vue';
|
import { BasicUpload, ImageUpload } from '/@/components/Upload';
|
||||||
import { BasicUpload } from '/@/components/Upload';
|
|
||||||
import { StrengthMeter } from '/@/components/StrengthMeter';
|
import { StrengthMeter } from '/@/components/StrengthMeter';
|
||||||
import { IconPicker } from '/@/components/Icon';
|
import { IconPicker } from '/@/components/Icon';
|
||||||
import { CountdownInput } from '/@/components/CountDown';
|
import { CountdownInput } from '/@/components/CountDown';
|
||||||
|
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="clearfix">
|
|
||||||
<a-upload
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
:list-type="listType"
|
|
||||||
:multiple="multiple"
|
|
||||||
:max-count="maxCount"
|
|
||||||
:customRequest="handleCustomRequest"
|
|
||||||
:before-upload="handleBeforeUpload"
|
|
||||||
v-bind="$attrs"
|
|
||||||
@preview="handlePreview"
|
|
||||||
v-model:value="state"
|
|
||||||
>
|
|
||||||
<div v-if="fileList.length < maxCount">
|
|
||||||
<plus-outlined />
|
|
||||||
<div style="margin-top: 8px">
|
|
||||||
{{ t('component.upload.upload') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a-upload>
|
|
||||||
<a-modal :open="previewOpen" :footer="null" @cancel="handleCancel">
|
|
||||||
<img alt="example" style="width: 100%" :src="previewImage" />
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, PropType, reactive, ref, watch } from 'vue';
|
|
||||||
import { message, Modal, Upload, UploadProps } from 'ant-design-vue';
|
|
||||||
import { UploadFile } from 'ant-design-vue/lib/upload/interface';
|
|
||||||
import { useI18n } from '@/hooks/web/useI18n';
|
|
||||||
import { join } from 'lodash-es';
|
|
||||||
import { buildShortUUID } from '@/utils/uuid';
|
|
||||||
import { isArray, isNotEmpty, isUrl } from '@/utils/is';
|
|
||||||
import { useRuleFormItem } from '@/hooks/component/useFormItem';
|
|
||||||
import { useAttrs } from '@vben/hooks';
|
|
||||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
|
||||||
|
|
||||||
type ImageUploadType = 'text' | 'picture' | 'picture-card';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ImageUpload',
|
|
||||||
components: {
|
|
||||||
PlusOutlined,
|
|
||||||
AUpload: Upload,
|
|
||||||
AModal: Modal,
|
|
||||||
},
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
value: [Array, String],
|
|
||||||
api: {
|
|
||||||
type: Function as PropType<(file: UploadFile) => Promise<string>>,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
listType: {
|
|
||||||
type: String as PropType<ImageUploadType>,
|
|
||||||
default: () => 'picture-card',
|
|
||||||
},
|
|
||||||
// 文件类型
|
|
||||||
fileType: {
|
|
||||||
type: Array,
|
|
||||||
default: () => ['image/png', 'image/jpeg'],
|
|
||||||
},
|
|
||||||
multiple: {
|
|
||||||
type: Boolean,
|
|
||||||
default: () => false,
|
|
||||||
},
|
|
||||||
// 最大数量的文件
|
|
||||||
maxCount: {
|
|
||||||
type: Number,
|
|
||||||
default: () => 1,
|
|
||||||
},
|
|
||||||
// 最小数量的文件
|
|
||||||
minCount: {
|
|
||||||
type: Number,
|
|
||||||
default: () => 0,
|
|
||||||
},
|
|
||||||
// 文件最大多少MB
|
|
||||||
maxSize: {
|
|
||||||
type: Number,
|
|
||||||
default: () => 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
emits: ['change', 'update:value'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const attrs = useAttrs();
|
|
||||||
const { t } = useI18n();
|
|
||||||
const previewOpen = ref(false);
|
|
||||||
const previewImage = ref('');
|
|
||||||
const emitData = ref<any[] | any | undefined>();
|
|
||||||
const fileList = ref<UploadFile[]>([]);
|
|
||||||
|
|
||||||
// Embedded in the form, just use the hook binding to perform form verification
|
|
||||||
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
|
|
||||||
|
|
||||||
const fileState = reactive<{
|
|
||||||
newList: any[];
|
|
||||||
newStr: string;
|
|
||||||
oldStr: string;
|
|
||||||
}>({
|
|
||||||
newList: [],
|
|
||||||
newStr: '',
|
|
||||||
oldStr: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => fileList.value,
|
|
||||||
(v) => {
|
|
||||||
fileState.newList = v
|
|
||||||
.filter((item: any) => {
|
|
||||||
return item?.url && item.status === 'done' && isUrl(item?.url);
|
|
||||||
})
|
|
||||||
.map((item: any) => item?.url);
|
|
||||||
fileState.newStr = join(fileState.newList);
|
|
||||||
// 不相等代表数据变更
|
|
||||||
if (fileState.newStr !== fileState.oldStr) {
|
|
||||||
fileState.oldStr = fileState.newStr;
|
|
||||||
emitData.value = props.multiple ? fileState.newList : fileState.newStr;
|
|
||||||
state.value = props.multiple ? fileState.newList : fileState.newStr;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => state.value,
|
|
||||||
(v) => {
|
|
||||||
changeFileValue(v);
|
|
||||||
emit('update:value', v);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function changeFileValue(value: any) {
|
|
||||||
const stateStr = props.multiple ? join((value as string[]) || []) : value || '';
|
|
||||||
if (stateStr !== fileState.oldStr) {
|
|
||||||
fileState.oldStr = stateStr;
|
|
||||||
let list: string[] = [];
|
|
||||||
if (props.multiple) {
|
|
||||||
if (isNotEmpty(value)) {
|
|
||||||
if (isArray(value)) {
|
|
||||||
list = value as string[];
|
|
||||||
} else {
|
|
||||||
list.push(value as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isNotEmpty(value)) {
|
|
||||||
list.push(value as string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileList.value = list.map((item) => {
|
|
||||||
const uuid = buildShortUUID();
|
|
||||||
return {
|
|
||||||
uid: uuid,
|
|
||||||
name: uuid,
|
|
||||||
status: 'done',
|
|
||||||
url: item,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 关闭查看 */
|
|
||||||
const handleCancel = () => {
|
|
||||||
previewOpen.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 查看图片 */
|
|
||||||
// @ts-ignore
|
|
||||||
const handlePreview = async (file: UploadProps['fileList'][number]) => {
|
|
||||||
if (!file.url && !file.preview) {
|
|
||||||
file.preview = (await getBase64(file.originFileObj)) as string;
|
|
||||||
}
|
|
||||||
previewImage.value = file.url || file.preview;
|
|
||||||
previewOpen.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 上传前校验 */
|
|
||||||
const handleBeforeUpload: UploadProps['beforeUpload'] = (file) => {
|
|
||||||
if (fileList.value.length > props.maxCount) {
|
|
||||||
fileList.value.splice(props.maxCount, fileList.value.length - props.maxCount);
|
|
||||||
message.error(t('component.upload.maxNumber', [props.maxCount]));
|
|
||||||
return Upload.LIST_IGNORE;
|
|
||||||
}
|
|
||||||
const isPNG = props.fileType.includes(file.type);
|
|
||||||
if (!isPNG) {
|
|
||||||
message.error(t('component.upload.acceptUpload', [props.fileType.toString()]));
|
|
||||||
}
|
|
||||||
const isLt2M = file.size / 1024 / 1024 < props.maxSize;
|
|
||||||
if (!isLt2M) {
|
|
||||||
message.error(t('component.upload.maxSizeMultiple', [props.maxSize]));
|
|
||||||
}
|
|
||||||
if (!(isPNG && isLt2M)) {
|
|
||||||
fileList.value.pop();
|
|
||||||
}
|
|
||||||
return (isPNG && isLt2M) || Upload.LIST_IGNORE;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 自定义上传 */
|
|
||||||
const handleCustomRequest = async (option: any) => {
|
|
||||||
const { file } = option;
|
|
||||||
await props
|
|
||||||
.api(option)
|
|
||||||
.then((url) => {
|
|
||||||
file.url = url;
|
|
||||||
file.status = 'done';
|
|
||||||
fileList.value.pop();
|
|
||||||
fileList.value.push(file);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
fileList.value.pop();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function getBase64(file: File) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
reader.onload = () => resolve(reader.result);
|
|
||||||
reader.onerror = (error) => reject(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
previewOpen,
|
|
||||||
fileList,
|
|
||||||
state,
|
|
||||||
attrs,
|
|
||||||
t,
|
|
||||||
handlePreview,
|
|
||||||
handleBeforeUpload,
|
|
||||||
handleCustomRequest,
|
|
||||||
handleCancel,
|
|
||||||
previewImage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* you can make up upload button and sample style by using stylesheets */
|
|
||||||
.ant-upload-select-picture-card i {
|
|
||||||
color: #999;
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-upload-select-picture-card .ant-upload-text {
|
|
||||||
margin-top: 8px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 96px;
|
width: 96px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
line-height: 56px;
|
line-height: 56px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-confirm-body {
|
&-confirm-body {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { withInstall } from '/@/utils';
|
import { withInstall } from '/@/utils';
|
||||||
import basicUpload from './src/BasicUpload.vue';
|
import basicUpload from './src/BasicUpload.vue';
|
||||||
|
import uploadImage from './src/components/ImageUpload.vue';
|
||||||
|
|
||||||
|
export const ImageUpload = withInstall(uploadImage);
|
||||||
export const BasicUpload = withInstall(basicUpload);
|
export const BasicUpload = withInstall(basicUpload);
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,8 @@
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
import { isArray } from '/@/utils/is';
|
import { isArray } from '/@/utils/is';
|
||||||
import UploadModal from './UploadModal.vue';
|
import UploadModal from './components/UploadModal.vue';
|
||||||
import UploadPreviewModal from './UploadPreviewModal.vue';
|
import UploadPreviewModal from './components/UploadPreviewModal.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BasicUpload',
|
name: 'BasicUpload',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { fileListProps } from './props';
|
import { fileListProps } from '../props';
|
||||||
import { isFunction, isDef } from '/@/utils/is';
|
import { isFunction, isDef } from '/@/utils/is';
|
||||||
import { useSortable } from '/@/hooks/web/useSortable';
|
import { useSortable } from '/@/hooks/web/useSortable';
|
||||||
import { useModalContext } from '/@/components/Modal/src/hooks/useModalContext';
|
import { useModalContext } from '/@/components/Modal/src/hooks/useModalContext';
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Upload
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
:list-type="listType"
|
||||||
|
:accept="getStringAccept"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
@preview="handlePreview"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<div v-if="fileList && fileList.length < maxNumber">
|
||||||
|
<plus-outlined />
|
||||||
|
<div style="margin-top: 8px">{{ t('component.upload.upload') }}</div>
|
||||||
|
</div>
|
||||||
|
</Upload>
|
||||||
|
<Modal :open="previewOpen" :title="previewTitle" :footer="null" @cancel="handleCancel">
|
||||||
|
<img alt="" style="width: 100%" :src="previewImage" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup name="ImageUpload">
|
||||||
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { Upload, Modal } from 'ant-design-vue';
|
||||||
|
import type { UploadProps } from 'ant-design-vue';
|
||||||
|
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||||
|
import { useMessage } from '@/hooks/web/useMessage';
|
||||||
|
import { isArray, isFunction } from '@/utils/is';
|
||||||
|
import { warn } from '@/utils/log';
|
||||||
|
import { useI18n } from '@/hooks/web/useI18n';
|
||||||
|
import { useUploadType } from '../hooks/useUpload';
|
||||||
|
import { uploadContainerProps } from '../props';
|
||||||
|
import { isImgTypeByName } from '../helper';
|
||||||
|
|
||||||
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
|
const props = defineProps({
|
||||||
|
...uploadContainerProps,
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { createMessage } = useMessage();
|
||||||
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
|
const { getStringAccept } = useUploadType({
|
||||||
|
acceptRef: accept,
|
||||||
|
helpTextRef: helpText,
|
||||||
|
maxNumberRef: maxNumber,
|
||||||
|
maxSizeRef: maxSize,
|
||||||
|
});
|
||||||
|
const previewOpen = ref<boolean>(false);
|
||||||
|
const previewImage = ref<string>('');
|
||||||
|
const previewTitle = ref<string>('');
|
||||||
|
|
||||||
|
const fileList = ref<UploadProps['fileList']>([]);
|
||||||
|
const isLtMsg = ref<boolean>(true);
|
||||||
|
const isActMsg = ref<boolean>(true);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(v) => {
|
||||||
|
if (isArray(v)) {
|
||||||
|
fileList.value = v.map((url, i) => ({
|
||||||
|
uid: String(-i),
|
||||||
|
name: url ? url.substring(url.lastIndexOf('/') + 1) : 'image.png',
|
||||||
|
status: 'done',
|
||||||
|
url,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function getBase64(file: File) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.onload = () => resolve(reader.result);
|
||||||
|
reader.onerror = (error) => reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePreview = async (file: UploadProps['fileList'][number]) => {
|
||||||
|
if (!file.url && !file.preview) {
|
||||||
|
file.preview = (await getBase64(file.originFileObj)) as string;
|
||||||
|
}
|
||||||
|
previewImage.value = file.url || file.preview;
|
||||||
|
previewOpen.value = true;
|
||||||
|
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = async (file: UploadProps['fileList'][number]) => {
|
||||||
|
if (fileList.value) {
|
||||||
|
const index = fileList.value.findIndex((item: any) => item.uuid === file.uuid);
|
||||||
|
index !== -1 && fileList.value.splice(index, 1);
|
||||||
|
emit('change', fileList.value);
|
||||||
|
emit('delete', file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
previewOpen.value = false;
|
||||||
|
previewTitle.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
const { maxSize, accept } = props;
|
||||||
|
const { name } = file;
|
||||||
|
isActMsg.value = isImgTypeByName(name);
|
||||||
|
if (!isActMsg.value) {
|
||||||
|
createMessage.error(t('component.upload.acceptUpload', [accept]));
|
||||||
|
isActMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
isLtMsg.value = file.size / 1024 / 1024 > maxSize;
|
||||||
|
if (isLtMsg.value) {
|
||||||
|
createMessage.error(t('component.upload.maxSizeMultiple', [maxSize]));
|
||||||
|
isLtMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
return (isActMsg.value && !isLtMsg.value) || Upload.LIST_IGNORE;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
|
const { api } = props;
|
||||||
|
if (!api || !isFunction(api)) {
|
||||||
|
return warn('upload api must exist and be a function');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await props.api?.({
|
||||||
|
data: {
|
||||||
|
...(props.uploadParams || {}),
|
||||||
|
},
|
||||||
|
file: info.file,
|
||||||
|
name: props.name,
|
||||||
|
filename: props.filename,
|
||||||
|
});
|
||||||
|
info.onSuccess!(res.data);
|
||||||
|
emit('change', fileList.value);
|
||||||
|
} catch (e: any) {
|
||||||
|
info.onError!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.ant-upload-select-picture-card i {
|
||||||
|
color: #999;
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-select-picture-card .ant-upload-text {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -53,14 +53,14 @@
|
||||||
import { Upload, Alert } from 'ant-design-vue';
|
import { Upload, Alert } from 'ant-design-vue';
|
||||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||||
// hooks
|
// hooks
|
||||||
import { useUploadType } from './useUpload';
|
import { useUploadType } from '../hooks/useUpload';
|
||||||
import { useMessage } from '/@/hooks/web/useMessage';
|
import { useMessage } from '/@/hooks/web/useMessage';
|
||||||
// types
|
// types
|
||||||
import { FileItem, UploadResultStatus } from './typing';
|
import { FileItem, UploadResultStatus } from '../types/typing';
|
||||||
import { basicProps } from './props';
|
import { basicProps } from '../props';
|
||||||
import { createTableColumns, createActionColumn } from './data';
|
import { createTableColumns, createActionColumn } from './data';
|
||||||
// utils
|
// utils
|
||||||
import { checkImgType, getBase64WithFile } from './helper';
|
import { checkImgType, getBase64WithFile } from '../helper';
|
||||||
import { buildUUID } from '/@/utils/uuid';
|
import { buildUUID } from '/@/utils/uuid';
|
||||||
import { isFunction } from '/@/utils/is';
|
import { isFunction } from '/@/utils/is';
|
||||||
import { warn } from '/@/utils/log';
|
import { warn } from '/@/utils/log';
|
||||||
|
|
@ -193,7 +193,7 @@
|
||||||
);
|
);
|
||||||
const { data } = ret;
|
const { data } = ret;
|
||||||
item.status = UploadResultStatus.SUCCESS;
|
item.status = UploadResultStatus.SUCCESS;
|
||||||
item.responseData = data;
|
item.response = data;
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
@ -247,9 +247,9 @@
|
||||||
const fileList: string[] = [];
|
const fileList: string[] = [];
|
||||||
|
|
||||||
for (const item of fileListRef.value) {
|
for (const item of fileListRef.value) {
|
||||||
const { status, responseData } = item;
|
const { status, response } = item;
|
||||||
if (status === UploadResultStatus.SUCCESS && responseData) {
|
if (status === UploadResultStatus.SUCCESS && response) {
|
||||||
fileList.push(responseData.url);
|
fileList.push(response.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 存在一个上传成功的即可保存
|
// 存在一个上传成功的即可保存
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
import { defineComponent, watch, ref } from 'vue';
|
import { defineComponent, watch, ref } from 'vue';
|
||||||
import FileList from './FileList.vue';
|
import FileList from './FileList.vue';
|
||||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||||
import { previewProps } from './props';
|
import { previewProps } from '../props';
|
||||||
import { PreviewFileItem } from './typing';
|
import { PreviewFileItem } from '../types/typing';
|
||||||
import { downloadByUrl } from '/@/utils/file/download';
|
import { downloadByUrl } from '/@/utils/file/download';
|
||||||
import { createPreviewColumns, createPreviewActionColumn } from './data';
|
import { createPreviewColumns, createPreviewActionColumn } from './data';
|
||||||
import { useI18n } from '/@/hooks/web/useI18n';
|
import { useI18n } from '/@/hooks/web/useI18n';
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { BasicColumn, ActionItem } from '/@/components/Table';
|
import type { BasicColumn, ActionItem } from '/@/components/Table';
|
||||||
import { FileBasicColumn, FileItem, PreviewFileItem, UploadResultStatus } from './typing';
|
import { FileBasicColumn, FileItem, PreviewFileItem, UploadResultStatus } from '../types/typing';
|
||||||
import { isImgTypeByName } from './helper';
|
import { isImgTypeByName } from '../helper';
|
||||||
import { Progress, Tag } from 'ant-design-vue';
|
import { Progress, Tag } from 'ant-design-vue';
|
||||||
import TableAction from '/@/components/Table/src/components/TableAction.vue';
|
import TableAction from '/@/components/Table/src/components/TableAction.vue';
|
||||||
import ThumbUrl from './ThumbUrl.vue';
|
import ThumbUrl from './ThumbUrl.vue';
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { FileBasicColumn } from './typing';
|
import { FileBasicColumn } from './types/typing';
|
||||||
|
|
||||||
import type { Options } from 'sortablejs';
|
import type { Options } from 'sortablejs';
|
||||||
|
|
||||||
|
|
@ -13,7 +13,13 @@ type SortableOptions = Merge<
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
type ListType = 'text' | 'picture' | 'picture-card';
|
||||||
|
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
|
listType: {
|
||||||
|
type: String as PropType<ListType>,
|
||||||
|
default: 'picture-card',
|
||||||
|
},
|
||||||
helpText: {
|
helpText: {
|
||||||
type: String as PropType<string>,
|
type: String as PropType<string>,
|
||||||
default: '',
|
default: '',
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BasicColumn } from '../../Table';
|
import { BasicColumn } from '/@/components/Table';
|
||||||
import { UploadApiResult } from '/@/api/sys/model/uploadModel';
|
import { UploadApiResult } from '/@/api/sys/model/uploadModel';
|
||||||
|
|
||||||
export enum UploadResultStatus {
|
export enum UploadResultStatus {
|
||||||
|
|
@ -15,7 +15,7 @@ export interface FileItem {
|
||||||
percent: number;
|
percent: number;
|
||||||
file: File;
|
file: File;
|
||||||
status?: UploadResultStatus;
|
status?: UploadResultStatus;
|
||||||
responseData?: UploadApiResult;
|
response?: UploadApiResult;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import { areaRecord } from '/@/api/demo/cascader';
|
import { areaRecord } from '/@/api/demo/cascader';
|
||||||
import { uploadApi } from '/@/api/sys/upload';
|
import { uploadApi } from '/@/api/sys/upload';
|
||||||
|
// import { isArray } from '/@/utils/is';
|
||||||
|
|
||||||
const valueSelectA = ref<string[]>([]);
|
const valueSelectA = ref<string[]>([]);
|
||||||
const valueSelectB = ref<string[]>([]);
|
const valueSelectB = ref<string[]>([]);
|
||||||
|
|
@ -743,13 +744,30 @@
|
||||||
{
|
{
|
||||||
field: 'field23',
|
field: 'field23',
|
||||||
component: 'ImageUpload',
|
component: 'ImageUpload',
|
||||||
label: '字段23',
|
label: '上传图片',
|
||||||
colProps: {
|
required: true,
|
||||||
span: 8,
|
defaultValue: [
|
||||||
},
|
'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||||
|
],
|
||||||
componentProps: {
|
componentProps: {
|
||||||
api: () => Promise.resolve('https://via.placeholder.com/600/92c952'),
|
api: uploadApi,
|
||||||
|
accept: ['png', 'jpeg', 'jpg'],
|
||||||
|
maxSize: 2,
|
||||||
|
maxNumber: 1,
|
||||||
},
|
},
|
||||||
|
// rules: [
|
||||||
|
// {
|
||||||
|
// required: true,
|
||||||
|
// trigger: 'change',
|
||||||
|
// validator(_, value) {
|
||||||
|
// if (isArray(value) && value.length > 0) {
|
||||||
|
// return Promise.resolve();
|
||||||
|
// } else {
|
||||||
|
// return Promise.reject('请选择上传图片');
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue