用户反馈组件实现(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算法&…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...

Qemu arm操作系统开发环境
使用qemu虚拟arm硬件比较合适。 步骤如下: 安装qemu apt install qemu-system安装aarch64-none-elf-gcc 需要手动下载,下载地址:https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x…...
多元隐函数 偏导公式
我们来推导隐函数 z z ( x , y ) z z(x, y) zz(x,y) 的偏导公式,给定一个隐函数关系: F ( x , y , z ( x , y ) ) 0 F(x, y, z(x, y)) 0 F(x,y,z(x,y))0 🧠 目标: 求 ∂ z ∂ x \frac{\partial z}{\partial x} ∂x∂z、 …...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...