vue + axios 无感刷新功能

89 阅读3分钟

一、无感刷新

一般后端设置的token过期了,提供了一个新的token

这时候需要再次调用接口获取到token,重新执行之前的接口

这里实现方案:token过期了,后端会在接口返回code = 4005, 根据code查询token接口

import axios from 'axios'
import { session } from '@/utils/cache'
import router from "@/router/index";

const env = import.meta.env
// 是否正在刷新的标记
let isRefreshing = false

var instance = axios.create({
    baseURL:  env.VITE_API_URL + (env.VITE_NODE_ENV == 'production' ? '/openapi' : '/openapi_prev'),
    headers: {
        'Content-Type': 'application/json',
        'X-Access-Token': session.get('otherToken') ||  ''
    },
    responseType: 'json',
    timeout: 80000
})

instance.interceptors.request.use(
    http => {
        http.headers.Authorization = ''
        return http
    },
    error => {
        return Promise.reject(error)
    }
)

instance.interceptors.response.use(
    res => {
        // 需要重新获取状态码的判断条件 
        if(res.data?.code === 4005){
            const config = res.config;
            if(!isRefreshing) {
                isRefreshing = true
                session.remove('otherToken')

                return refreshToken().then(r => {
                    if(r?.code !== 0){
                        //重新请求token失败,跳转到空页面
                        router?.push('/empty');
                    } else {
                        const { token } = r;
                        session.set("otherToken", token);
                        config.headers['X-Access-Token'] = session.get("otherToken");
                        config.headers['Authorization'] = 'Bearer ' + token;
                        return instance(config);
                    }
                    }).catch(err => {
                        //重新请求token失败
                        console.log(err, 'token获取err')
                    } ).finally(()=> {
                        //完成之后在关闭状态
                        isRefreshing = false
                    } )
            }
        }


        return res.data || {}
    },
    error => {
        console.log(error)
        return Promise.resolve('')
    }
)

// token失效并重新获取
function refreshToken () {
    return post("user/v1/refreshLogin", { appId: 1, code: 1 }).then(res => res.data)
}

export function get(url) {
    return instance.get(url).then(res => {
        console.log('[GET]', url, res)
        return Promise.resolve(res)
    })
}

export function post(url, params, headers) {
    return instance.post(url, params || {},  { headers: headers ? headers : instance.headers }).then(res => {
        console.log('[POST]', url, params || {}, res)
        return Promise.resolve(res)
    })
}

export default {
    get,
    post
}

二、显示请求上传进度

上传图片、视频等file显示当前进度值

使用axios中的onUploadProgress属性监听上传进度,并将进度反馈给进度条

将请求的进度存到vuex,请求的时候显示进度条,请求成功后隐藏进度条

image.png

image.png

    // 封装的接口函数中获取进度值
    export const trainVideoFile = (data, store, file) => {
      return request({
        url: `/api/VideoFile`,
        method: 'post',
        data,
        onUploadProgress: progressEvent => {
          console.log(progressEvent, 'progressEvent');
          const complete = parseInt(((progressEvent.loaded / progressEvent.total) * 100) | 0, 10);
          console.log(complete, 'complete');
          // 存到浏览器缓存,用于刷新页面显示进度
          setStorage('videoUploadProgress', complete);
          // 存到vuex中,设置参数
          store.dispatch('setVideoUploadProgress', {
            progressVisible: true,
            progressPercent: complete,
          });
        },
      });
    };

    <template>
<div v-if="!form.trainVideoFileId" class="upload-box">
  <el-upload
    class="upload"
    drag
    action="/"
    :auto-upload="false"
    :disabled="$store.state.videoUploadProgress.progressVisible"
    :show-file-list="false"
    :on-change="beforeAvatarUpload"
    :accept="'.mp4,.mov'"
  >
    <img class="upload-pic" src="@/assets/images/upload-pic.png" alt="" />
    <div class="upload-text">
      <div class="text">点击上传视频</div>
      <div class="demand">
        <div v-for="item in demandList" :key="item.id">{{ item.name }}</div>
      </div>
    </div>
    <!-- progressVisible -->
    <div class="upload-progress" v-if="$store.state.videoUploadProgress.progressVisible">
      <el-progress
        class="progress"
        :percentage="$store.state.videoUploadProgress.progressPercent"
      ></el-progress>
    </div>
  </el-upload>
</div>
<div v-else class="video-list">
  <div class="file-resource">
    <div class="flex-center">
      <!-- 视频根据上传后的比例 横|竖 设置宽高 -->
      <video
        :style="[
          {
            width: '171px',
            height: '305px',
            objectFit: 'contain',
            backgroundColor: '#333',
          },
        ]"
        class="video"
        :src="videoOptions.fileUrl"
        controls
      ></video>
    </div>
      </div>
      <span @click="handleDeleteAudioFile">
        <img class="icon-delete" src="@/assets/images/shanchu@2x.png" alt="" />
        删除视频
      </span>
    </div>
</template>

    data() {
        return {
          uploadRules: false,
          uploadRulesMsg: '',
          form: {
            trainVideoFileId: '',
          },
          demandList: [
            {
              id: 1,
              name: '1、视频大小不超过1GB,时长不短于1分钟、不长于10分钟',
            },
            {
              id: 2,
              name: '2、视频格式为mp4、mov',
            },
            {
              id: 3,
              name: '3、视频分辨率1080P,宽高比符合16:9(9:16)',
            },
            {
              id: 4,
              name: '4、视频帧率不低于25fps、不高于60fps',
            },
          ],
        }
    }

    methods: {
        // 上传接口
        async handleVideoFileUpload(file) {
          let formData = new FormData();
          formData.append('file', file.raw);
          const res = await trainVideoFile(formData, this.$store, file);
          // 请求成功后隐藏进度条
          this.$store.dispatch('setVideoUploadProgress', {
            progressVisible: false,
            progressPercent: 1,
          });
          if (res.code == 200) {
            const { fileUrl, fileId, fileName } = res.data;
            this.form.trainVideoFileId = fileId;
            this.videoOptions = {
              fileUrl,
              fileId,
              fileName,
            };
            this.$message.success('上传成功');
          } else {
            this.$message.error(res.msg);
          }
        },
        
        // 文件上传前文件类型、文件大小的处理
        beforeAvatarUpload(file) {
          console.log(file, '上传格式验证');
          if (!file.name) return;

          this.validateAudioFile(file)
            .then(result => {
              if (!result) {
                console.error('验证视频文件失败', result);
                return false;
              } else {
                this.handleVideoFileUpload(file);
                console.log('上传视频成功');
                return true;
              }
            })
            .catch(error => {
              console.error('获取视频时长时出错:', error);
              return false;
            });
        },

        // 视频文件校验规则
        async validateAudioFile(file) {
          console.log(file, '获取视频参数');
          
          // 视频格式为mp4、mov
          if (file.name) {
            const imgType = ['mp4', 'mov'];
            const type = file.name.split('.')[file.name.split('.').length - 1];
            const isSupportedFormat = imgType.some(ext => ext.toUpperCase() === type.toUpperCase());

            if (!isSupportedFormat) {
              this.uploadRules = true;
              this.uploadRulesMsg = '视频格式不支持';
              return false;
            }
          }

          // 视频大小不超过1GB
          if (file.size) {
            const isLt1G = 1 * 1024 * 1024 * 1024;
            if (file.size > isLt1G) {
              this.uploadRules = true;
              this.uploadRulesMsg = '您的视频大小不符合,请上传大小不超过1GB的视频';
              return false;
            }
          }

          // 视频raw
          if (file.raw) {
            const video = await this.getVideoDuration(file.raw);
            const duration = video.duration; // 视频时长
            const width = video.videoWidth; // 视频宽度
            const height = video.videoHeight; // 视频高度
            const aspectRatio = width / height; // 视频宽高比
            const fps = video.captureStream().getVideoTracks()[0].getSettings().frameRate; // 视频帧率

            // 视频时长时长不短于1分钟、不长于10分钟
            if (duration < 60 || duration > 600) {
              this.uploadRules = true;
              this.uploadRulesMsg = '您的视频时长不符合,请上传时长为1-10分钟的视频';
              return false;
            }

            // 视频分辨率1080P,宽高比符合16:9(9:16)
            if (aspectRatio !== 16 / 9 && aspectRatio !== 9 / 16) {
              this.uploadRules = true;
              this.uploadRulesMsg = '您的视频宽高比不符合,请上传视频分辨率1080P,宽高比符合16:9(9:16)';
              return false;
            }

            // 视频分辨率1080P
            if (width < 1920 || height < 1080) {
              this.uploadRules = true;
              this.uploadRulesMsg = '您的视频分辨率不符合,视频分辨率1080P,宽高比符合16:9(9:16)';
              return false;
            }

            // 视频帧率不低于25fps、不高于60fps
            if (fps < 25 || fps > 60) {
              this.uploadRules = true;
              this.uploadRulesMsg = '您的视频帧率不符合,请上传帧率不低于25fps、不高于60fps的视频';
              return false;
            }
          }

          return true;
        },
        // 根据文件获取视频时长
        getVideoDuration(file) {
          return new Promise((resolve, reject) => {
            const video = document.createElement('video');
            video.preload = 'metadata';
            video.onloadedmetadata = () => {
              resolve(video);
            };
            video.onerror = () => {
              reject(new Error('无法加载视频文件'));
            };
            video.src = URL.createObjectURL(file);
          });
        },
    }