用户反馈组件实现(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算法&…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...

【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...

【Linux系统】Linux环境变量:系统配置的隐形指挥官
。# Linux系列 文章目录 前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变量的生命周期 四、环境变量的组织方式五、C语言对环境变量的操作5.1 设置环境变量:setenv5.2 删除环境变量:unsetenv5.3 遍历所有环境…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...