@ithinkdt/naive
Version:
iThinkDT Naive UI
439 lines (407 loc) • 12.4 kB
JSX
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