前端文件上传组件最全封装+删除+下载+预览

前言:使用的是若依的框架+element ui+vue2封装的。如果有不对的地方欢迎指出。后台管理使用,文件需要上传。回显列表,详情也需要回显+预览

// 开始封装组件:封装在 src/components/FileUpload/index.vue中
<template>
  <div class="upload-file">
    <el-upload
      multiple
      name="multipartFile"
      :action="uploadFileUrl"
      :data="{ 上传时附带的额外参数 }"
      :limit="limit"
      :file-list="fileList"
      :before-upload="handleBeforeUpload"
      :on-exceed="handleExceed"
      :on-error="handleUploadError"
      :on-success="handleUploadSuccess"
      :show-file-list="false"
      :headers="headers"
      class="upload-file-uploader"
      ref="fileUpload"
    >
      <!-- 上传按钮 -->
      <el-button size="mini" type="primary" plain v-if="!disabled">选取文件</el-button>
      <!-- 上传提示 -->
      <div class="el-upload__tip" slot="tip" v-if="showTip && !disabled">
        请上传
        <template v-if="fileSize">
          大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
        </template>
        <template v-if="fileType">
          格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
        </template>
        的文件
      </div>
    </el-upload>

    <!-- 文件列表,我们功能需要 删除文件、下载文件、预览文件功能 -->
    <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul" style="min-width: 300px">
      <li class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList" :key="file.fileId">
        <el-link class="link" :href="`${baseUrl}${file.filePath}`" :underline="false" target="_blank">
          <span class="el-icon-document" style="padidng-left: 10px">
            {{ getFileName(file.fileName) }}
          </span>
        </el-link>
        <div class="controls">
          <div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center">
            <el-link :underline="false" @click="handleDelete(index)" type="danger" v-if="!disabled">删除</el-link>
          </div>
          <div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center">
            <el-link :underline="false" @click="handlePreview(file)" type="danger">预览</el-link>
          </div>
          <div class="ele-upload-list__item-content-action" style="width: 50px; text-align: center">
            <el-link :underline="false" @click="handleDownload(file)" type="danger">下载</el-link>
          </div>
        </div>
      </li>
    </transition-group>

	<!-- 文件预览功能,点击文件列表后的预览按钮,需要预览文档,pdf,excel,照片,视频,音频。其他没有封装,所以就不支持 -->
    <el-dialog title="文件预览" :visible.sync="preview.open" append-to-body :before-close="previewCancel">
      <vue-office-docx v-if="fileSuffix == 'docx'" :src="preview.url" style="height: 100vh" />
      <vue-office-excel v-else-if="fileSuffix == 'xlsx'" :src="preview.url" style="width: auto; height: 100vh" />
      <vue-office-pdf v-else-if="fileSuffix == 'pdf'" :src="preview.url" style="height: 100vh" />
      <div style="text-align: center" v-else-if="fileSuffix == 'img'">
        <el-image style="width: 500px" :src="preview.url" fit="fill" :preview-src-list="[preview.url]"></el-image>
      </div>
      <div style="text-align: center" v-else-if="fileSuffix == 'mp3'">
        <audio controls loop ref="myAudio" autoplay class="my-audio">
          <source :src="preview.url" />
        </audio>
      </div>
      <div style="text-align: center" v-else-if="fileSuffix == 'mp4'">
        <video-app :src="preview.url" :second="1"></video-app>
      </div>
      <div style="text-align: center" v-else>暂不支持该文件预览,请下载预览</div>
    </el-dialog>
  </div>
</template>

<script>
import { getToken } from "@/utils/auth";
import { 查看和下载的接口,后端给的 } from "";

//引入VueOfficeDocx组件,需要npm安装
import VueOfficeDocx from "@vue-office/docx";
import "@vue-office/docx/lib/index.css";

//引入VueOfficeExcel组件,需要npm安装
import VueOfficeExcel from "@vue-office/excel";
import "@vue-office/excel/lib/index.css";

//引入VueOfficePdf组件,需要npm安装
import VueOfficePdf from "@vue-office/pdf";

import VideoApp from "./video.vue"; // 这个是我封装的视频预览的组件

export default {
  name: "FileUpload",
  props: {
    // 数量限制
    limit: {
      type: Number,
      default: 5,
    },
    // 大小限制(MB)
    fileSize: {
      type: Number,
      default: 20,
    },
    // 文件类型, 例如['png', 'jpg', 'jpeg']
    fileType: {
      type: Array,
      default: () => ["docx", "pptx", "pdf"],
    },
    // 是否显示提示
    isShowTip: {
      type: Boolean,
      default: true,
    },
    formFileList: {
      type: Array,
      default: () => [],
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    // 培训记录附件的类型
    busiType: {
      type: String,
    },
  },
  components: {
    VueOfficeDocx,
    VueOfficeExcel,
    VueOfficePdf,
    VideoApp,
  },
  data() {
    return {
      number: 0,
      uploadList: [],
      baseUrl: // 地址 ,
      uploadFileUrl: , // 上传文件服务器地址
      headers: {
        Authorization: "Bearer " + getToken(),
      },
      fileList: [],

      lookFile: false,
      url: "",
      // 文件预览
      preview: {
        open: false,
        url: "",
      },
      fileSuffix: "",
    };
  },
  watch: {
  	// 编辑和详情的回显fileList
    formFileList: {
      handler(val) {
        if (val !== undefined) {
          this.fileList = val;
        }
        if (val == null) {
          this.fileList = [];
          return;
        }
      },
      deep: true,
      immediate: true,
    },
  },
  computed: {
    // 是否显示提示
    showTip() {
      return this.isShowTip && (this.fileType || this.fileSize);
    },
  },
  methods: {
    // 上传前校检格式和大小
    handleBeforeUpload(file) {
      // 校检文件类型
      if (this.fileType) {
        const fileName = file.name.split(".");
        const fileExt = fileName[fileName.length - 1];
        const isTypeOk = this.fileType.length ? this.fileType.indexOf(fileExt) >= 0 : [];
        if (!isTypeOk) {
          this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`);
          return false;
        }
      }
      // 校检文件大小
      if (this.fileSize) {
        const isLt = file.size / 1024 / 1024 < this.fileSize;
        if (!isLt) {
          this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`);
          return false;
        }
      }
      this.$modal.loading("正在上传文件,请稍候...");
      this.number++;
      return true;
    },
    // 文件个数超出
    handleExceed() {
      this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`);
    },
    // 上传失败
    handleUploadError(err) {
      this.number--;
      this.$modal.msgError("上传文件失败,请重试");
      this.$modal.closeLoading();
    },
    // 上传成功回调
    handleUploadSuccess(res, file) {
      if (res.code === 200) {
        this.uploadList.push(res.data);
        this.$modal.closeLoading();
        this.uploadedSuccessfully();
      } else {
        this.number--;
        this.$modal.closeLoading();
        this.$modal.msgError(res.msg);
        this.$refs.fileUpload.handleRemove(file);
        this.uploadedSuccessfully();
      }
    },
    // 删除文件
    handleDelete(index) {
      this.fileList.splice(index, 1);
      this.$emit("input", this.listToString(this.fileList));
    },
    // 上传结束处理
    uploadedSuccessfully() {
      if (this.number > 0 && this.uploadList.length === this.number) {
        this.fileList = this.fileList.concat(this.uploadList);
        this.uploadList = [];
        this.number = 0;
        this.$emit("input", this.listToString(this.fileList));
        this.$modal.closeLoading();
        this.$emit("fileUploadSuccess", this.fileList);
      }
    },
    // 获取文件名称
    getFileName(name) {
      if (name?.lastIndex/Of("/") > -1) {
        return name.slice(name.lastIndexOf("/") + 1);
      } else {
        return name;
      }
    },
    // 对象转成指定字符串分隔
    listToString(list, separator) {
      let strs = "";
      separator = separator || ",";
      for (let i in list) {
        strs += list[i].url + separator;
      }
      return strs != "" ? strs.substr(0, strs.length - 1) : "";
    },
    resetFileList() {
      this.fileList = [];
    },
    // 下载
    handleDownload(file) {
    	
      downloadFile(file.fileId).then((res) => {
        this.exportFunction(res, file.fileName, file.fileType);
      });
    },
    exportFunction(response, name, type) {
      const link = document.createElement("a");
      const blob = new Blob([response], { type });
      link.style.display = "none";
      link.href = URL.createObjectURL(blob);
      link.setAttribute("download", name, type);
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
    },
    // 预览
    handlePreview(file) {
      this.preview.url = "";
      if (file.fileType.indexOf("image") !== -1) {
        this.fileSuffix = "img";
      } else {
        this.fileSuffix = file.suffix;
      }
      
      showFileURL(file.fileId).then((res) => {
        this.preview.url = defaultSettings.minioUrl + res;
        if (file.suffix == "mp3") {
          this.$nextTick((res) => {
            this.$refs.myAudio.load();
            this.$refs.myAudio.play();
          });
        }
        this.preview.open = true;
      });
    },
    previewCancel() {
      this.preview.open = false;
      this.preview.url = "";
      if (this.fileSuffix == "mp3") {
        this.$refs.myAudio.pause();
      }
    },
  },
};
</script>

<style scoped lang="scss">
::v-deep .el-button--primary.is-plain {
  color: var(--select-selected-color);
  background: var(--table-content-bColor);
  border-color: var(--search-back);
}
::v-deep .el-button--primary.is-plain:hover,
::v-deep .el-button--primary.is-plain:focus {
  border-color: var(--select-selected-color);
  background-color: var(--select-selected-color);
  color: #fff !important;
}

.upload-file-uploader {
  margin-bottom: 5px;
}
.upload-file-list .el-upload-list__item {
  border: 1px solid #e4e7ed;
  line-height: 2;
  margin-bottom: 10px;
  position: relative;
}
.upload-file-list .ele-upload-list__item-content {
  display: flex;
  color: inherit;
  .link {
    flex: 1;
    display: block;
    margin-left: 2px;
    ::v-deep .el-icon-document {
      width: 310px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
  .controls {
    display: flex;
  }
}
.ele-upload-list__item-content-action .el-link {
  margin-right: 10px;
}
::v-deep {
  .x-spreadsheet-table {
    width: auto !important;
  }
}
.my-audio {
  width: 100%;
}
</style>

video组件封装:


<template>
  <div class="m-video" :class="{'u-video-hover': !hidden}" :style="`width: ${width}px; height: ${height}px;`">
    <video
      ref="veo"
      :style="`object-fit: ${zoom};`"
      :src="src"
      :poster="veoPoster"
      :width="width"
      :height="height"
      :autoplay="autoplay"
      :controls="!originPlay&&controls"
      :loop="loop"
      :muted="autoplay || muted"
      :preload="preload"
      crossorigin="anonymous"
      @loadeddata="poster ? () => false : getPoster()"
      @pause="showPlay ? onPause() : () => false"
      @playing="showPlay ? onPlaying() : () => false"
      @click.prevent.once="onPlay"
      v-bind="$attrs">
      您的浏览器不支持video标签。
    </video>
    <svg v-show="originPlay || showPlay" class="u-play" :class="{'hidden': hidden}" :style="`width: ${playWidth}px; height: ${playWidth}px;`" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"></path>
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.25 12L9.75 8.75V15.25L15.25 12Z"></path>
    </svg>
  </div>
</template>
<script>
export default {
  name: 'Video',
  props: {
    src: { // 视频文件url,必传,支持网络地址 https 和相对地址 require('@/assets/files/Bao.mp4')
      type: String,
      required: true,
      default: ''
    },
    poster: { // 视频封面url,支持网络地址 https 和相对地址 require('@/assets/images/Bao.jpg')
      type: String,
      default: ''
    },
    second: { // 在未设置封面时,自动截取视频第 second 秒对应帧作为视频封面
      type: Number,
      default: 0.5
    },
    width: { // 视频播放器宽度
      type: Number,
      default: 800
    },
    height: { // 视频播放器高度
      type: Number,
      default: 450
    },
    autoplay: { // 视频就绪后是否马上播放
      type: Boolean,
      default: false
    },
    controls: { // 是否向用户显示控件,比如进度条,全屏
      type: Boolean,
      default: true
    },
    loop: { // 视频播放完成后,是否循环播放
      type: Boolean,
      default: false
    },
    muted: { // 是否静音
      type: Boolean,
      default: false
    },
    preload: { // 是否在页面加载后载入视频,如果设置了autoplay属性,则preload将被忽略;
      type: String,
      default: 'auto' // auto:一旦页面加载,则开始加载视频; metadata:当页面加载后仅加载视频的元数据 none:页面加载后不应加载视频
    },
    showPlay: { // 播放暂停时是否显示播放器中间的暂停图标
      type: Boolean,
      default: true
    },
    playWidth: { // 中间播放暂停按钮的边长
      type: Number,
      default: 96
    },
    zoom: { // video的poster默认图片和视频内容缩放规则
      type: String,
      default: 'contain' // none:(默认)保存原有内容,不进行缩放; fill:不保持原有比例,内容拉伸填充整个内容容器; contain:保存原有比例,内容以包含方式缩放; cover:保存原有比例,内容以覆盖方式缩放
    }
  },
  data () {
    return {
      veoPoster: this.poster,
      originPlay: true,
      hidden: false
    }
  },
  mounted () {
    if (this.autoplay) {
      this.hidden = true
      this.originPlay = false
    }
    /*
      自定义设置播放速度,经测试:
      在vue2中需设置:this.$refs.veo.playbackRate = 2
      在vue3中需设置:veo.value.defaultPlaybackRate = 2
    */
    // this.$refs.veo.playbackRate = 2
  },
  methods: {
    /*
      loadeddata 事件在媒体当前播放位置的视频帧(通常是第一帧)加载完成后触发
      preload为none时不会触发
    */
    getPoster () { // 在未设置封面时,自动获取视频0.5s对应帧作为视频封面
      // 由于不少视频第一帧为黑屏,故设置视频开始播放时间为0.5s,即取该时刻帧作为封面图
      this.$refs.veo.currentTime = this.second
      // 创建canvas元素
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      // canvas画图
      canvas.width = this.$refs.veo.videoWidth
      canvas.height = this.$refs.veo.videoHeight
      ctx.drawImage(this.$refs.veo, 0, 0, canvas.width, canvas.height)
      // 把canvas转成base64编码格式
      this.veoPoster = canvas.toDataURL('image/png')
    },
    onPlay () {
      if (this.originPlay) {
        this.$refs.veo.currentTime = 0
        this.originPlay = false
      }
      if (this.autoplay) {
        this.$refs.veo.pause()
      } else {
        this.hidden = true
        this.$refs.veo.play()
      }
    },
    onPause () {
      this.hidden = false
    },
    onPlaying () {
      this.hidden = true
    }
  }
}
</script>
<style lang="scss" scoped>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
.m-video {
  display: inline-block;
  position: relative;
  background: #000;
  cursor: pointer;
  .u-play {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    margin: auto;
    fill: none;
    color: #FFF;
    pointer-events: none;
    opacity: 0.7;
    transition: opacity .3s;
    path {
      stroke: #FFF;
    }
  }
  .hidden {
    opacity: 0;
  }
}
.u-video-hover {
  &:hover {
    .u-play {
      opacity: 0.9;
    }
  }
}
</style>

使用组件:

import FileUpload= from "@/components/FileUpload=";
<el-form-item label="文件上传">
  <file-upload
    ref="fileResetRef"
    @fileUploadSuccess="fileUploadSuccessHandle"
    :formFileList="form.files" // 回显的数据文件列表
    :disabled="isReadonly" // 区分编辑还是查看
    :file-type="[ // 支持的类型
      'png',
      'jpg',
      'docx',
      'xlsx',
      'pptx',
      'pdf',
      'mp3',
      'mp4',
      'zip',
    ]"
  >
  </file-upload>
</el-form-item>
// 文件上传成功的展示
fileUploadSuccessHandle1(fileList) {
  this.form.files = fileList;
},

上传组件效果图:
在这里插入图片描述
上传的文件列表:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用Pyxamstore快速解析Xamarin AssemblyStore文件

关于Pyxamstore Pyxamstore是一款针对Xamarin AssemblyStore文件&#xff08;assemblies.blob&#xff09;的强大解析工具&#xff0c;该工具基于纯Python 2.7开发&#xff0c;支持从一个APK文件中解包并重封装assemblies.blob和assemblies.manifest Xamarin文件。 什么是ass…

YOLOv5算法进阶改进(10)— 更换主干网络之MobileViTv3 | 轻量化Backbone

前言:Hello大家好,我是小哥谈。MobileViTv3是一种改进的模型架构,用于图像分类任务。它是在MobileViTv1和MobileViTv2的基础上进行改进的,通过引入新的模块和优化网络结构来提高性能。本节课就给大家介绍一下如何在主干网络中引入MobileViTv3网络结构,希望大家学习之后能够…

生活明朗,万物可爱人间值得,未来可期

这是一件洋溢着温馨 与童真的时尚佳作它以细腻的织法、柔暖的材质 给您的宝贝带来舒适的穿着体验独特的韩版设计&#xff0c;彰显时尚品味 让您的孩子从小就走在 潮流的前沿适合各种场合 无论是周末聚会还是平日上学 都能让您的孩子焕发自信光彩

Axure鲜花速递商城网站原型图,花店网站O2O本地生活电商平台

作品概况 页面数量&#xff1a;共 30 页 兼容软件&#xff1a;仅支持Axure RP 9/10&#xff0c;非程序软件无源代码 应用领域&#xff1a;鲜花网、花店网站、本地生活电商 作品特色 本作品为「鲜花购物商城」网站模板&#xff0c;高保真高交互&#xff0c;属于O2O本地生活电…

【k8s】deamonset文件和说明

目录 deamonset的相关命令 deamonset的定义 deamonset的使用场景 deamonset的例子 deamonset字段说明 serviceAccountName DaemonSet的结构及其各个部分的作用 deamonset的相关命令 #查看<name-space>空间内有哪些deamonset kubectl get DaemonSet -n <na…

华为OD机试 - 两个字符串间的最短路径问题(Java JS Python C)

题目描述 给定两个字符串,分别为字符串 A 与字符串 B。 例如 A字符串为 "ABCABBA",B字符串为 "CBABAC" 可以得到下图 m * n 的二维数组,定义原点为(0,0),终点为(m,n),水平与垂直的每一条边距离为1,映射成坐标系如下图。 从原点 (0,0) 到 (0,A) 为水…

普中STM32-PZ6806L开发板(HAL库函数实现-USART2 中断接收)

简介 实现USART2 的 中断接收&#xff0c; 发送数据。电路原理图 USART2接线 原理图USART2 在主芯片引脚 实物图 其他知识 APIs stm32f1xx_hal_uart.h /* 堵塞发送, pData是发送数据, Size发送数据大小, Timeout是超时时间 */ HAL_StatusTypeDef HAL_UART_Transmit(UAR…

md文件图片上传方案:Github+PicGo 搭建图床

文章目录 1. PicGo 下载2. 配置Github3. 配置PicGo4. PicGo集成Typora4.1 picGo监听端口设置 5. 测试 1. PicGo 下载 下载地址&#xff1a;https://molunerfinn.com/PicGo/ 尽量下载稳定版本 2. 配置Github 1. 创建一个新仓库&#xff0c;用于存放图片 2. 生成一个token&a…

数据库——SQL DDLDML使用

1.实验内容及原理 1. 在 Windows 系统中安装 VMWare 虚拟机&#xff0c;在 VMWare 中安装 Ubuntu 系统,并在 Ubuntu 中搭建 LAMP 实验环境。 2. 使用 MySQL 进行一些基本操作&#xff1a; &#xff08;1&#xff09;登录 MySQL&#xff0c;在 MySQL 中创建用户&#xff0c;…

[密码学]ECC加密

椭圆曲线加密 Ellipse Curve Cryptography 椭圆曲线上的离散对数问题 Ellipse Curve Discrete logarithm Problem 椭圆曲线 注意积分公式的分母&#xff0c;椭圆曲线由此得名。这种曲线和椭圆一点不像。 离散对数&#xff1a; yg^x mod p,对于给定的g,x,p求y很容易&#…

JVM工作原理与实战(三):字节码文件的组成

专栏导航 JVM工作原理与实战 RabbitMQ入门指南 从零开始了解大数据 目录 专栏导航 前言 一、基础信息 1.Magic魔数 2.主副版本号 3.其他信息 二、常量池 1.案例解析 三、方法 1.方法介绍 2.案例解析 四、字段 五、属性 总结 前言 JVM作为Java程序的运行环境&…

【致远OA】获取指定人员的协同已办列表

接口说明 按人员编码获取所有待办事项&#xff08;V6.0接口更新:不在传入ticket&#xff0c;改为传memberId人员ID&#xff09; 兼容版本 since V7.0 请求方式 V6.0之前http请求方式&#xff1a;GET http://ip:port/seeyon/rest/affairs/done 如 http://127.0.0.1/seeyon/…

Builder建造者模式(对象创建)

Builder 链接&#xff1a;建造者模式实例代码 解析 目的 在软件系统中&#xff0c;有时候面临着“一个复杂对象”的创建工作&#xff0c;其通常由各个部分的子对象用一定的算法构成&#xff1b;由于需求的变化&#xff0c;这 个复杂对象的各个部分经常面临着剧烈的变化&…

深入理解C#中的隐式类型转换

深入理解C#中的隐式类型转换 在C#中&#xff0c;类型转换可以是显式的或隐式的。理解这些转换的工作原理对于编写高效、可维护的代码至关重要。本文将深入探讨C#中的隐式类型转换&#xff0c;特别关注赋值操作符&#xff08;&#xff09;在隐式转换中的角色&#xff0c;并对比C…

FL Studio 21最新版本for mac 21.2.2.3740中文解锁版2024最新图文安装教程

FL Studio 21最新版本for mac 21.2.0.3740中文解锁版是最新强大的音乐制作工具。它可以与所有类型的音乐一起创作出令人惊叹的音乐。它提供了一个非常简单且用户友好的集成开发环境&#xff08;IDE&#xff09;来工作。这个完整的音乐工作站是由比利时公司 Image-Line 开发的。…

三种方式在ASP.NET Core中实现代理功能请求获取数据的接口(以请求百度统计数据接口为例)

一、定义请求数据属性 TargetUrl参数是目标接口的URL&#xff0c;RequestDataArray参数是要发送的请求数据列表 //定义属性:TargetUrl参数是目标接口的URL&#xff0c;RequestDataArray参数是要发送的请求数据列表public class ToResponseBody{[JsonPropertyName("Target…

安装 Node.js、npm

安装 nodejs 安装Node.js的最简单的方法是通过软件包管理器。 Node.js官网&#xff1a;https://nodejs.org/en/download/ cd /usr/local/src/wget -c https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-x64.tar.xz xz -d node-v18.16.0-linux-x64.tar.xz tar -xf node…

使用react+vite开发项目时候,部署上线后刷新页面无法访问解决办法

说一下我这边的环境和使用的路由模式&#xff1a;vitereactBrowserRouter路由模式&#xff0c;所以如果你和我一样的话&#xff0c;可以试试我的这种解决办法&#xff0c;我是将项目打包后直接丢到服务器上的目录里面&#xff0c;然后配置nginx直接访问根目录。 我的nginx配置…

Android 模拟器检测

文章目录 普遍检测代码如下&#xff1a;推荐模拟器检测方法&#xff1a;设备信息检测代码&#xff1a;蓝牙检测代码&#xff1a;光传感器检测代码&#xff1a;CPU检测代码&#xff1a; 最近看到某客户端有一个检测模拟器的方法&#xff0c;我正常手机结果被判断是模拟器了&…

Python 自学(一) 之语言基础

目录 0. 前言 1. 声明python解释器 2. 变量的类型 type() id() P35 3. 类型转换 str() P37 4. 换行 r"" P40 5. 常用类型转换函数 P41 6. 特殊的运算符 // ** P42 7. 特殊的比较运算符 0 < a <100 P45 8. 函数输入 input() P51 9. print…