封装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智能…

鸿蒙开发加强2

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

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;相信无论你是在职的还是…

基于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…

After Effects 2022(AE2022)支持win版和mac版下载

​After Effects 2022 是由Adobe公司推出的一款专业视频后期制作软件&#xff0c;它主要用于视频合成、视频特效制作、视频剪辑、动画制作等领域。After Effects 2022 内置了丰富的特效和过渡效果&#xff0c;用户可以通过它进行高级的视频合成和动画制作。 该软件具有直观的用…

一文搞懂分布式事务Seta-AT模式实现原理

分布式事务概念 分布式事务&#xff08;Distributed Transaction&#xff09; 是指在分布式系统中&#xff0c;涉及多个数据库、服务、消息队列等资源&#xff0c;并且需要保证这些资源上的操作要么全部成功提交&#xff0c;要么全部失败回滚的一种机制。在分布式系统中&#…

IO流---字节流.Java

一&#xff0c;概述 IO流是存储和读取数据的解决方案。 I&#xff1a;input O:output流&#xff1a;像水流一样传输数据 因为IO流与File是息息相关的&#xff0c;所以在学习IO流之前&#xff0c;简单回顾一下File&#xff1a;&#x1f604;&#x1f60a;&#…

python对文本操作,生成可执行文件

.exe文件主要包含pingmianF.py文件和read_inp_auto.py文件 实现效果 代码 read_inp_auto.py #-*- coding: utf-8 -*- import re import sys import os import os.path import time import pingmianF from pingmianF import vector import numpy as np from tkinter import me…

GDPU JavaWeb EL与JSTL

标签化&#xff0c;可以简化百分号的繁忙。 标签库配置 先下载好jstl标签库&#xff0c;然后放到lib。接着&#xff0c;要让编译器识别到&#xff0c;因此要在模块配置依赖&#xff0c;这里导jar包都得注意一下模块依赖。 也可以在libraries项目库设置。 EL与JSTL查询图书 在…

2024华为OD机试真题-攀登者1-C++(C卷D卷)

题目描述 攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。 地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。 其中数组元素0代表地面。 例如: [0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图, 地图中有两个山脉位置分别为1,2,3,4,5 …

设计模式(十)结构型模式---享元模式

文章目录 享元模式简介结构UML图具体实现UML图代码实现 享元模式简介 享元模式&#xff08;fly weight pattern&#xff09;主要是通过共享对象来减少系统中对象的数量&#xff0c;其本质就是缓存共享对象&#xff0c;降低内存消耗。享元模式将需要重复使用的对象分为两个状态…