UNPKG

@ithinkdt/naive

Version:

iThinkDT Naive UI

439 lines (407 loc) 12.4 kB
import { toRef, toValue, withDirectives, resolveDirective, markRaw, reactive } from 'vue' import { NButton, NInput, NInputNumber, NP, NSelect, NRadio, NCheckbox, NSwitch } from 'ithinkdt-ui' import { debouncedWatch, promiseTimeout } from '@vueuse/core' import { CORE_CTX, useDict } from '@ithinkdt/core' import { NRadios, NCheckboxs, DtIconSelect, NDatePicker, NTimePicker, NUpload, NUploadDragger, DtUserDept, } from '../components' const getFileOps = (type, { max, props }, auto) => { const uploadMap = {} const deleted = [] let _onRemove = props.onRemove props.onRemove = (options) => { const file = options.file const fileId = file.file?.fileId ?? file.fileId if (fileId) { deleted.push(fileId) } delete uploadMap[fileId ?? file.id] return _onRemove?.(options) } const lazyFileInfo = reactive({}) debouncedWatch( lazyFileInfo, (infos) => { const keys = Object.keys(infos) if (keys.length === 0) return CORE_CTX.fileInfo(keys).then((data) => { for (const it of data) { const f = lazyFileInfo[it.fileId] delete lazyFileInfo[it.fileId] f.name = it.fileName } }) }, { debounce: 100 }, ) function transformFiles(ids, type) { return ids.map((it) => { let f = uploadMap[it] ?? { id: it, fileId: it, name: it, status: 'finished', type: type === 'image' ? 'image/*' : '', } if (f.status === 'finished') { f.thumbnailUrl ??= CORE_CTX.filePreview(it) f.url = type === 'image' ? f.thumbnailUrl : CORE_CTX.fileDownload(it) } if (uploadMap[it] || type === 'image') return f f = reactive(f) lazyFileInfo[f.id] = f return f }) } let uploadFlag = false const ret = { modelValue: 'fileList', props: { action: '', max, multiple: max > 1, defaultUpload: false, customRequest: ({ file, onFinish, onError, onProgress, ...options }) => { file.$ = markRaw( CORE_CTX.fileUploader(file.file, { ...options, field: 'file', onUploadProgress: (percent) => { onProgress({ percent }) }, }) .then((f) => { if (!uploadMap[file.id]) { CORE_CTX.fileRemove([f.fileId]) } file.file.fileId = f.fileId file.file.filePath = f.filePath file.file.fileName = f.fileName file.file.extendName = f.extendName file.file.fileSize = f.fileSize onFinish() }) .catch(() => { onError() }), ) }, }, rule: [ { trigger: ['change', 'blur'], validator: async (v) => { await Promise.all(Object.values(uploadMap).map((it) => it.$)) if ( uploadFlag && (typeof v === 'string' ? v?.split(',') : v)?.find( (it) => (it.id ? it : uploadMap[it])?.status !== 'finished', ) ) return '文件上传出现错误!' }, }, ], _submit: async ($) => { deleted.length > 0 && (await CORE_CTX.fileRemove(deleted)) if (typeof toValue(auto) === 'boolean') return await Promise.all([$?.submit(), promiseTimeout(300)]) await Promise.all(Object.values(uploadMap).map((it) => it.$)) }, } if (type === 'upload') return ret return { ...ret, _parse(v) { // eslint-disable-next-line unicorn/no-null if (!v?.length) return null const str = v .map((it) => { const fileId = it.file?.fileId ?? it.fileId if (it.status === 'removed') { if (fileId) { deleted.push(fileId) } delete uploadMap[fileId ?? it.id] // eslint-disable-next-line unicorn/no-null return null } if (it.status === 'finished') { delete uploadMap[it.id] uploadMap[fileId] = it return it.file?.fileId ?? it.fileId } uploadMap[it.id] = it return it.id }) .filter((it) => !!it) .join(',') return str }, _transform(v) { v = v?.trim?.() if (!v) return [] return transformFiles(v.split(','), type) }, } } const typeMap = { input: (_, p) => { const [minlength, maxlength] = (p?.split('~') ?? []).map((it) => (it ? Number.parseInt(it) : undefined)) return { component: NInput, modelValue: 'value', props: { clearable: true, minlength, maxlength, }, } }, text: (_, p) => { return { component: NInput, modelValue: 'value', props: { ...typeMap.input(_, p).props, type: 'textarea', showCount: true, }, } }, number: (_, p) => { const [min, max] = (p?.split('~') ?? []).map((it) => (it ? Number.parseFloat(it) : undefined)) return { component: NInputNumber, modelValue: 'value', props: { clearable: true, min, max, style: { width: '100%', }, }, } }, int: (_, p) => { return { component: NInputNumber, modelValue: 'value', props: { ...typeMap.number(_, p).props, precision: 0, }, } }, select: ({ props }, p) => { const dicts = p && useDict(p) return { component: NSelect, modelValue: 'value', props: { clearable: true, filterable: true, options: dicts, loading: toRef(dicts, 'loading'), renderLabel(option) { return withDirectives( <span>{option?.[props.labelField || props['label-field'] || 'label']}</span>, [[resolveDirective('tooltip'), {}, '', { auto: true }]], ) }, }, } }, selects: (_, p) => { return { component: NSelect, modelValue: 'value', props: { ...typeMap.select(_, p).props, multiple: true, }, } }, radio: () => { return { component: NRadio, modelValue: 'value', } }, radios: (_, p) => { return { component: NRadios, modelValue: 'value', props: { options: p && useDict(p), }, } }, checkbox: () => { return { component: NCheckbox, modelValue: 'checked', } }, checkboxs: (_, p) => { return { component: NCheckboxs, modelValue: 'value', props: { options: p && useDict(p), }, } }, datepicker: (_, p) => { return { component: NDatePicker, modelValue: 'value', props: { clearable: true, type: p, updateValueOnClose: true, }, } }, timepicker: () => { return { component: NTimePicker, modelValue: 'value', props: { clearable: true, }, } }, switch: () => { return { component: NSwitch, modelValue: 'value', } }, upload: ({ props }, p) => { const max = Math.max(1, Number.parseInt(p)) || Number.MAX_SAFE_INTEGER const ops = getFileOps( 'upload', { props, max }, toRef(() => props.defaultUpload), ) return { component: NUpload, ...ops, slots: { default: props.listType === 'image-card' ? undefined : props?.directoryDnd ? () => ( <NUploadDragger> <NP depth="3" style="padding: 16px 0"> 点击或者拖动文件到该区域来上传 </NP> </NUploadDragger> ) : () => <NButton>选择文件</NButton>, }, } }, file: ({ props }, p) => { const max = Math.max(1, Number.parseInt(p)) || Number.MAX_SAFE_INTEGER const ops = getFileOps( 'file', { props, max }, toRef(() => props.defaultUpload), ) return { component: NUpload, ...ops, props: { ...ops.props, listType: 'text', }, slots: { default: props?.directoryDnd ? () => ( <NUploadDragger> <NP depth="3" style="padding: 16px 0"> 点击或者拖动文件到该区域来上传 </NP> </NUploadDragger> ) : () => <NButton>选择文件</NButton>, }, } }, image: ({ props }, p) => { const max = Math.max(1, Number.parseInt(p)) || Number.MAX_SAFE_INTEGER const ops = getFileOps( 'image', { props, max }, toRef(() => props.defaultUpload), ) return { component: NUpload, ...ops, props: { ...ops.props, listType: 'image-card', accept: 'image/*', }, } }, icon: () => { return { component: DtIconSelect, modelValue: 'value', props: {}, } }, user: (_, p) => { return { component: DtUserDept, modelValue: 'value', props: { type: 'user', selectType: p, }, } }, users: (_, p) => { return { component: DtUserDept, modelValue: 'value', props: { ...typeMap.user(_, p), multiple: true, }, } }, dept: (_, p) => { return { component: DtUserDept, modelValue: 'value', props: { type: 'dept', selectType: p, }, } }, depts: (_, p) => { return { component: DtUserDept, modelValue: 'value', props: { ...typeMap.dept(_, p), multiple: true, }, } }, } export default typeMap