用户反馈组件实现(Vue3+ElementPlus)含图片拖拽上传
用户反馈组件实现(Vue3+ElementPlus)含图片拖拽上传
- 1. 页面效果
- 1.1 正常展示
- 1.2 鼠标悬浮
- 1.3 表单
- 2. 代码部分
- 1.2 html、ts
- 1.2 less部分
- 3. 编码过程遇到的问题
1. 页面效果
1.1 正常展示
1.2 鼠标悬浮
1.3 表单
2. 代码部分
1.2 html、ts
<template><Teleport><divclass="feedback"@mouseenter="() => (showText = true)"@mouseleave="() => (showText = false)"><el-popover :visible="visible" trigger="manual" placement="left" :width="510"><div class="feedback-content" @dragover="handleDragOver" @drop="handleDrop"><header class="flex"><strong>反馈中心</strong><el-link type="primary" @click="toJiraPage"> <strong>我的反馈</strong> </el-link></header><hr style="margin: 10px 0 0 -13px; border-top: 1px solid #dbdbdb" /><section><p style="margin-top: 10px; letter-spacing: 1px"><strong>尊敬的用户:</strong></p><p style="letter-spacing: 1px; text-indent: 4ch">感谢您提供诚挚的建议,我们将尽快帮您处理解决。</p><el-formref="refForm":model="fromData":rules="fromRules"label-position="top"size="large"style="margin-top: 20px"class="from-content"><el-form-itemlabel="问题类型"prop="issueType":rules="{ required: true, message: '请选择问题类型', trigger: ['blur', 'change'] }"><div class="card-list"><divv-for="t in feedbackType":key="t.name":class="['card-item', { active: fromData.issueType === t.id }]"@click="fromData.issueType = t.id">{{ t.name }}</div></div></el-form-item><el-form-item label="概述" prop="summary"><el-input v-model="fromData.summary"></el-input></el-form-item><el-form-item label="问题描述" prop="description"><el-input v-model="fromData.description" type="textarea" :rows="4"></el-input></el-form-item><el-uploadaction="none"list-type="picture-card":auto-upload="false":before-upload="beforeAvatarUpload":on-exceed="handleExceed":file-list="fromData.imgs":on-preview="handlePictureCardPreview"><el-icon><Plus /></el-icon><template #file="{ file }"><div><img class="el-upload-list__item-thumbnail" :src="file.url" alt="" /><span class="el-upload-list__item-actions"><spanclass="el-upload-list__item-preview"@click="handlePictureCardPreview(file)"><el-icon><zoom-in /></el-icon></span><spanv-if="!disabled"class="el-upload-list__item-delete"@click="handleRemove(file)"><el-icon><Delete /></el-icon></span></span></div></template></el-upload><div class="btn-row"><el-button class="btn-row-left" type="default" size="small" round @click="close">取 消</el-button><el-buttonclass="btn-row-right"size="small"type="primary"round:disabled="loading"@click="handleSubmit(refForm)">提 交</el-button></div></el-form></section><div class="dot"></div></div><template #reference><div v-if="visible" class="line"></div><div v-else class="slot-content" @click="visible = true"><ChatLineSquare class="feedback-icon" /><div v-if="showText" class="feedback-text">意见反馈 </div></div></template></el-popover></div><el-dialog v-model="dialogVisible"><img w-full :src="dialogImageUrl" alt="Preview Image" /></el-dialog></Teleport>
</template><script setup lang="ts">import { ElMessage } from 'element-plus';import type { UploadFile, ElForm, UploadProps } from 'element-plus';import { ChatLineSquare, Delete, Plus, ZoomIn } from '@element-plus/icons-vue';import { submitFeedback } from '@/api/config-center';+(() => {// 初始化数据准备。。。})();const handleDragOver = (event) => {event.preventDefault();};const allowedFormats = ['image/jpeg','image/png','image/gif','image/bmp','image/tiff','image/x-icon','image/svg+xml',] as const;const handleDrop = (event) => {event.preventDefault();const file = event.dataTransfer.files[0];if (!allowedFormats.includes(file.type)) {ElMessage.warning('只能上传 JPEG、PNG、GIF、BMP、TIFF、ICO 或 SVG 格式的图片');return;}if (fromData.value.imgs?.length >= 5) {ElMessage.warning('抱歉,最多只能上传5张图片!');return;}if (file.size > 2 * 1024 * 1024) {ElMessage.warning('图片大小不能超过 2MB');return;}const reader = new FileReader();reader.onload = () => {const image = {name: file.name,url: reader.result, // 用于页面回显raw: file, // 将图片的原始文件对象存储到 raw 属性中};fromData.value.imgs.push(image);};reader.readAsDataURL(file);};const showText = ref(false);const visible = ref(false);type FormInstance = InstanceType<typeof ElForm>;const refForm = ref<FormInstance>();const feedbackType = ref([]);const toJiraPage = () => {};const loading = ref(false);const handleSubmit = (formEl: FormInstance | undefined): void => {if (!formEl) return;formEl.validate((valid: any) => {if (valid) {loading.value = true;let fd = new FormData();fd.append('issueType', fromData.value.issueType);fd.append('summary', fromData.value.summary);fd.append('description', fromData.value.description);fromData.value.imgs.forEach((v) => fd.append('files', v.raw));submitFeedback(fd).then((res: any) => {if (res.code === 200) {ElMessage.success('反馈成功,感谢您的关注!');visible.value = false;fromData.value = {issueType: 0,summary: '',description: '',imgs: [],};} else {ElMessage.error('反馈失败:' + res.message);}}).catch((e) => ElMessage.error('反馈失败:' + e)).finally(() => (loading.value = false));} else {return false;}});};const fromData = ref({issueType: '', // 问题类型summary: '', // 概要description: '', // 描述imgs: [], // 图片});const close = () => {visible.value = false;showText.value = false;refForm.value?.resetFields();fromData.value = {issueType: '',summary: '',description: '',imgs: [],};};const dialogImageUrl = ref('');const dialogVisible = ref(false);const disabled = ref(false);const handlePictureCardPreview = (file: UploadFile) => {dialogImageUrl.value = file.url!;dialogVisible.value = true;};const handleRemove = (file: UploadFile) => {const index = fromData.value.imgs.findIndex((f: any) => f.uid === file.uid);fromData.value.imgs.splice(index, 1);};const fromRules = reactive({// issueType: [{ required: true, message: '请选择问题类型', trigger: 'blur' }],summary: [{ required: true, message: '请输入概要', trigger: 'blur' }],description: [{ required: true, message: '请输入描述', trigger: 'blur' }],});
</script>
由于我这边项目的需求,反馈组件我是和菜单组件放在一起
1.2 less部分
<style lang="less" scoped>::v-deep(.el-upload-list--picture-card .el-upload-list__item-actions span + span) {margin-left: 0.6rem !important;}::v-deep(.el-upload.el-upload--picture-card),::v-deep(li.el-upload-list__item) {width: 70px !important;height: 70px !important;}::v-deep .el-upload-dragger {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;}.feedback-content {height: 561px;position: relative;header {display: flex;justify-content: space-between;margin: 0 10px;}section {.from-content {height: 454px;// overflow-y: scroll;}.card-list {display: flex;gap: 20px;.card-item {padding: 0 20px;border-radius: 5px;background-color: #f2f3f5;border: 1px solid #dfdfdf;cursor: pointer;width: 100%;height: 35px;line-height: 35px;font-size: 12px;&.active {color: #fff;background-color: #4c7cee;}}}.upload {width: 60px;height: 60px;cursor: pointer;border: 1px dashed var(--el-border-color-darker);background-color: #fafafa;&:hover {border-color: var(--el-color-primary);color: var(--el-color-primary);}}}.dot {position: absolute;left: -12px;top: 0;width: 4px;height: 21px;border-radius: 5px;background-color: #4c7cee;}}.feedback {position: fixed;top: 50%;right: 0;color: #fff;cursor: pointer;border-radius: 6px;transform: translateY(-50%);background-color: #4c7cea;z-index: 999999999999;.line {width: 7px;height: 100px;border-radius: 6px;background-color: #4c7cea;}.feedback-text {letter-spacing: 0.3em;writing-mode: vertical-lr;text-orientation: upright;}@media only screen and (min-width: 1280px) {.slot-content {margin: 6px;.feedback-icon {width: 24px;height: 24px;margin-bottom: 5px;}.feedback-text {font-size: 16px;}}}@media only screen and (max-width: 1280px) {.slot-content {margin: 3px;.feedback-icon {width: 19px;height: 19px;margin-bottom: 3px;}.feedback-text {font-size: 13px;}}}}.btn-row {margin: 16px 8px 0;text-align: end;&-left {border-color: #4c7cee;color: #4c7cee;}}
</style>
3. 编码过程遇到的问题
- Teleport 是 Vue3 的一个内置组件,详细使用请查阅 Vue3官网
- 关于图片拖拽
- 最初的时候,是采用
el-upload
的drag
属性,来实现,但是后面有用户提出拖拽上传目标的框太小,建议可以把图片拖拽进整个表单,最开始时候的想法是在最外层的div加一个拖拽事件,但是实现起来有一个问题,el-upload
拖拽事件添加.stop
,会造成下方区域无法实现拖拽上传,其他区域OK,后采取的解决方式是,el-upload
去除拖拽属性,全部采用最外层的原生拖拽事件上传
- 最初的时候,是采用
- 图片的上传
图片需要和文字一起上传,最初的时候实在没有想到实现方式,后面查了好些文章,发现是通过FormData
实现
相关文章:
用户反馈组件实现(Vue3+ElementPlus)含图片拖拽上传
用户反馈组件实现(Vue3ElementPlus)含图片拖拽上传 1. 页面效果1.1 正常展示1.2 鼠标悬浮1.3 表单 2. 代码部分1.2 html、ts1.2 less部分 3. 编码过程遇到的问题 1. 页面效果 1.1 正常展示 1.2 鼠标悬浮 1.3 表单 2. 代码部分 1.2 html、ts <templ…...
K8S部署nginx并且使用NFS存储数据
安装NFS 在master安装NFS systemctl start nfs-server修改配置 /etc/exports /data *(rw,no_root_squash,no_all_squash,sync)目录为 /data 允许所有地址访问 验证下 [rootmaster nginx]# showmount -e 192.168.57.61 Export list for 192.168.57.61: /data *共享可以正常…...
Homework 3: Higher-Order Functions, Self Reference, Recursion, Tree Recursion
Q1: Compose 编写一个高阶函数composer,它返回两个函数func和func_adder。 func是一个单参数函数,它应用到目前为止已经组合的所有函数。这些函数将首先应用最新的函数(参见doctests和示例)。 func_adder用于向我们的组合添加更多…...
(C++)有效三角形的个数--双指针法
个人主页:Lei宝啊 愿所有美好如期而遇 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://le…...
11.30BST理解,AVL树操作,定义;快速幂,二分求矩阵幂(未完)
完全二叉树结点的度可能有1,满二叉树的度只能为0或2 BST构建 BST是左孩子都比根节点小,右孩子都比根节点大 二叉搜索树的插入,删除,调整 平衡树理解 任何一个平衡二叉树,它的中序遍历都是一样的,都是有…...
深入理解Java核心技术:Java工程师的实用干货笔记
💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 在Java工程师的职业生涯中,深入理解…...
大学里面转专业介绍
目录 个人情况转专业过程中的经验分享转专业后的学习建议和心态调整转专业后的时间平衡 个人情况 信息科学与工程学院计算机科学与技术专业2019级本科生,曾从物理与微电子科学学院后转入信息科学与技术学院。学习成绩连续三年专业前10% 项目:爬虫项目、…...
MySQL_1. mysql数据库介绍
shell脚本差不多快完结了接下来会为大家更新MySQL系列的相关的基础知识笔记,希望对大家有所帮助,好废话不多说,接下来开始正题! 1.mysql数据库介绍 mysql 是一款安全、跨平台、高效的,并与 PHP、Java 等主流编程语言…...
TimeGPT:时间序列预测模型实例
时间序列预测领域正在经历一个非常激动人心的时期。在过去的三年里,我们见证了许多重要的贡献,如N-BEATS、N-HiTS、PatchTST和TimesNet等。同时,大型语言模型(LLM)近来在流行度方面取得了很大的成功,例如Ch…...
【JavaEE】多线程 (1)
目录 1. 认识线程(Thread) 1) 线程是什么 2) 为啥要有线程 3) 进程和线程的区别 2.第⼀个多线程程序 3.多线程的其他创建方式 方法二:实现 Runnable 接⼝ 方法三:匿名内部类 方法四:实现Runable, 重写run, 匿名内部类 方法五:使用lambda表达式…...
linux 应用层同步与互斥机制之条件变量
2、条件变量 互斥量防止多个线程同时访问同一共享变量。(我们称为互斥) 有一种情况,多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务,满足了某一个条件,线程A才能继续执行。&…...
3.5毫米音频连接器接线方式
3.5毫米音频连接器接线方式 耳机插头麦克风插头 绘制电路图注意事项 3.5毫米音频连接器分为单声道开关型和无开关型如下图: sleeve(套筒) tip(尖端) ring(环) 耳机插头 麦克风插头 绘制电路图…...
智慧农田可视化大数据综合管理平台方案,EasyCVR助力农业高质量发展
一、背景需求 我国是农业大国,农业耕地面积达到20亿亩。随着物联网、大数据、人工智能等新一代信息技术与农业农村加速融合,以及国家对农业的重视,智慧农业对于我国农业现代化建设和实施乡村振兴战略具有重大引领与推动作用。在传统农田生产…...
python超详细基础文件操作【建议收藏】
文章目录 前言1 文件操作1.1 文件打开与关闭1.1.1 打开文件1.1.2 关闭文件 1.2 访问模式及说明 2 文件读写2.1 写数据(write)2.2 读数据(read)2.3 读数据(readlines)2.3 读数据(readline&#x…...
华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级
华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级 TPM(Transformation Progress Metrics,变革进展指标)用来衡量管理体系在华为的推行程度和推行效果,并找出推行方面的不足与问题,…...
vue el-select多选封装及使用
使用了Element UI库中的el-select和el-option组件来构建多选下拉框。同时,也包含了一个el-input组件用于过滤搜索选择项,以及el-checkbox-group和el-checkbox组件用于显示多选项。 创建组件index.vue (src/common-ui/selectMultiple/index.vue) <tem…...
大模型上下文学习(ICL)训练和推理两个阶段31篇论文
大模型都火了这么久了,想必大家对LLM的上下文学习(In-Context Learning)能力都不陌生吧? 以防有的同学不太了解,今天我就来简单讲讲。 上下文学习(ICL)是一种依赖于大型语言模型的学习任务方式…...
WordPress(安装比子主题文件)zibll-7.5.1
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、新建网站二、配置ssl三.配置伪静态四.上传文件五.添加本地访问前言 提示:这里可以添加本文要记录的大概内容: 首先,我们要先理解什么是授权原理。 原理就是我们大家运营网站,点击授权…...
蓝桥杯 动态规划
01 数字三角形 #include<bits/stdc.h> using namespace std; const int N105; using lllong long; ll a[N][N],dp[N][N]; int main(){int n;cin>>n;for(int i1;i<n;i){for(int j1;j<i;j){cin>>a[i][j];}}for(int i5;i>1;i--){for(int j1;j<i;j){…...
【图论】重庆大学图论与应用课程期末复习资料2-各章考点(计算部分)(私人复习资料)
图论各章考点 二、树1、避圈法(克鲁斯克尔算法)2、破圈法3、Prim算法 四、路径算法1、Dijkstra算法2、Floyd算法 五、匹配1、匈牙利算法(最大权理想匹配(最小权权值取反)) 六、行遍性问题1、Fleury算法&…...
整数和浮点数在内存中的存储(大小端详解)
目录 一、整数在内存中的存储 二、大小端字节序和字节序判断 2.1为什么有大小端? 2.2请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)-百度笔试题 方法一(char*强制类型转换)…...
SpringBoot 集成 ChatGPT,实战附源码
1 前言 在本文中,我们将探索在 Spring Boot 应用程序中调用 OpenAI ChatGPT API 的过程。我们的目标是开发一个 Spring Boot 应用程序,能够利用 OpenAI ChatGPT API 生成对给定提示的响应。 您可能熟悉 ChatGPT 中的术语“提示”。在 ChatGPT 或类似语…...
数据结构——希尔排序(详解)
呀哈喽,我是结衣 不知不觉,我们的数据结构之路已经来到了,排序这个新的领域,虽然你会说我们还学过冒泡排序。但是冒泡排序的性能不高,今天我们要学习的希尔排序可就比冒泡快的多了。 希尔排序 希尔排序的前身是插入排…...
C++ day53 最长公共子序列 不相交的线 最大子序和
题目1:1143 最长公共子序列 题目链接:最长公共子序列 对题目的理解 返回两个字符串的最长公共子序列的长度,如果不存在公共子序列,返回0,注意返回的是最长公共子序列,与前一天的最后一道题不同的是子序…...
ubuntu中删除镜像和容器、ubuntu20.04配置静态ip
1 删除镜像 # 短id sudo docker rmi 镜像id # 完整id sudo docker rmi 镜像id# 镜像名【REPOSITORY:TAG】 sudo docker rmi redis:latest2 删除容器 # 删除某个具体容器 sudo docker rm 容器id# 删除Exited状态/未运行的容器,三种命令均可 sudo docker rm docker …...
华为手环 8 五款免费表盘已上线,请注意查收
华为手环 8,作为一款集时尚与实用于一体的智能手环,不仅具备强大的功能,还经常更新的表盘样式,让用户掌控时间与健康的同时,也能展现自己的时尚品味。这不,12 月官方免费表盘又上新了,推出了五款…...
JOSEF约瑟 同步检查继电器DT-13/200 100V柜内安装,板前接线
系列型号 DT-13/200同步检查继电器; DT-13/160同步检查继电器; DT-13/130同步检查继电器; DT-13/120同步检查继电器; DT-13/90同步检查继电器; DT-13/254同步检查继电器; 同步检查继电器DT-13/200 100V柜内板前接线 一、用途 DT-13型同步检查继电器用于两端供电线路的…...
龙迅#LT8311X3 USB中继器应用描述!
1. 概述 LT8311X3是一款USB 2.0高速信号中继器,用于补偿ISI引起的高速信号衰减。通过外部下拉电阻器选择的编程补偿增益有助于提高 USB 2.0 高速信号质量并通过 CTS 测试。 2. 特点 • 兼容 USB 2.0、OTG 2.0 和 BC 1.2• 支持 HS、FS、LS 信令 • 自动检测和补偿 U…...
eclipse jee中 如何建立动态网页及服务的设置问题
第一次打开eclipse 时,设置工作区时,一定是空目录 进入后 File-----NEW------Dynamic Web Project 填 项目名,不要有大写 m1 next next Generate前面打对勾 finish 第一大步: window----Preferences type filter text 处填 :Serve…...
一张网页截图,AI帮你写前端代码,前端窃喜,终于不用干体力活了
简介 众所周知,作为一个前端开发来说,尤其是比较偏营销和页面频繁改版的项目,大部分的时间都在”套模板“,根本没有精力学习前端技术,那么这个项目可谓是让前端的小伙伴们看到了一丝丝的曙光。将屏幕截图转换为代码&a…...
大连的网站制作公司/百度一下就知道百度首页
Apache的mod_rewrite是提供了强大URL操作的杀手级的模块,可以实现几乎所有你梦想的URL操作类型,其代价是你必须接受其复杂性,因为mod_rewrite的主要障碍就是初学者不容易理解和运用,即使是Apache专家有时也会发掘出mod_rewrite的新…...
wordpress发信设置/许昌网站推广公司
1.安装依赖 pip install --upgradetools pip install numpy Matplotlib 2.安装opencv-python(网络一定要通畅) pip install opencv-python...
用js做的个人酷炫网站/百度招聘
js算法一、关于排序1.1冒泡排序1.2 快速排序二、反向输出2.1 数组反向输出2.2 字符串反向输出三、数学方法3.1 阶乘的实现3.2 生成斐波那契数列四、回文串五、变量值互换六、计算出现次数6.1 计算字符串里出现最多的字符及次数6.2 计算数组里出现最多的元素及次数一、关于排序 …...
西安当地做网站的公司/网络营销的概念及特征
毕业大半年了,现在还清晰的记得当时毕业论文不会用SPSS的痛苦,每天挣扎把度娘、知乎、知网、优酷、某宝等各大网站都逛了个遍,依然没有找到用SPSS完整的分析一份问卷的流程,几乎都是零零散散的一些知识,又或是几十个视…...
阿里云 做网站 百度开放云/百度指数免费查询
为大家分享一款专业的音乐标签及管理工具,Yate for mac主要用于标记和整理你的音频文件,你可以轻松编辑音乐文件的元标签数据,Yate for mac支持AIFF、DFF、FLAC、M4A、M4B、MP3、MP4等格式的音频文件,不管是自动还是手动标记文件都…...
做渠道的网站有哪些/seo推广费用需要多少
上篇文章 《Nacos 配置中心原理》我和大家分析了 Nacos 的配置中心原理,主要分析了 Nacos 客户端是如何感知到服务端的配置变更的,但是只是从客户端的角度进行了分析,并没有从服务端的角度进行分析,本篇文章我将结合服务端从两个角…...