Use of Nuxt3+naive-ui form and upload plug-in, upload videos, upload pictures, custom upload

Use of Nuxt3+naive-ui form and upload plug-in

Go to the code, there are comments in the code, anyscript [face covering] will take a look

<template>
  <div class="set-page">
     <ClientOnly>
       <n-config-provider :theme-overrides="themeOverrides">
         <n-form ref="formRef" :model="model" label-placement="left" label-width="110"
           :show-require-mark="false" require-mark-placement="right-hanging" size="medium">
           <div class="flex justify-between w-full">
             <n-form-item label="性别" :validation-status="errorsObj.sex ? 'error' : undefined"
               :feedback="errorsObj.sex" class="w-2/4 shrink-0">
               <n-select v-model:value="model.sex" placeholder="请选择性别" :options="sexOptions" />
             </n-form-item>
             
			<n-form-item label="个人视频" :validation-status="errorsObj.videos ? 'error' : undefined"
              :feedback="errorsObj.videos">
              <n-upload :default-file-list="videos" v-model:file-list="videos" accept="video/*"
                :custom-request="customRequestVideo" @remove="handleRemoveVideo" @preview="handlePreviewVideo"
                list-type="image-card">
              </n-upload>
            </n-form-item>

            <n-form-item label="个人相册" :validation-status="errorsObj.photos ? 'error' : undefined"
              :feedback="errorsObj.photos">
              <n-upload list-type="image-card" :default-file-list="photos" v-model:file-list="photos" accept="image/*"
                :custom-request="customRequestPhoto" @remove="handleRemovePhoto">
              </n-upload>
            </n-form-item>
            
            <div class="btn-con">
              <div @click="handleSubmit" class="btn">提交</div>
            </div>
          </n-form>
        </n-config-provider>
      </ClientOnly>
      <!-- 视频播放 -->
	    <div v-if="videoVisible" class="video-bg">
	      <div class="video-body">
	        <iframe id="iframeContain" name="iframeContain" seamless scrolling="yes" :src="iframeURL" width="800px"
	          height="460px">
	        </iframe>
	        <div class="video-btn" @click="videoVisible = false"></div>
	      </div>
	    </div>
    </div>
   </template>
   
<script lang="ts" setup>
import {
    
     xx, xx} from "@/utils/api"
import {
    
     NConfigProvider, NSelect, NUpload, NForm, NFormItem } from 'naive-ui'
import type {
    
     UploadInst, UploadFileInfo, UploadCustomRequestOptions } from 'naive-ui'
import {
    
     nextTick } from 'vue'
// 组件的样式重置,可以根据官网右下角的编辑后导出
const themeOverrides = {
    
    
  common: {
    
    
    primaryColor: '#000000',
    primaryColorHover: "#000000"
  },
  Form: {
    
    
    feedbackPadding: "0px 0 0 2px",
    feedbackHeightMedium: "14px",
    feedbackFontSizeMedium: "12px"
  }
}
// 性别选择项
const sexOptions = [
  {
    
     label: "男", value: 1 },
  {
    
     label: '女', value: 2 },
  {
    
     label: '保密', value: 3 }
]
let model = reactive({
    
    
  sex: null
})
let photos = ref<UploadFileInfo[]>([])
let videos = ref<UploadFileInfo[]>([])
let errorsObj = reactive({
    
    
  sex: '',
  photos: '',
  videos: ''
})
const statetoken = useUserToken()
let auth = statetoken.value['token'] ? statetoken.value['token'] : ''
let videosList = ref([]) //主要为了自定义上传后,原本进入页面回显的视频拿不到一些自定义添加的字段,所以用它来另外存一下
let photosList = ref([])  //主要为了自定义上传后,原本进入页面回显的视频拿不到一些自定义添加的字段,所以用它来另外存一下
// 视频播放相关设置
const options = reactive({
    
    
  width: "800px", //播放器高度
  height: "450px", //播放器高度
  color: "#409eff", //主题色
  title: "", //视频名称
  src: "https://cdn.jsdelivr.net/gh/xdlumia/files/video-play/IronMan.mp4", //视频源
  poster: '',
  muted: false, //静音
  webFullScreen: false,
  speedRate: ["0.75", "1.0", "1.25", "1.5", "2.0"], //播放倍速
  autoPlay: false, //自动播放
  loop: false, //循环播放
  mirror: false, //镜像画面
  ligthOff: false, //关灯模式
  volume: 0.3, //默认音量大小
  control: true, //是否显示控制
  controlBtns: [
    "audioTrack",
    "quality",
    "speedRate",
    "volume",
    "setting",
    "pip",
    "pageFullScreen",
    "fullScreen",
  ], //显示所有按钮,
});
const iframeURL = ref('')
const videoVisible = ref(false)

const getFileURL = (file) => {
    
    
  var url = null;
  if (window.URL != undefined) {
    
     // mozilla(firefox)
    url = window.URL.createObjectURL(file);
  } else if (window.webkitURL != undefined) {
    
     // webkit or chrome
    url = window.webkitURL.createObjectURL(file);
  }
  return url;
}
// 截取视频第一帧
const getVideoBase64 = (url) => {
    
    
  return new Promise(function (resolve) {
    
    
    let dataURL = "";
    const video = document.createElement("video");
    video.setAttribute("crossOrigin", "anonymous"); // 处理跨域
    video.setAttribute("src", url);
    video.setAttribute("preload", "auto");
    video.addEventListener("loadeddata", function () {
    
    
      const canvas = document.createElement("canvas");
      console.log("video.clientWidth", video.videoWidth); // 视频宽
      console.log("video.clientHeight", video.videoHeight); // 视频高
      const width = video.videoWidth || 400; // canvas的尺寸和图片一样
      const height = video.videoHeight || 240; // 设置默认宽高为  400  240
      canvas.width = width;
      canvas.height = height;
      canvas.getContext("2d").drawImage(video, 0, 0, width, height); // 绘制canvas
      dataURL = canvas.toDataURL("image/jpeg"); // 转换为base64
      resolve(dataURL);
    })
  })
}
// base64转图片file
const getFileFromBase64 = (base64URL, filename) => {
    
    
  var arr = base64URL.split(","),
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    
    
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, {
    
     type: "image/png" });
}
// 上传视频
const customRequestVideo = async ({
     
      file, onFinish, onError }: UploadCustomRequestOptions) => {
    
    
  console.log('file', file)
  const objUrl = getFileURL(file.file as File)
  const objBase = await getVideoBase64(objUrl) // 截取视频第一帧
  const objCoverFile = await getFileFromBase64(objBase, `${
      
      file.name.split('.')[0]}.jpg`) // 第一帧转file
  const videoformData = new FormData()
  videoformData.append('file', file.file as File)
  videoformData.append('cover', objCoverFile as File) // 后端要我截取封面图传给他,话说在服务端截取不好一些吗,谁叫我人微言轻呢
  const {
    
     data: dataV, pending, refresh, error } = await useFetch('http://xxx/upload/video', {
    
    
    key: uuid(),
    method: 'post',
    headers: {
    
    
      Authorization: auth
    },
    body: videoformData,
  })
  console.log('接口返回', dataV.value['data'])
  // 上传file给后端后,接口返回的视频地址和封面图地址,加到videos的url和thumbnaiUrl上
  if (dataV.value['success']) {
    
    
    onFinish()
    errorsObj.videos = ''
    videos.value.map(it => {
    
    
      if (it.id === file.id) {
    
    
        it.url = dataV.value['data']['file']['path']
        it.status = 'finished'
        it.thumbnailUrl = dataV.value['data']['cover']['path']
        it.type = null // 一定要把type设置为null,不然视频的类型为video/mp4,在页面缩率图thumbnailUrl会不起作用
        it['extension'] = dataV.value['data']['file']['extension'] // 提交的时候后端需要的自定义参数
        it['size'] = dataV.value['data']['file']['size'] // 提交的时候后端需要的自定义参数
        videosList.value.push(it) // 提交的时候后端需要的自定义参数在添加文件之后之前videos里的会没有,所以我要用videosList重新保存一下,并在删除的时候要从videosList里删除
      }
    })
  } else {
    
    
    onError()
    errorsObj.photos = dataV.value['data']['errors']['image'] + ',请删除后重新选择'
  }
  console.log('videos', videos.value, videosList.value)
}
// 删除视频
const handleRemoveVideo = (data: {
     
      file: UploadFileInfo; fileList: UploadFileInfo[] }) => {
    
    
  console.log('remove', data.file)
  let arr = []
  videosList.value.forEach(it => {
    
    
    if (it.id !== data.file.id) {
    
    
      arr.push(it)
    }
  })
  videosList.value = arr
  console.log('jj', videosList.value)
}
// 预览视频,把视频的url给iframe
const handlePreviewVideo = (file: UploadFileInfo) => {
    
    
  console.log('预览', file)
  options.src = file['url']
  options.poster = file['thumbnailUrl']
  iframeURL.value = file['url']l
  videoVisible.value = true
}

// 上传照片
const customRequestPhoto = async ({
     
      file, onFinish, onError, onProgress }: UploadCustomRequestOptions) => {
    
    
  console.log('file', file)
  const photoformData = new FormData()
  photoformData.append('image', file.file as File)
  const {
    
     data: dataV, pending, refresh, error } = await useFetch('http://xxx/upload/image', {
    
    
    key: uuid(),
    method: 'post',
    headers: {
    
    
      Authorization: 'auth
    },
    body: photoformData,
  })
  console.log('接口返回', dataV.value['data'])
  if (dataV.value['success']) {
    
    
    onFinish() // 上传接口成功调用后记得调用onFinish,不然你上传的时候看看file都打印的啥
    errorsObj.photos = ''
    photos.value.map(it => {
    
    
      if (it.id === file.id) {
    
    
        it.url = dataV.value['data']['file']['path']
        it['extension'] = dataV.value['data']['file']['extension']
        it['size'] = dataV.value['data']['file']['size']
        photosList.value.push(it)
      }
    })
    console.log('photos', photos.value, photosList.value)

  } else {
    
    
    errorsObj.photos = dataV.value['data']['errors']['image'] + ',请删除后重新选择'
    onError()
  }

}
// 删除照片
const handleRemovePhoto = (data: {
     
      file: UploadFileInfo; fileList: UploadFileInfo[] }) => {
    
    
  console.log('remove', data.file)
  let arr = []
  photosList.value.forEach(it => {
    
    
    if (it.id !== data.file.id) {
    
    
      arr.push(it)
    }
  })
  photosList.value = arr
}
// 提交
const handleSubmit = async () => {
    
    
	console.log('提交', videos.value, videosList.value)
  console.log('提交', photos.value, photosList.value)
  // 因为videos.value和photos.value里一些自己加的字段没有,所以采用videosList.value 和photosList.value数据进行提交
}
// url转file
const getImageFileFromUrl = (url, imageName, type) => {
    
    
  return new Promise((resolve, reject) => {
    
    
    let blob = null;
    let imgFile = null;
    let xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.setRequestHeader("Accept", type);
    // xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
    // xhr.setAttribute("crossOrigin", "anonymous"); // 处理跨域
    xhr.responseType = "blob";
    xhr.onload = () => {
    
    
      blob = xhr.response;
      imgFile = new File([blob], imageName, {
    
     type });
      resolve(imgFile);
    };
    xhr.onerror = e => {
    
    
      reject(e);
    };
    xhr.send();
  });
}
// 获取已设置
const getSetInfo = async () => {
    
    
  await nextTick()
  getInfo({
    
     key: uuid() }).then(async (res) => {
    
    
  model.sex= res.sex
  res.videos.length && res.videos.forEach(async (it, index) => {
    
    
      let obj = {
    
    }
      obj['id'] = index.toString()
      obj['name'] = it.name
      obj['status'] = 'finished' // 要给视频设置已完成状态,不然预览不了
      obj['url'] = it.path // 设置视频的url,path是后端返回的url字段
      obj['thumbnailUrl'] = it.cover // 要给视频设置封面图
      v.push(obj)
    })
 res.photos.length && res.photos.forEach(async (it, index) => {
    
    
      let ff = await getImageFileFromUrl(it.path, it.name, "image/png") // 一个把url转为file文件的方法
      let obj = {
    
    }
      obj['id'] = index.toString()
      obj['name'] = it.name
      obj['status'] = 'finished'
      obj['url'] = it.path
      obj['file'] = ff //这个file可以不需要,只是调用customRequestPhoto photos.value里一开始回显的文件file会没有
      obj['size'] = it.size
      obj['extension'] = it.extension // 提交的时候后端需要的一些字段,本身插件没有该字段
      obj['thumbnailUrl'] = it.path
      p.push(obj)
    })
    setTimeout(() => {
    
    
      videos.value = v
      photos.value = p
      photosList.value = p
      videosList.value = v
      console.log('set-p', photos.value, photosList.value)
      console.log('set', videos.value, videosList.value)
    }, 500)

  })
}

getSetInfo()
</script>

renderings

Guess you like

Origin blog.csdn.net/qq_38661597/article/details/132344012