本文介绍项目前端功能之文件上传的设计开发流程
一、站在巨人的肩膀上:设计参考
可以看出来文件上传按钮一般设置在输入框旁边(当然一般也不会设置在别的地方...),同时一般用图标表达“上传文件”的信息,美观而又易懂。
可以看出来文件上传之后放在了输入框的上部,有图标为衬更加美观,以及有文件相关信息的介绍如文件名、后缀、文件大小等。同时右上角都有小叉叉用以删除上传的文件。
携带文件发送提问时文件信息也要展示。
二、功能分解
1、样式设计:如上述,包括按钮位置、图标、文件信息展示等
2、文件上传按钮:点击按钮能够打开文件选择器选择文件
3、文件信息展示与管理:存在已上传的文件应能展示,应展示已上传的文件的文件名等信息,允许删除上传的文件
4、聊天框文件展示:在携带文件发送提问后,在聊天框应能展示文件的信息
三、解决方案与代码编写
1、样式设计与效果展示:相关图标使用的是Font awesome svg图标库。代码直接复制svg部分即可使用
2、文件上传按钮:支持多文件上传
- 放置了一个button用于交互提示,一个隐藏状态的input用于打开文件选择器,同时button响应点击事件去触发input以打开文件选择器。
<button @click.stop.prevent="openFileSelector" class="absolute p-1 rounded-md text-gray-500 bottom-1.5 left-1 md:bottom-2.5 md:left-2 hover:bg-gray-100 dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent"> <svg data-v-11241ba2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="folder-open" role="img" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" class="svg-inline--fa fa-folder-open fa-w-18"> <path data-v-11241ba2="" fill="currentColor" d="M572.694 292.093L500.27 416.248A63.997 63.997 0 0 1 444.989 448H45.025c-18.523 0-30.064-20.093-20.731-36.093l72.424-124.155A64 64 0 0 1 152 256h399.964c18.523 0 30.064 20.093 20.73 36.093zM152 224h328v-48c0-26.51-21.49-48-48-48H272l-64-64H48C21.49 64 0 85.49 0 112v278.046l69.077-118.418C86.214 242.25 117.989 224 152 224z" class="" data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;"></path> </svg> </button> <input type="file" ref="fileInput" @change="handleFileSelection" accept=".doc,.docx,.txt,.pdf,.png,.jpg" style="display: none;" multiple="multiple">
3、输入框文件信息展示:
- 首先需要设置一个变量用于存储上传的文件,作为之后文件信息展示的基础
data(){ return{ ...... file_list:{}, //存储打开的文件 now_file_num:0, //当前打开文件数 max_file_num:9, //最大上传文件数 } }
- 定义一个函数处理上传文件这一事件,input组件绑定到这一函数上来
handleFileSelection(event) { // 获取选择的文件 const files = event.target.files; var now_file_num=this.now_file_num; //判断文件数是否超限 if((now_file_num+files.length)>this.max_file_num){ alert(`最多允许上传${this.max_file_num}个文件哦!还可以上传${this.max_file_num-now_file_num}个文件`); return; } for(let i=0;i<files.length;i++){ // 将选择的文件存储,将时间戳作为唯一id this.file_list[Date.now().toString(36)+i]=files[i]; now_file_num++; } this.now_file_num=now_file_num; this.$refs.fileInput.value = ""; },
- 文件信息的展示、文件删除,关键在于布局设置,力求和谐美观
<div v-show="now_file_num>0" class="flex flex-row gap-2 w-full py-2 flex-grow md:py-3 md:pl-4 relative border border-black/10 bg-white dark:border-gray-900/50 dark:text-white dark:bg-gray-700 rounded-md shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]" > <div v-for="(file,file_id,index) in file_list" :key="index" style="display: flex; align-items: center; width: calc(1/10 * 100%); position: relative; padding: 8px; " class="bg-gray-200 rounded-lg"> <svg data-v-11241ba2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" class="svg-inline--fa fa-file fa-w-12" height="1em" width="1em"> <path data-v-11241ba2="" fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z" class="" data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;"></path> </svg> <!--文件信息框--> <div class="file-info text-gray-600" style="margin-left: 10px; flex-grow: 1; display: flex; flex-direction: column;overflow:hidden;width: calc(3/4 * 100%);height: 40px; padding:1px;"> <!-- 文件名 --> <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;max-width:100%;line-height: 20px;">{{ fileNameWithoutExtension(file?.name) }}</span> <!-- 文件扩展 --> <span style="line-height: 20px;">{{ fileExtension(file?.name) }}</span> </div> <!--删除文件按钮--> <button @click.stop.prevent="deleteFile(file_id)" class="btn-right-top dark:hover:text-gray-400 dark:hover:bg-gray-900 disabled:hover:bg-transparent dark:disabled:hover:bg-transparent" style="position: absolute;top:0;right:0;"> <svg data-v-11241ba2="" aria-hidden="true" focusable="false" data-prefix="far" data-icon="times-circle" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" class="svg-inline--fa fa-times-circle fa-w-16" height="1em" width="1em"> <path data-v-11241ba2="" fill="currentColor" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z" class="" data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;"></path> </svg> </button> </div> </div>
效果:
4、发送消息后,聊天框文件信息展示,表示该消息是带有文件的,但是不负责具体的文件管理。关键也是布局设置,力求和谐美观
<div v-show="conv.has_file"> <!--若聊天信息有文件则展示-->
<div class="flex flex-row gap-2 w-full py-2 md:py-3 md:pl-4 relative border border-black/10 bg-white dark:border-gray-900/50 dark:text-white dark:bg-gray-700 rounded-md shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]" >
<div v-for="(file,file_id,index) in conv.file_list" :key="index" style="display: inline-flex; align-items: center; width: calc(10%); padding: 8px;" class="bg-gray-200 rounded-lg ">
<!--图标-->
<svg data-v-11241ba2="" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="file" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" class="svg-inline--fa fa-file fa-w-12" height="1em" width="1em">
<path data-v-11241ba2="" fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm160-14.1v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z" class="" data-darkreader-inline-fill="" style="--darkreader-inline-fill:currentColor;"></path>
</svg>
<!--文件信息框-->
<div class="file-info text-gray-600" style="margin-left: 10px; display: inline-flex; overflow: hidden; padding: 1px;width: calc(3/4 * 100%)">
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle;min-width: 100px;">{{ file||index }}</span>
</div>
</div>
</div>
</div>
效果:
至此大功告成。最麻烦耗时的还是样式的不居中、不对齐、撑大、缩小等问题,需要不断判断调试、追根溯源、解决问题,代码逻辑倒在其次。