封装uview-plus上传组件up-upload,支持v-model绑定

痛点

vue上传组件拿到了一般无法直接使用,需要对其上下传的接口按照业务进行处理及定制。本次拿到的uview-plus也是一样,对其上传组件up-upload进行封装,令其更方便开发

目标

封装希望达到的目标,就是实现v-model的绑定。令其支持三种模式:

1)单文件绑定,双向绑定一个string,其值可以从数据库取对应的文件字段

2)多文档绑定,双向绑定一个string[],其值可以是一组一对多的数据,来自处理后的数据库数据或非结构化存储数据

3)split压缩绑定,双向绑定一个string,其值为逗号分隔的方式,存储到数据库对应的文件字段。可以根据文件数估算需要存储的字段大小

其它参数根据业务,只支持几个关键参数:

maxCount:决定单文件还是多文件,为1时为单文件,大于1时为多文件

maxSize:上传文件的大小,默认10M,最大设置和nginx最大文件包大小及commons-file-upload的配置有关。根据业务数据大小来前端限制

accept:支持的文件类型过滤

compactMultiValue:多文件时是否通过逗号压缩到一个string,例如:1/2024/0531/665981d8fbb9be8c8414c8da.png,1/2024/0531/665981d8fbb9be8c8414c8ea.png,temp/665a906afbb9c649baf1fbe8.jpg
默认为true,false的话则v-model需要绑定类型为Array<string>

其它的暂不考虑扩展

实现

下面是自己的代码的实现

说明:

1)默认为图片组件,也可以通过制定acept上传其它类型

2)import.meta.env.VITE_SERVER_BASEURL为服务器上传请求地址

3)fileDomain为pinia数据,在APP启动时,加载为服务器上传后的文件地址,例如oss地址,本地也可以例如:http://localhost:8080/upload

4) 数据请求Result格式定义:

export class Result<T> {// ccframe约定返回code!: numbersuccess!: booleanmessage?: stringresult?: T
}

5)上传请求 返回结果类型。为x-file-storage的返回dto,pont映射的defs.FileInfo类型如下

export class FileInfo {/** attr */attr?: ObjectMap<any, object>/** basePath */basePath?: string/** contentType */contentType?: string/** createTime */createTime?: string/** ext */ext?: string/** fileAcl */fileAcl?: object/** filename */filename?: string/** hashInfo */hashInfo?: ObjectMap<any, string>/** id */id?: string/** metadata */metadata?: ObjectMap<any, string>/** objectId */objectId?: string/** objectType */objectType?: string/** originalFilename */originalFilename?: string/** path */path?: string/** platform */platform?: string/** size */size?: number/** thContentType */thContentType?: string/** thFileAcl */thFileAcl?: object/** thFilename */thFilename?: string/** thMetadata */thMetadata?: ObjectMap<any, string>/** thSize */thSize?: number/** thUrl */thUrl?: string/** thUserMetadata */thUserMetadata?: ObjectMap<any, string>/** uploadId */uploadId?: string/** uploadStatus */uploadStatus?: number/** url */url?: string/** userMetadata */userMetadata?: ObjectMap<any, string>}

6) 解释下const extract = /((\w+\/)*)([^\0-\x1F\\/:*?"<>|]+\.([^.]+))$/.exec(item):

因为服务器存储的路径为:temp/<文件> 或<租户ID>/<年>/<月日>/<文件>。例如:
1/2024/0531/665981d8fbb9be8c8414c8da.png
1/2024/0531/665981d8fbb9be8c8414c8ea.png
temp/665a906afbb9c649baf1fbe8.jpg
因此有了这个正则提取文件各部分,这里主要是提取后缀名来进行类型的映射(up-upload组件需要)

组件实现类cc-upload.vue

<template><up-upload:fileList="data.fileList"@afterRead="afterRead"@delete="deletePic":maxCount="props.maxCount"maxSize="10485760"v-bind="$attrs"></up-upload>
</template>
<script lang="ts" setup>
import { Result } from '@/utils/service'
import { useAppStore } from '@/store'
import { UPDATE_MODEL_EVENT, CHANGE_EVENT, INPUT_EVENT } from './event'const { fileDomain } = useAppStore()interface UploadFileItem {status: 'uploading' | 'failed' | 'success'url: stringtype: string // 例如'image' | 'video'message: stringthumb?: stringisImage?: booleanisVideo?: boolean
}const props = withDefaults(defineProps<{modelValue: string | string[] | undefinedmaxCount: numbermaxSize: numbercompactMultiValue: booleanwidth?: numberheight?: numberaccept?: string}>(),{modelValue: undefined,maxCount: 1,maxSize: 10485760, // 10McompactMultiValue: true, // 默认开启多文件逗号压缩width: 80,height: 80,accept: '.gif,.jpg,.png,image/gif,image/jpeg,image/png' // 注意,默认是上传图片}
)const data = reactive<{fileList: Array<UploadFileItem>
}>({fileList: []
})const emitVal = () => {const fieldVal: string[] = data.fileList.filter((item) => item.status === 'success').map((item) => item.url.slice(fileDomain.length)) // 只更新上传成功的if (props.maxCount === 1) {// 单数据const sigleVal = fieldVal.length === 0 ? undefined : fieldVal[0]emit(UPDATE_MODEL_EVENT, sigleVal)emit(CHANGE_EVENT, sigleVal)emit(INPUT_EVENT, sigleVal)} else {// 多数据const multiValue = props.compactMultiValue ? fieldVal.join(',') : fieldValemit(UPDATE_MODEL_EVENT, multiValue)emit(CHANGE_EVENT, multiValue)emit(INPUT_EVENT, multiValue)}
}const afterRead = async (event) => {const files = [].concat(event.file) // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式,兼容两种// 添加到列表&上传中状态files.forEach((item) => {data.fileList.push({...item,status: 'uploading',message: '上传中'})})files.forEach(async (file) => {const serverUrl: string = (await uploadFilePromise(file.url)) as stringconst updateRecord = data.fileList.find((item) => item.url === file.url)if (serverUrl && serverUrl.length > 0) {// 设置上传结果updateRecord.url = serverUrlupdateRecord.status = 'success'updateRecord.message = ''} else {updateRecord.status = 'failed'updateRecord.message = '上传失败'}emitVal()})
}const deletePic = async (event) => {const fileData: UploadFileItem[] = data.fileList.splice(event.index, 1) // 直接删除本地,服务器上不管,由保存方法处理if (fileData[0].status === 'success') {emitVal()}
}const uploadFilePromise = async (dataurl) => {return new Promise((resolve, reject) => {const a = uni.uploadFile({url: import.meta.env.VITE_SERVER_BASEURL + '/api/tools/upload', // 前台图片上传地址filePath: dataurl,name: 'file',formData: {},success: (res) => {if (res.statusCode === 200) {const uploadResult = JSON.parse(res.data) as Result<defs.FileInfo>if (uploadResult.code === 200) {resolve(uploadResult.result.url)return}}resolve('') // 上传失败},fail: (err) => {console.log(err)resolve('') // 上传失败}})})
}const emit = defineEmits([UPDATE_MODEL_EVENT, CHANGE_EVENT, INPUT_EVENT])watch(// modelValue重新赋值时,根据值解析path和filename、ext、url() => props.modelValue,(val) => {loadVal(val)}
)const checkType: (string) => string = (fileExt) => {const imageExts = ['jpg', 'jpeg', 'png', 'gif', 'webp']const videoExts = ['mp4', 'mkv', 'avi', 'mov', 'm4v']if (imageExts.indexOf(fileExt) > -1) {return 'image'}if (videoExts.indexOf(fileExt) > -1) {return 'video'}return 'file'
}// methods
const loadVal = (val?: string | string[]) => {if ((Array.isArray(val) && props.maxCount === 1) ||(typeof val === 'string' && props.maxCount > 1 && props.compactMultiValue === false)) {console.error('val type and maxCount mismatch!')return}data.fileList.splice(0, data.fileList.length)if (val) {const vals = [];[].concat(val).forEach((item) => {vals.push(...item.split(','))})// eslint-disable-next-line no-control-regexvals.forEach((item) => {const extract = /((\w+\/)*)([^\0-\x1F\\/:*?"<>|]+\.([^.]+))$/.exec(item)if (extract) {const fileType = checkType(extract[4].toLocaleLowerCase)const newItem: UploadFileItem = {status: 'success',message: '',type: fileType,url: fileDomain + extract[1] + extract[3]}data.fileList.push(newItem)}})}
}
</script>

event.ts

export const UPDATE_MODEL_EVENT = 'update:modelValue'
export const CHANGE_EVENT = 'change'
export const INPUT_EVENT = 'input'

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/21093.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

字符串-将str1编辑成str2所需最小代价(hard)

一、题目描述 二、解题思路 该题目使用动态规划的思想来解决问题 刚开始我还在想&#xff0c;删除添加的操作可以等价为替换操作&#xff0c;如果替换操作的Cost大于删除添加组合操作的Cost之和就需要把 rcdcic。 但是在动态规划中&#xff0c;如果对三种不同的操作方式进行…

【Centos7】解决 CentOS 7 中出现 “xx: command not found“ 错误的全面指南

【Centos7】初探xx:command not found解决方案 大家好 我是寸铁&#x1f44a; 【Centos7】解决 CentOS 7 中出现 “xx: command not found” 错误的全面指南✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 经常有小伙伴问我&#xff0c;xx:command not found怎么办&#xff1…

Spring原理-IOC和AOP

概述 在此记录spring的学习内容。spring官网&#xff1a;https://spring.io/ 概念故事 从前&#xff0c;在Java的大森林中&#xff0c;有一片神奇的土地&#xff0c;名叫"Spring"。这片土地上生长着各种美丽而强大的植物&#xff0c;它们分别象征着Spring框架中的…

1.1 寻找灵感:怎样像艺术家一样去看待这个世界

“你是从哪里获取到这些创作灵感的&#xff1f;” 当每一个艺术家被问到这个问题时&#xff0c;只有诚实的艺术家会回答&#xff1a; “这是我偷窃的” 怎样像艺术家一样去看待这个世界呢&#xff1f; 你首先需要弄明白什么是值得你偷窃的&#xff0c;然后你才能继续接下来…

如何在一台电脑上安装多个版本的JDK并且切换使用?

如何在一台电脑上安装多个版本的JDK并且切换使用&#xff1f; 文章目录 如何在一台电脑上安装多个版本的JDK并且切换使用&#xff1f;1.目录管理2.下载JDK3.配置环境变量4. 验证 1.目录管理 我们需要首先对不同版本的JDK进行版本管理&#xff0c;如下所示&#xff0c;我在C盘路…

# linux 系统下 切换 root 用户时出现 authentication failure 的解决办法

linux 系统下 切换 root 用户时出现 authentication failure 的解决办法 1、问题分析&#xff1a; 切换 root 用户时出现 authentication failure&#xff0c;意思即 “身份验证失败”&#xff0c;这是由于新安装的系统&#xff0c;可能没有给 root 用户设置密码。 2、解决方…

算法(二)二分查找

文章目录 二分查找简介实现方式循环方式递归方式 经典例子 二分查找简介 二分查找&#xff08;binary search&#xff09;算法&#xff0c;也叫折半算法。二分查找是针对有序的数据集合的查找办法&#xff0c;如果是无序的数据结合就使用遍历。二分查找之所以快速&#xff0c;…

OWASP top10--SQL注入(四、sqlmap安装及使用)

目录 sqlmap工具安装&#xff1a; 工具说明&#xff1a; 主要功能特性包括&#xff1a; 基本使用示例&#xff1a; 先下载python2.7.9版本 sqlmap运行 sqlmap工具使用 -u -r –-levelLEVEL扫描深度级别 --riskRISK 执行测试的风险 -threads 线程数 -batch-smart智能…

JS异步编程

目录 概念定时器Promise对象概念 单线程模型指的是,JavaScript 只在一个线程上运行。也就是说,JavaScript 同时只能执行一个任务,其他任务都必须在后面排队等待。JavaScript 只在一个线程上运行,不代表 JavaScript 引擎只有一个线程。事实上,JavaScript 引擎有多个线程,…

鸿蒙开发加强2

快速创建一个符合长度的数组 空数组 Array.from({length: 200}) // 200的长度的空数组 场景&#xff1a;只确定长度&#xff0c;不确定内容的情况 State 状态装饰器 》增强功能 数据变化了 可以引起页面的重新渲染 没有State修饰的变量&#xff0c;不会引起UI的刷新渲染 加…

php获取今天凌晨零点的时间

不废话直接上代码 //使用strtotime $midnightToday strtotime("today midnight"); //输出&#xff1a;1716998400 //如果是明天 $midnightToday 86400 //后天 $midnightToday 86400*2//ORM中比对使用 $row ModelVisit::where(uid,$this->uid)->where(visi…

C++面试十二连问(语言基础篇)

前言&#xff1a; C开发讲究的是细分领域&#xff0c;越往后越没有人在意你是不是了解语言特性&#xff0c;但是对相关行业经验的要求就越高。作为初入行业的新人&#xff0c;在经验这块是比较欠缺的&#xff0c;相比之下&#xff0c;C八股还是比较容易上手的&#xff0c;属于努…

学习整理 docker

nexus 搭建nexus将其他pom、jar导入到nexus中

java 比Scanner 更高效的输入输出 BufferedWriter和`OutputStreamWriter

BufferedWriter bw new BufferedWriter(new OutputStreamWriter(System.out)); 当我们在 Java 中需要进行字符输出时&#xff0c;可以使用 BufferedWriter 和 OutputStreamWriter 的组合来实现。下面提供更多相关信息&#xff1a; BufferedWriter&#xff1a; BufferedWriter…

硬件接口介绍(一)常用接口及其测试

目录 一、引言 二、常用接口 ------>2.1、DIMM ------>2.2、DCIN ------>2.3、SDIO 三、以太网 ------>3.1、PHY ------>3.2、MAC ------>3.3、LAN 四、MIPI ------>4.1、MIPI CSI ------>4.2、MIPI DSI 五、HDMI 六、音频 ------>6…

fintuning chatglm3

chatglm3介绍 ChatGLM3-6B 是 ChatGLM 系列最新一代的开源模型&#xff0c;在保留了前两代模型对话流畅、部署门槛低等众多优秀特性的基础上&#xff0c;ChatGLM3-6B 引入了如下特性&#xff1a; 更强大的基础模型&#xff1a; ChatGLM3-6B 的基础模型 ChatGLM3-6B-Base 采用…

ic基础|时钟篇06:crg到底是什么?一文带你了解crg中的时钟系统!

大家好&#xff0c;我是数字小熊饼干&#xff0c;一个练习时长两年半的ic打工人。我在两年前通过自学跨行社招加入了IC行业。现在我打算将这两年的工作经验和当初面试时最常问的一些问题进行总结&#xff0c;并通过汇总成文章的形式进行输出&#xff0c;相信无论你是在职的还是…

Material Design 风格的 UI 框架 Vuetify 使用初体验

不知道国外为什么这么多使用 vuetify UI 框架的&#xff0c;简单使用下来发现很多坑。持续更新 1、input 和 button 设置相同的 density&#xff0c;但是大小却不一样&#xff1b; 2、表格功能过于简单&#xff0c;没有列拖拽&#xff0c;只有左侧固定列没有右侧固定列&#…

基于Swing和socket实现双向通讯案例

server代码&#xff1a; import javax.swing.*; import java.awt.*; import java.io.*; import java.net.ServerSocket; import java.net.Socket;public class Server extends JFrame {private JTextArea messageArea;private JTextField textField;private PrintWriter write…

像艺术家一样工作:前言

名人名言 “艺术是盗窃” —— 巴勃罗毕加索 “不成熟的诗人模仿&#xff0c;成熟的诗人偷窃&#xff1b;对于偷窃得到的艺术&#xff0c;坏的诗人丑化它&#xff0c;好的诗人加入自己的理解&#xff0c;使它变得更好&#xff0c;至少会让它有点不同。最优秀的诗人&#xff0…