feat: add upload component

This commit is contained in:
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',
value: 0
},
#elseif($column.htmlType == "imageUpload")## 图片上传
form: {
component: 'UploadImg',
componentProps: {
limit: 1
}
},
#elseif($column.htmlType == "fileUpload")## 图片上传
form: {
component: 'UploadFile'
},
#end
#end
#if ($column.listOperation)

View File

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

View File

@ -1,3 +1,4 @@
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>
<el-upload
ref="uploadRef"
:multiple="limit > 1"
name="file"
list-type="picture-card"
v-model:file-list="fileList"
:show-file-list="true"
:action="updateUrl"
:headers="uploadHeaders"
:limit="limit"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:on-remove="handleRemove"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= limit }"
>
<Icon icon="ep:upload-filled" />
</el-upload>
<div class="component-upload-image">
<el-upload
ref="uploadRef"
:multiple="props.limit > 1"
name="file"
v-model="valueRef"
list-type="picture-card"
:file-list="fileList"
:show-file-list="true"
:action="updateUrl"
:headers="uploadHeaders"
:limit="props.limit"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleFileSuccess"
:on-error="excelUploadError"
:on-remove="handleRemove"
:on-preview="handlePictureCardPreview"
:class="{ hide: fileList.length >= props.limit }"
>
<Icon icon="ep:upload-filled" />
</el-upload>
</div>
<!-- 文件列表 -->
<Dialog v-model="dialogVisible" title="预览" width="800" append-to-body>
<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'
const message = useMessage() //
const emit = defineEmits(['input'])
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
imgs: propTypes.oneOfType([String, Object, Array]),
modelValue: propTypes.oneOfType([String, Object, Array]),
title: propTypes.string.def('图片上传'),
updateUrl: propTypes.string.def(import.meta.env.VITE_UPLOAD_URL),
fileType: propTypes.array.def(['jpg', 'png', 'gif', 'jpeg']), // , ['png', 'jpg', 'jpeg']
@ -44,6 +47,7 @@ const props = defineProps({
isShowTip: propTypes.bool.def(false) //
})
// ========== ==========
const valueRef = ref(props.modelValue)
const uploadRef = ref<UploadInstance>()
const uploadList = ref<UploadUserFile[]>([])
const fileList = ref<UploadUserFile[]>([])
@ -55,15 +59,15 @@ const uploadHeaders = ref({
'tenant-id': getTenantId()
})
watch(
() => props.imgs,
() => props.modelValue,
(val) => {
if (val) {
// , 穿map
const list = Array.isArray(props.imgs)
? props.imgs
: Array.isArray(props.imgs?.split(','))
? props.imgs?.split(',')
: Array.of(props.imgs)
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') {
@ -84,6 +88,10 @@ watch(
)
//
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)
@ -111,14 +119,12 @@ const beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {
//
const handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {
message.success('上传成功')
console.info(uploadList.value)
console.info(fileList.value)
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('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)
if (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'
| 'InputPassword'
| 'Editor'
| 'UploadImg'
| 'UploadFile'
export type ColProps = {
span?: number

View File

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

View File

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