feat(feature):upload->preview & upload->resultfield (#3707)

* feat(components->upload->resultField): upload和imageupload组件中添加resultField与demo

* feat(components->upload->preview): upload组件中添加自定义预览组件的方法与demo
This commit is contained in:
Electrolux 2024-03-31 07:02:02 +08:00 committed by GitHub
parent b5c87cf6ab
commit b28a46edcb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 259 additions and 26 deletions

View File

@ -34,6 +34,8 @@
@register="registerPreviewModal" @register="registerPreviewModal"
@list-change="handlePreviewChange" @list-change="handlePreviewChange"
@delete="handlePreviewDelete" @delete="handlePreviewDelete"
v-bind:preview-columns="props.previewColumns"
v-bind:before-preview-data="props.beforePreviewData"
/> />
</div> </div>
</template> </template>

View File

@ -4,6 +4,7 @@
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';
import { defineComponent, CSSProperties, watch, nextTick, ref, onMounted } from 'vue'; import { defineComponent, CSSProperties, watch, nextTick, ref, onMounted } from 'vue';
import { FileBasicColumn } from '../types/typing';
export default defineComponent({ export default defineComponent({
name: 'FileList', name: 'FileList',
@ -51,7 +52,9 @@
return () => { return () => {
const { columns, actionColumn, dataSource } = props; const { columns, actionColumn, dataSource } = props;
const columnList = [...columns, actionColumn]; let columnList: FileBasicColumn[];
columnList = (actionColumn ? [...columns, actionColumn] : [...columns]) as FileBasicColumn[];
return ( return (
// x scrollbar // x scrollbar
<div class="overflow-x-auto"> <div class="overflow-x-auto">

View File

@ -37,12 +37,12 @@
import { uploadContainerProps } from '../props'; import { uploadContainerProps } from '../props';
import { isImgTypeByName } from '../helper'; import { isImgTypeByName } from '../helper';
import { UploadResultStatus } from '@/components/Upload/src/types/typing'; import { UploadResultStatus } from '@/components/Upload/src/types/typing';
import { get,omit } from 'lodash-es';
defineOptions({ name: 'ImageUpload' }); defineOptions({ name: 'ImageUpload' });
const emit = defineEmits(['change', 'update:value', 'delete']); const emit = defineEmits(['change', 'update:value', 'delete']);
const props = defineProps({ const props = defineProps({
...uploadContainerProps, ...omit(uploadContainerProps,["previewColumns","beforePreviewData"]),
}); });
const { t } = useI18n(); const { t } = useI18n();
const { createMessage } = useMessage(); const { createMessage } = useMessage();
@ -170,7 +170,12 @@
name: props.name, name: props.name,
filename: props.filename, filename: props.filename,
}); });
if(props.resultField){
info.onSuccess!(res);
}else{
// resultField
info.onSuccess!(res.data); info.onSuccess!(res.data);
}
const value = getValue(); const value = getValue();
isInnerOperate.value = true; isInnerOperate.value = true;
emit('update:value', value); emit('update:value', value);
@ -185,6 +190,9 @@
const list = (fileList.value || []) const list = (fileList.value || [])
.filter((item) => item?.status === UploadResultStatus.DONE) .filter((item) => item?.status === UploadResultStatus.DONE)
.map((item: any) => { .map((item: any) => {
if(props.resultField){
return get(item?.response, props.resultField)
}
return item?.url || item?.response?.url; return item?.url || item?.response?.url;
}); });
return props.multiple ? list : list.length > 0 ? list[0] : ''; return props.multiple ? list : list.length > 0 ? list[0] : '';

View File

@ -66,6 +66,7 @@
import { warn } from '@/utils/log'; import { warn } from '@/utils/log';
import FileList from './FileList.vue'; import FileList from './FileList.vue';
import { useI18n } from '@/hooks/web/useI18n'; import { useI18n } from '@/hooks/web/useI18n';
import { get } from 'lodash-es';
const props = defineProps({ const props = defineProps({
...basicProps, ...basicProps,
@ -190,6 +191,14 @@
const { data } = ret; const { data } = ret;
item.status = UploadResultStatus.SUCCESS; item.status = UploadResultStatus.SUCCESS;
item.response = data; item.response = data;
if(props.resultField){
//
item.response = {
code:0,
message:"upload Success!",
url:get(ret, props.resultField)
}
}
return { return {
success: true, success: true,
error: null, error: null,

View File

@ -15,30 +15,55 @@
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 '../types/typing'; import { FileBasicColumn, 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';
import { isArray } from '@/utils/is'; import { isArray } from '@/utils/is';
import { BasicColumn } from '@/components/Table';
const props = defineProps(previewProps); const props = defineProps(previewProps);
const emit = defineEmits(['list-change', 'register', 'delete']); const emit = defineEmits(['list-change', 'register', 'delete']);
const columns = createPreviewColumns() as any[]; let columns : BasicColumn[] | FileBasicColumn[] = createPreviewColumns();
const actionColumn = createPreviewActionColumn({ handleRemove, handleDownload }) as any; let actionColumn :any;
const [register] = useModalInner(); const [register] = useModalInner();
const { t } = useI18n(); const { t } = useI18n();
const fileListRef = ref<PreviewFileItem[]>([]); const fileListRef = ref<PreviewFileItem[] | Array<any>>([]);
watch(
() => props.previewColumns,
() => {
if (props.previewColumns.length) {
columns = props.previewColumns;
actionColumn = null
}else{
columns=createPreviewColumns();
actionColumn = createPreviewActionColumn({ handleRemove, handleDownload })
};
},
{ immediate: true },
);
watch( watch(
() => props.value, () => props.value,
(value) => { (value) => {
if (!isArray(value)) value = []; if (!isArray(value)) value = [];
if(props.beforePreviewData){
value = props.beforePreviewData(value) as any
fileListRef.value = value
return
}
fileListRef.value = value fileListRef.value = value
.filter((item) => !!item) .filter((item) => !!item)
.map((item) => { .map((item) => {
if(typeof item!="string"){
console.error("return value should be string")
return
}
return { return {
url: item, url: item,
type: item.split('.').pop() || '', type: item.split('.').pop() || '',

View File

@ -4,6 +4,8 @@ import { FileBasicColumn } from './types/typing';
import type { Options } from 'sortablejs'; import type { Options } from 'sortablejs';
import { Merge } from '@/utils/types'; import { Merge } from '@/utils/types';
import { propTypes } from '@/utils/propTypes';
import { BasicColumn } from '@/components/Table';
type SortableOptions = Merge< type SortableOptions = Merge<
Omit<Options, 'onEnd'>, Omit<Options, 'onEnd'>,
@ -13,6 +15,19 @@ type SortableOptions = Merge<
} }
>; >;
export const previewType = {
previewColumns:{
type: Array as (PropType<BasicColumn[] | FileBasicColumn[]>),
default: [],
required: false,
},
beforePreviewData:{
type: Function as PropType<(arg:string[])=>Recordable<any>>,
default: null,
required: false,
},
}
type ListType = 'text' | 'picture' | 'picture-card'; type ListType = 'text' | 'picture' | 'picture-card';
export const basicProps = { export const basicProps = {
@ -69,11 +84,13 @@ export const basicProps = {
type: Object as PropType<SortableOptions>, type: Object as PropType<SortableOptions>,
default: () => ({}), default: () => ({}),
}, },
// support xxx.xxx.xx
resultField: propTypes.string.def(''),
}; };
export const uploadContainerProps = { export const uploadContainerProps = {
value: { value: {
type: Array as PropType<string[]>, type: Array as (PropType<string[]>),
default: () => [], default: () => [],
}, },
...basicProps, ...basicProps,
@ -85,6 +102,7 @@ export const uploadContainerProps = {
type: Boolean as PropType<boolean>, type: Boolean as PropType<boolean>,
default: false, default: false,
}, },
...previewType
}; };
export const previewProps = { export const previewProps = {
@ -92,11 +110,12 @@ export const previewProps = {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
default: () => [], default: () => [],
}, },
...previewType
}; };
export const fileListProps = { export const fileListProps = {
columns: { columns: {
type: Array as PropType<FileBasicColumn[]>, type: Array as (PropType<BasicColumn[] | FileBasicColumn[]> ),
default: null, default: null,
}, },
actionColumn: { actionColumn: {

View File

@ -16,7 +16,7 @@ export interface FileItem {
percent: number; percent: number;
file: File; file: File;
status?: UploadResultStatus; status?: UploadResultStatus;
response?: UploadApiResult; response?: UploadApiResult | Recordable<any>;
uuid: string; uuid: string;
} }

View File

@ -12,7 +12,14 @@
<Alert message="嵌入表单,加入表单校验" /> <Alert message="嵌入表单,加入表单校验" />
<BasicForm @register="register" class="my-5" /> <BasicForm @register="registerValiate" class="my-5" />
<Alert message="嵌入表单,加入resultFiled自定义返回值" />
<BasicForm @register="registerCustom" class="my-5" />
<Alert message="嵌入表单,自定义预览内容" />
<BasicForm @register="registerPreview" class="my-5" />
</PageWrapper> </PageWrapper>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -20,33 +27,193 @@
import { useMessage } from '@/hooks/web/useMessage'; import { useMessage } from '@/hooks/web/useMessage';
import { BasicForm, FormSchema, useForm } from '@/components/Form'; import { BasicForm, FormSchema, useForm } from '@/components/Form';
import { PageWrapper } from '@/components/Page'; import { PageWrapper } from '@/components/Page';
import { Alert } from 'ant-design-vue'; import { Alert,Button } from 'ant-design-vue';
import { uploadApi } from '@/api/sys/upload'; import { uploadApi } from '@/api/sys/upload';
import { createVNode } from "vue"
const schemas: FormSchema[] = [ const schemasValiate: FormSchema[] = [
{ {
field: 'field1', field: 'field1',
component: 'Upload', component: 'Upload',
label: '字段1', label: '字段1',
colProps: {
span: 8,
},
rules: [{ required: true, message: '请选择上传文件' }], rules: [{ required: true, message: '请选择上传文件' }],
componentProps: { componentProps: {
api: uploadApi, api: uploadApi,
}, },
},{
field: 'field2',
component: "ImageUpload",
label: '字段2(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
api: uploadApi,
}
}, },
]; ];
const { createMessage } = useMessage(); const schemasCustom: FormSchema[] = [
const [register] = useForm({ {
labelWidth: 120, field: 'field3',
schemas, component: 'Upload',
actionColOptions: { label: '字段3',
span: 16, componentProps: {
resultField:"data3.url",
api: (file,progress)=>{
return new Promise((resolve)=>{
uploadApi(file,progress).then((uploadApiResponse)=>{
resolve({
code:200,
data3:{
url:uploadApiResponse.data.url
}
})
})
})
}, },
}); },
},
{
field: 'field4',
component: "ImageUpload",
label: '字段4(ImageUpload)',
colProps: {
span: 8,
},
componentProps: {
resultField:"data4.url",
api: (file,progress)=>{
return new Promise((resolve)=>{
uploadApi(file,progress).then((uploadApiResponse)=>{
resolve({
code:200,
data4:{
url:uploadApiResponse.data.url
}
})
})
})
},
},
},
];
const schemasPreview: FormSchema[] = [
{
field: 'field5',
component: 'Upload',
label: '字段5',
componentProps: {
previewColumns:[{
title:"url5",
dataIndex:"url5"
},{
title:"type5",
dataIndex:"type5"
},{
title:"name5",
dataIndex:"name5"
},
{
title:"operation",
dataIndex:"",
customRender: ({ record })=>{
return createVNode(Button,{
onclick:()=>{
console.log(record)
createMessage.success(`请到控制台查看该行输出结果`);
}
},"点我")
}
},
],
beforePreviewData:(arg)=>{
let data = arg.filter((item) => !!item).map((item) => {
if(typeof item !== "string"){
console.error("return value should be string")
return
}
return {
url5: item,
type5: item.split('.').pop() || '',
name5: item.split('/').pop() || '',
};
})
return data
},
resultField:"data5.url",
api: (file,progress)=>{
return new Promise((resolve)=>{
uploadApi(file,progress).then((uploadApiResponse)=>{
resolve({
code:200,
data5:{
url:uploadApiResponse.data.url
}
})
})
})
},
},
},
];
const { createMessage } = useMessage();
function handleChange(list: string[]) { function handleChange(list: string[]) {
createMessage.info(`已上传文件${JSON.stringify(list)}`); createMessage.success(`已上传文件${JSON.stringify(list)}`);
} }
const [registerValiate,{getFieldsValue:getFieldsValueValiate,validate}] = useForm({
labelWidth: 160,
schemas:schemasValiate,
actionColOptions: {
span: 18,
},
submitFunc:()=>{
return new Promise((resolve)=>{
validate().then((e)=>{
resolve()
console.log(getFieldsValueValiate())
createMessage.success(`请到控制台查看结果`);
}).catch(()=>{
createMessage.error(`请输入必填项`);
})
})
}
});
// resultFields
const [registerCustom,{getFieldsValue:getFieldsValueCustom}] = useForm({
labelWidth: 160,
schemas:schemasCustom,
actionColOptions: {
span: 18,
},
submitFunc:()=>{
return new Promise((resolve)=>{
console.log(getFieldsValueCustom())
resolve()
createMessage.success(`请到控制台查看结果`);
})
}
});
// registerPreview
const [registerPreview,{getFieldsValue:getFieldsValuePreview}] = useForm({
labelWidth: 160,
schemas:schemasPreview,
actionColOptions: {
span: 18,
},
submitFunc:()=>{
return new Promise((resolve)=>{
console.log(getFieldsValuePreview())
resolve()
createMessage.success(`请到控制台查看结果`);
})
}
});
</script> </script>