vue3 + fastapi 实现选择目录所有文件自定义上传到服务器

news2024/12/28 20:02:42

文章目录

    • ⭐前言
      • 💖 技术栈选择
    • ⭐前端页面搭建
      • 💖 调整请求content-type传递formData
    • ⭐后端接口实现
      • 💖 swagger文档测试接口
    • ⭐前后端实现效果
      • 💖 上传单个文件
      • 💖 上传目录文件
    • ⭐总结
    • ⭐结束

yma16-logo

⭐前言

大家好,我是yma16,本文分享关于vue3 + fastapi 实现选择目录文件上传到服务器指定位置。
vue3系列相关文章:
前端vue2、vue3去掉url路由“ # ”号——nginx配置
csdn新星计划vue3+ts+antd赛道——利用inscode搭建vue3(ts)+antd前端模板
认识vite_vue3 初始化项目到打包
python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示
python系列文章:
python爬虫_基本数据类型
python爬虫_函数的使用
python爬虫_requests的使用
python爬虫_selenuim可视化质量分
python爬虫_django+vue3可视化csdn用户质量分
python爬虫_正则表达式获取天气预报并用echarts折线图显示
python爬虫_requests获取bilibili锻刀村系列的字幕并用分词划分可视化词云图展示
python爬虫_selenuim登录个人markdown博客站点
python爬虫_requests获取小黄人表情保存到文件夹
python_selenuim获取csdn新星赛道选手所在城市用echarts地图显示

💖 技术栈选择

前端:vue3 + ts + antd
后端:python + fastapi

vue3优势
Vue3相比较于Vue2有以下几个优势:

  1. 更快的渲染速度:Vue3通过重新设计响应式系统和虚拟DOM,可以实现更快的渲染速度。在内存使用和性能方面,Vue3比Vue2更加高效。

  2. 更好的TypeScript支持:Vue3更好地支持TypeScript,TypeScript在Vue3中的使用更加直接、正式、稳定,并且类型推导更加准确。

  3. 更好的组件化开发:Vue3可以更方便地编写组件,将模板、脚本和样式分离开来,使得代码更加易读易维护。

  4. 更好的开发体验:Vue3增加了很多新的特性,如Composition API、Teleport、Suspense等,这些特性使得开发过程更加简单、便捷、灵活。

  5. 更多的生态支持:随着Vue3的面世,越来越多的插件和库开始支持Vue3,例如Vue Router、Vuex等,这些生态工具的发展将有助于Vue3的快速发展。

fastapi优势
FastAPI的优势主要体现在以下几个方面:

  1. 高性能:FastAPI使用异步编程模型,使用基于事件循环的异步处理请求,可以轻松处理大量的并发请求,提高服务器性能。

  2. 简单易用的API开发:FastAPI能够自动生成API文档,因此开发者可以通过它来快速地编写API,而不必花费大量时间去编写文档。

  3. 高可靠性:FastAPI 自动进行类型检查,能够避免类型错误引起的运行时错误,提高了API的稳定性。

  4. 支持原生Python语法:FastAPI可以使用Python原生语法来编写代码,不需要学习新的语言,可以更方便地使用Python的生态系统。

  5. 兼容多种前端框架:FastAPI 可以与多种前端框架配合使用,包括React、Angular、Vue.js等,提供了更大的开发自由度。

  6. 广泛的社区支持:FastAPI社区非常活跃,拥有大量的开发者和用户,提供了丰富的资源和支持。

⭐前端页面搭建

布局:
上下结构
上方为选择目录
下方为选择文件夹
实现效果图如下
upload

vue3 语法糖代码实现

<script  lang="ts" setup>
import { ref,reactive,computed } from 'vue';
import { InboxOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { uploadFile,uploadUrl } from "../../service/gpt/index";
import { UploadOutlined } from '@ant-design/icons-vue';
const state:any=reactive({
    fileList:[],
    loading:false,
    text:'',
    dirList:[],
    dirPath:'',
    customFile:null,
    activeKey:'1',
    movieUrl:''
});

const upUrl=async ()=>{
    state.loading=true
    try{
        const res=await uploadUrl({
            url:state.movieUrl
        })
        console.log('res',res)
    }
    catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}

const remove=(e:any)=> {
  console.log('drop file',e);
    state.fileList=[]
}

const removeDir=(e:any)=>{
    state.dirList=state.dirList.filter((file:any)=>file.uid!==e.uid)
}


const customRequesHandle=(e:any)=>{
    console.log(e,'custom')
}

const beforeUpload = (file:any) => {
    console.log('file before',file)
    state.fileList=[file]
    return false;
};

const beforeUploadDir = (file:any) => {
    state.dirList.push(file)
    return false;
};

const uploadSingleFile= async ()=>{
    state.loading=true
    console.log(typeof state.fileList[0],'file 类型')
    try{
        const formData=new FormData();
        formData.append('file',state.fileList[0])
        const res=await uploadFile(formData)
        console.log('res',res)
    }catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}

const upBtnDisabled=computed(()=>{
    return state.fileList.length===0
})

    const change=(e:any)=>{
    console.log('change e',e)
    }
const upDir=async ()=>{
    if(state.dirList.length===0){
        return message.warning('请选择文件夹!')
    }
    state.loading=true
    const paramsData:any={
        dirList:state.dirList,
        dirPath:state.dirPath,
    }
    try{
        state.dirList.forEach(async (file:any)=>{
            try{
                const formData=new FormData();
                formData.append('file',file)
                const res=await uploadFile(formData)
                console.log('res',res)
            }catch(r){
                message.error(JSON.stringify(r))
            }
        })
    }catch (e) {
        message.error(JSON.stringify(e))
    }
    finally {
        setTimeout(()=>{
            state.loading=false
        },200)
    }
}

const previewDirFile=async (file:any)=>{
    return new Promise(resolve=>resolve(false))
}
</script>
<template>
    <div>
        <a-spin :spinning="state.loading" tip="upload...">

        <div class="header-tools">
        </div>
            <a-tabs v-model:activeKey="state.activeKey">
                <a-tab-pane key="1" tab="上传文件">
                    <div>
                        上传文件夹
                       <div style="margin: 5px;border: 1px dotted #1890ff;padding: 20px">
                           <div style="margin: 10px 0;max-height: 200px;overflow: auto">

                               <a-upload :before-upload="beforeUploadDir" v-model:file-list="state.dirList"
                                         list-type="picture"
                                         @remove="removeDir" directory>
                                   <a-button>
                                       <upload-outlined></upload-outlined>
                                       上传文件夹
                                   </a-button>
                               </a-upload>
                               <div >

                               </div>
                           </div>
                           <div style="margin:10px 0">
                               <a-button type="primary" block @click="upDir" :disabled="state.dirList.length===0" >点击开始解析文件夹</a-button>
                           </div>

                       </div>

                        上传单文件
                        <div style="margin: 5px;border: 1px dotted #1890ff;padding: 20px">
                        <div>
                            <a-upload-dragger
                                    :file-list="state.fileList"
                                    list-type="picture"
                                    :multiple="false"
                                    :before-upload="beforeUpload"
                                    @remove="remove"
                                    @change="change"
                            >
                                <p class="ant-upload-drag-icon">
                                    <inbox-outlined></inbox-outlined>
                                </p>
                                <p class="ant-upload-text">点击上传或者拖拽到这</p>
                                <p class="ant-upload-hint">
                                    选择文件
                                </p>
                            </a-upload-dragger>
                        </div>
                        <div style="margin:10px 0">
                            <a-button type="primary" block @click="uploadSingleFile" :disabled="upBtnDisabled">点击开始上传文件</a-button>
                        </div>
                        </div>
                    </div>
                </a-tab-pane>
            </a-tabs>
        </a-spin>
    </div>
</template>
<style>
    .header-tools{
        text-align: center;
        font-size: 24px;
        font-weight: bold;
    }
    .content-box{

    }
    .des{
        margin:20px 0;
    }
</style>

💖 调整请求content-type传递formData

axios封装

import axios from "axios";

// 实例
const createInstance = (baseURL:string)=>{
    return axios.create({
        baseURL:baseURL,
        timeout: 10000,
        headers: {'X-Custom-Header': 'yma16'}
    })
};

// @ts-ignore
const http:any=createInstance('');


// 添加请求拦截器
http.interceptors.request.use(function (config:any) {
    // 在发送请求之前做些什么
    return config;
}, function (error:any) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
http.interceptors.response.use(function (response:any) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error:any) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});

// 文件上传
const createUploadInstance = (baseURL:string)=>{
    return axios.create({
        baseURL:baseURL,
        timeout: 10000,
        headers: {"Content-Type": "multipart/form-data"}
    })
};

// @ts-ignore
const uploadHttp:any=createUploadInstance('');


// 添加请求拦截器
uploadHttp.interceptors.request.use(function (config:any) {
    // 在发送请求之前做些什么
    return config;
}, function (error:any) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
uploadHttp.interceptors.response.use(function (response:any) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
}, function (error:any) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});

export {http,uploadHttp};

service对接后端

import {uploadHttp} from "../../http/index";
export const uploadFile: any = (formData: any) => {
    return uploadHttp.post("/api/uploadFile/action", formData);
};

⭐后端接口实现

安装环境

pip install uvicorn
pip install fastapi
pip install python-multipart

pipi install

上传单个文件接口实现:

from fastapi import FastAPI, status, File, Form, UploadFile
from fastapi import FastAPI, status, File, Form, UploadFile
from fastapi.middleware.cors import CORSMiddleware
import os

app = FastAPI()
# 跨域配置
origins = [
    "http://localhost:3000",
]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/api")
async def root():
    return {"data": "fast api!"}


# 上传文件
@app.post("/api/uploadFile/action")
async def create_file(
    file:UploadFile
):
    writeBytes('./media',file)
    return {
        'code':200,
        "msg":'success'
    }

# 将file写入dirs目录文件
def writeBytes(dirs,file):
    bytesFile=file.file.read()
    filename=file.filename
    if not os.path.exists(dirs):
        os.makedirs(dirs)
    with open(dirs+'/'+ filename, "wb") as f:
        f.write(bytesFile)

uvicorn运行fastapi

uvicorn server.main:app --reload --port 7777

💖 swagger文档测试接口

swagger文档地址:
http://ip:port/docs

上传成功!
upload

⭐前后端实现效果

💖 上传单个文件

uploadFile

💖 上传目录文件

uploadDir
上传目录文件的接口实现:

  • file为二进制文件
  • dir为目录名称
  • name为完整的文件名称
# 上传目录文件
@app.post("/api/uploadDirFile/action")
async def uploadDirFile(
    file:UploadFile,
    dir:str=Form(),
    name:str=Form()
):
    print(dir,'dir_____________')
    writeBytes('./media/'+dir,name,file)
    return {
        'code':200,
        "msg":'success'
    }

# 将二进制数据写入目录文件
def writeBytes(dirs,name,file):
    bytesFile=file.file.read()
    filename=name
    if not os.path.exists(dirs):
        os.makedirs(dirs)
    with open(dirs+'/'+ filename, "wb") as f:
        f.write(bytesFile)

⭐总结

文件上传注意事项
前端:

  1. 请求头配置 headers: {"Content-Type": "multipart/form-data"}
  2. 参数传递使用new FormData()

后端:

  1. 接受参数使用 Uploadfile格式
  2. 解析文件内容名称包括类型按格式写入文件

multipart/form-data

multipart/form-data 是一种常用的 HTTP 请求方法,通常用于上传文件或大量数据。它将请求的数据分成多个部分(part),每一部分使用一个 boundary 分隔符来分开,每个部分包含一个头部和一个内容体,头部描述了该部分的属性,如数据类型、数据编码等。在 HTTP 消息体中,每个部分之间必须以 “–boundary\r\n” 开始,以 “–boundary–\r\n” 结束,即在结尾处添加额外的 “–” 标记。在客户端使用该方法请求时,需要明确指定请求头中的 Content-Type 为 multipart/form-data。服务端接收到该请求后,需要解析出每个部分中的请求数据。

⭐结束

本文分享到这结束,如有错误或者不足之处欢迎指出!
scene

👍 点赞,是我创作的动力!
⭐️ 收藏,是我努力的方向!
✏️ 评论,是我进步的财富!
💖 感谢你的阅读!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1117733.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE5场景逐渐变亮问题

1、显示 -- 关闭眼部适应 2、项目设置 -- 关闭自动曝光 参考&#xff1a; 虚幻5/UE5 场景亮度逐渐变亮完美解决方法 - 哔哩哔哩

2024免费的苹果电脑杀毒软件cleanmymac X

苹果电脑怎么杀毒&#xff1f;这个问题自从苹果电脑变得越来越普及&#xff0c;苹果电脑的安全性问题也逐渐成为我们关注的焦点。虽然苹果电脑的安全性相对较高&#xff0c;但仍然存在着一些潜在的威胁&#xff0c;比如流氓软件窥探隐私和恶意软件等。那么&#xff0c;苹果电脑…

mac苹果电脑使用耳机听不到声音

大家在使用耳机收听音乐时候&#xff1f;是否经常遇到声音和音频播放问题的情况。这里小编为大家带来了三种不同的方法&#xff0c;帮助大家解决耳机在macOS系统电脑上怎么听不到任何声音的教程。如果大家对这篇文章感兴趣&#xff0c;那就来看下面的具体步骤吧。 方法一、检查…

平行进口美规,加版奔驰S500 S580更换主机,汉化导航,语音交互等功能

平行进口美规&#xff0c;加版奔驰S500 S580更换中规主机后&#xff0c;有中国地图导航&#xff0c;AR实景画面&#xff0c;中文你好奔驰&#xff0c;汉化摄氏度&#xff0c;激活自动变道&#xff0c;增强型抬头显示还可以实现箭头指示功能&#xff0c;原车带流星雨大灯还可以实…

人均瑞数系列,瑞数 6 代 JS 逆向分析

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…

“时尚设计 时尚原创”首届广州(三元里)时尚设计大赛正式起航

10月18日上午&#xff0c;由广州市商务局、广州市工业和信息化局、白云区人民政府指导&#xff0c;白云区科技工业商务和信息化局、白云区三元里街道办事处主办&#xff0c;广东省皮具商会、三元里街工商业联合会承办&#xff0c;白云世界皮具贸易中心作为执行单位的首届广州(三…

免费高清壁纸下载(静态和动态壁纸)

一、网址下载&#xff08;静态壁纸&#xff09; 高清图片直接另存为就可以了。然后在电脑空白处右键——个性化设置即可替换壁纸。 ①网址&#xff1a;https://www.hippopx.com ②极简壁纸&#xff1a;https://bz.zzzmh.cn/index ③彼岸图网&#xff1a;http://pic.netbian…

大模型基础——大模型范式

大模型背后的范式 整个预训练语言模型的使用范式&#xff1a; 对于预训练模型&#xff0c;最核心的要素是从无标注的数据中去学习&#xff0c;通过自监督的一些任务去做预训练&#xff0c;得到丰富的知识。在具体的应用中&#xff0c;会引入一些任务相关的数据&#xff0c;去调…

Leetcode—2525.根据规则将箱子分类【简单】

2023每日刷题&#xff08;五&#xff09; Leetcode—2525.根据规则将箱子分类 实现代码 char * categorizeBox(int length, int width, int height, int mass){long long volume;long long len (long long)length;long long wid (long long)width;long long heig (long lo…

十八、字符串(2)

本章概要 格式化输出 printf()Systen.out.format()Formatter 类格式化修饰符Formatter 转换String.format() 一个十六进制转储&#xff08;dump&#xff09;工具 格式化输出 在长久的等待之后&#xff0c;Java SE5 终于推出了 C 语言中 printf() 风格的格式化输出这一功能…

VFP GRID每行BLOB显示图片,简单几行代码就完成啦

不止一位狐友问我&#xff0c;想在表格里面显示图片&#xff0c;于是我想了想&#xff0c;满足狐友们的期望&#xff0c;升级了一个框架控件&#xff0c;再来个超容易的教程。 一、拖入一个表单 二、删除自动生成的TEXTBOX1 选中表格&#xff0c;右键->编辑 &#xff0c;点击…

Kibana Discover数据查询

步骤1&#xff1a;打开管理页面(Management) 步骤2&#xff1a; 因为前面的章节导入航班数据的时候&#xff0c;自动创建了一个名字叫kibana_sample_data_flights的航班数据索引&#xff0c;如果我们只想搜索kibana_sample_data_flights索引的数据&#xff0c;则不需要通配符&…

Mysql数据库表操作--存储

建表&#xff1a; 插入上面的数据&#xff1a; 1、创建一个可以统计表格内记录条数的存储函数 &#xff0c;函数名为count_sch() 2、创建一个存储过程avg_sal&#xff0c;有3个参数&#xff0c;分别是deptno&#xff0c;job&#xff0c;接收平均工资(out);功能查询employees表的…

哈希表超详解

目录 哈希表 概念 冲突-概念 冲突-避免 冲突-避免-哈希函数设计 冲突-避免-负载因子的调节 冲突-解决-闭散列 冲突-解决-开散列 哈希桶的实现 性能分析 java和类集的关系 哈希表 概念 顺序结构及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应关系&#xf…

不做技术不会管理,测试人还有这个职位可以进阶

之前我们讲过&#xff0c;测试工程师的4层技术发展路线都需要掌握哪些技能。学而优则仕&#xff0c;今天我们来说说如果想做某个行业的专家应该掌握哪些技能。 如果你对测试技术不感兴趣&#xff0c;但对某领域的业务兴趣浓厚&#xff0c;可以考虑行业专家路线。 由于测试工程…

python单元测试框架(继承、unittest参数化、断言、测试报告)

一、继承 继承能解决什么问题&#xff1f; unittest每个模块都要用到前提条件以及清理&#xff0c;如果有上百个模块&#xff0c;我们要改域名和浏览器&#xff0c;就会工作量很大特别麻烦&#xff0c;这时我们可以用继承的思想只用改一次 我们可以将前提和清理提出来单独放…

日志分析系统——ELK

目录 一、ELK概述 ELK的组成 1、ElasticSearch 2、Logstash 3、Kiabana 完整日志采集系统基本特征 ELK的工作原理 二、ELK的部署 1、环境准备 2、部署ElasticSearch软件 3、安装Elasticsearch-head插件 4、Logstash部署 5、Kibana部署 三、FilebeatELK部署 1、安…

python基础教程:递归函数教程

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 1.递归的定义&#xff1a; 在函数内部直接或者间接调用函数本身 &#x1f447; &#x1f447; &#x1f447; 更多精彩机密、教程&#xff0c;尽在下方&#xff0c;赶紧点击了解吧~ python源码、视频教程、插件安装教程、资…

数据科学中常用的应用统计知识

随着大数据算法技术发展&#xff0c;数据算法越来越倾向机器学习和深度学习相关的算法技术&#xff0c;概率论和应用统计 等传统的技术貌似用的并不是很多了&#xff0c;但实则不然&#xff0c;在数据科学工作&#xff0c;还是会经常需要应用统计概率相关知识解决一些数据问题&…

Autosar诊断实战系列25-UDS 0x27服务相关问题思考

本文框架 前言0x27服务几个相关问题1. 安全访问种子的随机数能不能是全0?2. 安全级别之间是否有联系?是怎么确定的?3. 安全访问错误计数器具体变化策略?前言 在本系列笔者将结合工作中对诊断实战部分的应用经验进一步介绍常用UDS服务的进一步探讨及开发中注意事项, Dem/D…