feat: add upload component

pull/2/head
xingyu 2022-12-06 23:45:35 +08:00
parent ba6a2957fc
commit 731e49d7b6
8 changed files with 234 additions and 43 deletions

View File

@ -77,6 +77,17 @@ const crudSchemas = reactive<VxeCrudSchema>({
component: 'InputNumber', component: 'InputNumber',
value: 0 value: 0
}, },
#elseif($column.htmlType == "imageUpload")## 图片上传
form: {
component: 'UploadImg',
componentProps: {
limit: 1
}
},
#elseif($column.htmlType == "fileUpload")## 图片上传
form: {
component: 'UploadFile'
},
#end #end
#end #end
#if ($column.listOperation) #if ($column.listOperation)

View File

@ -21,6 +21,7 @@ import {
} from 'element-plus' } from 'element-plus'
import { InputPassword } from '@/components/InputPassword' import { InputPassword } from '@/components/InputPassword'
import { Editor } from '@/components/Editor' import { Editor } from '@/components/Editor'
import { UploadImg, UploadFile } from '@/components/UploadFile'
import { ComponentName } from '@/types/components' import { ComponentName } from '@/types/components'
const componentMap: Recordable<Component, ComponentName> = { const componentMap: Recordable<Component, ComponentName> = {
@ -45,7 +46,9 @@ const componentMap: Recordable<Component, ComponentName> = {
TreeSelect: ElTreeSelect, TreeSelect: ElTreeSelect,
RadioButton: ElRadioGroup, RadioButton: ElRadioGroup,
InputPassword: InputPassword, InputPassword: InputPassword,
Editor: Editor Editor: Editor,
UploadImg: UploadImg,
UploadFile: UploadFile
} }
export { componentMap } export { componentMap }

View File

@ -1,3 +1,4 @@
import UploadImg from './src/UploadImg.vue' import UploadImg from './src/UploadImg.vue'
import UploadFile from './src/UploadFile.vue'
export { UploadImg } export { UploadImg, UploadFile }

View File

@ -0,0 +1,167 @@
<template>
<div class="upload-file">
<el-upload
ref="uploadRef"
:multiple="props.limit > 1"
name="file"
v-model="valueRef"
:file-list="fileList"
:show-file-list="false"
:action="updateUrl"
:headers="uploadHeaders"
:limit="props.limit"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:on-remove="handleRemove"
class="upload-file-uploader"
>
<Icon icon="ep:upload-filled" />
</el-upload>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useMessage } from '@/hooks/web/useMessage'
import { propTypes } from '@/utils/propTypes'
import { getAccessToken, getTenantId } from '@/utils/auth'
import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
const message = useMessage() //
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: propTypes.oneOfType([String, Object, Array]),
title: propTypes.string.def('文件上传'),
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // , ['png', 'jpg', 'jpeg']
fileSize: propTypes.number.def(5), // (MB)
limit: propTypes.number.def(5), //
isShowTip: propTypes.bool.def(false) //
})
// ========== ==========
const valueRef = ref(props.modelValue)
const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([])
const uploadNumber = ref<number>(0)
const uploadHeaders = ref({
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
})
watch(
() => props.modelValue,
(val) => {
if (val) {
// , 穿map
const list = Array.isArray(props.modelValue)
? props.modelValue
: Array.isArray(props.modelValue?.split(','))
? props.modelValue?.split(',')
: Array.of(props.modelValue)
//
fileList.value = list.map((item) => {
if (typeof item === 'string') {
// edit by
item = { name: item, url: item }
}
return item
})
} else {
fileList.value = []
return []
}
},
{
deep: true,
immediate: true
}
)
//
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
message.error(`上传文件数量不能超过${props.limit}个!`)
return false
}
let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
}
const isImg = props.fileType.some((type: string) => {
if (file.type.indexOf(type) > -1) return true
return !!(fileExtension && fileExtension.indexOf(type) > -1)
})
const isLimit = file.size < props.fileSize * 1024 * 1024
if (!isImg) {
message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)
return false
}
if (!isLimit) {
message.error(`上传文件大小不能超过${props.fileSize}MB!`)
return false
}
message.success('正在上传文件,请稍候...')
uploadNumber.value++
}
//
// const handleFileChange = (uploadFile: UploadFile): void => {
// uploadRef.value.data.path = uploadFile.name
// }
//
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
uploadList.value.push({ name: res.data, url: res.data })
if (uploadList.value.length == uploadNumber.value) {
fileList.value = fileList.value.concat(uploadList.value)
uploadList.value = []
uploadNumber.value = 0
emit('update:modelValue', listToString(fileList.value))
}
}
//
const handleExceed: UploadProps['onExceed'] = (): void => {
message.error(`上传文件数量不能超过${props.limit}个!`)
}
//
const excelUploadError: UploadProps['onError'] = (): void => {
message.error('导入数据失败,请您重新上传!')
}
//
const handleRemove = (file) => {
const findex = fileList.value.map((f) => f.name).indexOf(file.name)
if (findex > -1) {
fileList.value.splice(findex, 1)
emit('update:modelValue', listToString(fileList.value))
}
}
//
const listToString = (list: UploadUserFile[], separator?: string) => {
let strs = ''
separator = separator || ','
for (let i in list) {
strs += list[i].url + separator
}
return strs != '' ? strs.substr(0, strs.length - 1) : ''
}
</script>
<style scoped lang="scss">
.upload-file-uploader {
margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
border: 1px solid #e4e7ed;
line-height: 2;
margin-bottom: 10px;
position: relative;
}
.upload-file-list .ele-upload-list__item-content {
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

View File

@ -1,24 +1,27 @@
<template> <template>
<div class="component-upload-image">
<el-upload <el-upload
ref="uploadRef" ref="uploadRef"
:multiple="limit > 1" :multiple="props.limit > 1"
name="file" name="file"
v-model="valueRef"
list-type="picture-card" list-type="picture-card"
v-model:file-list="fileList" :file-list="fileList"
:show-file-list="true" :show-file-list="true"
:action="updateUrl" :action="updateUrl"
:headers="uploadHeaders" :headers="uploadHeaders"
:limit="limit" :limit="props.limit"
:before-upload="beforeUpload" :before-upload="beforeUpload"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-success="handleFileSuccess" :on-success="handleFileSuccess"
:on-error="excelUploadError" :on-error="excelUploadError"
:on-remove="handleRemove" :on-remove="handleRemove"
:on-preview="handlePictureCardPreview" :on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }" :class="{ hide: fileList.length >= props.limit }"
> >
<Icon icon="ep:upload-filled" /> <Icon icon="ep:upload-filled" />
</el-upload> </el-upload>
</div>
<!-- 文件列表 --> <!-- 文件列表 -->
<Dialog v-model="dialogVisible" title="预览" width="800" append-to-body> <Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" /> <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
@ -32,10 +35,10 @@ import { getAccessToken, getTenantId } from '@/utils/auth'
import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus' import { ElUpload, UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'
const message = useMessage() // const message = useMessage() //
const emit = defineEmits(['input']) const emit = defineEmits(['update:modelValue'])
const props = defineProps({ const props = defineProps({
imgs: propTypes.oneOfType([String, Object, Array]), modelValue: propTypes.oneOfType([String, Object, Array]),
title: propTypes.string.def('图片上传'), title: propTypes.string.def('图片上传'),
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL), updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // , ['png', 'jpg', 'jpeg'] fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // , ['png', 'jpg', 'jpeg']
@ -44,6 +47,7 @@ const props = defineProps({
isShowTip: propTypes.bool.def(false) // isShowTip: propTypes.bool.def(false) //
}) })
// ========== ========== // ========== ==========
const valueRef = ref(props.modelValue)
const uploadRef = ref<UploadInstance>() const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([]) const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([]) const fileList = ref<UploadUserFile[]>([])
@ -55,15 +59,15 @@ const uploadHeaders = ref({
'tenant-id': getTenantId() 'tenant-id': getTenantId()
}) })
watch( watch(
() => props.imgs, () => props.modelValue,
(val) => { (val) => {
if (val) { if (val) {
// , 穿map // , 穿map
const list = Array.isArray(props.imgs) const list = Array.isArray(props.modelValue)
? props.imgs ? props.modelValue
: Array.isArray(props.imgs?.split(',')) : Array.isArray(props.modelValue?.split(','))
? props.imgs?.split(',') ? props.modelValue?.split(',')
: Array.of(props.imgs) : Array.of(props.modelValue)
// //
fileList.value = list.map((item) => { fileList.value = list.map((item) => {
if (typeof item === 'string') { if (typeof item === 'string') {
@ -84,6 +88,10 @@ watch(
) )
// //
const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => { const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
if (fileList.value.length >= props.limit) {
message.error(`上传文件数量不能超过${props.limit}个!`)
return false
}
let fileExtension = '' let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) { if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1) fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
@ -111,14 +119,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
// //
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => { const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功') message.success('上传成功')
console.info(uploadList.value)
console.info(fileList.value)
uploadList.value.push({ name: res.data, url: res.data }) uploadList.value.push({ name: res.data, url: res.data })
if (uploadList.value.length == uploadNumber.value) { if (uploadList.value.length == uploadNumber.value) {
fileList.value = fileList.value.concat(uploadList.value) fileList.value = fileList.value.concat(uploadList.value)
uploadList.value = [] uploadList.value = []
uploadNumber.value = 0 uploadNumber.value = 0
emit('input', listToString(fileList.value)) emit('update:modelValue', listToString(fileList.value))
} }
} }
// //
@ -134,7 +140,7 @@ const handleRemove = (file) => {
const findex = fileList.value.map((f) => f.name).indexOf(file.name) const findex = fileList.value.map((f) => f.name).indexOf(file.name)
if (findex > -1) { if (findex > -1) {
fileList.value.splice(findex, 1) fileList.value.splice(findex, 1)
emit('input', listToString(fileList.value)) emit('update:modelValue', listToString(fileList.value))
} }
} }
// //

View File

@ -21,6 +21,8 @@ export type ComponentName =
| 'TreeSelect' | 'TreeSelect'
| 'InputPassword' | 'InputPassword'
| 'Editor' | 'Editor'
| 'UploadImg'
| 'UploadFile'
export type ColProps = { export type ColProps = {
span?: number span?: number

View File

@ -41,6 +41,12 @@ const crudSchemas = reactive<VxeCrudSchema>({
cellRender: { cellRender: {
name: 'XImg' name: 'XImg'
} }
},
form: {
component: 'UploadImg',
componentProps: {
limit: 1
}
} }
}, },
{ {

View File

@ -61,16 +61,12 @@
v-if="['create', 'update'].includes(actionType)" v-if="['create', 'update'].includes(actionType)"
:schema="allSchemas.formSchema" :schema="allSchemas.formSchema"
:rules="rules" :rules="rules"
> />
<template #logo="form">
<UploadImg :imgs="form['logo']" :limit="1" />
</template>
</Form>
<!-- 表单详情 --> <!-- 表单详情 -->
<Descriptions <Descriptions
v-if="actionType === 'detail'" v-if="actionType === 'detail'"
:schema="allSchemas.detailSchema" :schema="allSchemas.detailSchema"
:data="detailRef" :data="detailData"
> >
<template #accessTokenValiditySeconds="{ row }"> <template #accessTokenValiditySeconds="{ row }">
{{ row.accessTokenValiditySeconds + '秒' }} {{ row.accessTokenValiditySeconds + '秒' }}
@ -142,7 +138,6 @@ import { useMessage } from '@/hooks/web/useMessage'
import { useVxeGrid } from '@/hooks/web/useVxeGrid' import { useVxeGrid } from '@/hooks/web/useVxeGrid'
import { VxeGridInstance } from 'vxe-table' import { VxeGridInstance } from 'vxe-table'
import { FormExpose } from '@/components/Form' import { FormExpose } from '@/components/Form'
import { UploadImg } from '@/components/UploadFile'
// import // import
import * as ClientApi from '@/api/system/oauth2/client' import * as ClientApi from '@/api/system/oauth2/client'
import { rules, allSchemas } from './client.data' import { rules, allSchemas } from './client.data'
@ -163,7 +158,7 @@ const dialogTitle = ref('edit') // 弹出层标题
const actionType = ref('') // const actionType = ref('') //
const actionLoading = ref(false) // Loading const actionLoading = ref(false) // Loading
const formRef = ref<FormExpose>() // Ref const formRef = ref<FormExpose>() // Ref
const detailRef = ref() // Ref const detailData = ref() // Ref
// //
const setDialogTile = (type: string) => { const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
@ -188,7 +183,7 @@ const handleUpdate = async (rowId: number) => {
const handleDetail = async (rowId: number) => { const handleDetail = async (rowId: number) => {
setDialogTile('detail') setDialogTile('detail')
const res = await ClientApi.getOAuth2ClientApi(rowId) const res = await ClientApi.getOAuth2ClientApi(rowId)
detailRef.value = res detailData.value = res
} }
// //