欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3+vant4實現(xiàn)pdf文件上傳與預(yù)覽組件

 更新時間:2025年04月08日 09:30:49   作者:肖肖肖麗珠  
這篇文章主要介紹了vue3如何結(jié)合vant4實現(xiàn)簡單的pdf文件上傳與預(yù)覽組件,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

注意下載的插件的版本"pdfjs-dist": "^2.2.228",

npm i  pdfjs-dist@2.2.228

然后封裝一個pdf的遮罩。因為pdf文件有多頁,所以我用了swiper輪播的形式展示。因為用到移動端,手動滑動頁面這樣比點下一頁下一頁的方便多了。

直接貼代碼了

PdfPreview/index.vue

<!--預(yù)覽pdf文件的組件-->
<template>
  <van-overlay :show="show" @click="close()">
    <div class="pdf-viewer" >
      <van-swipe class="my-swipe" indicator-color="red" @click.stop>
        <van-swipe-item v-for="item in pageNum" :key="item">
          <canvas :id="`pdf-canvas-${item}`" class="pdf-page"/>
        </van-swipe-item>
        <template #indicator="{ active, total }">
          <div class="custom-indicator">{{ active + 1 }}/{{ total }}</div>
        </template>
      </van-swipe>
      <van-empty
          v-if="loadError"
          image="error"
          description="PDF加載出錯了..."
      />
    </div>
    <van-icon name="close" color="#fff" size="0.3rem"/>
  </van-overlay>

</template>

<script setup lang="tsx">
import {ref, nextTick, watch} from 'vue';
import {closeToast, showLoadingToast, showSuccessToast} from "vant";

// 引入pdf預(yù)覽插件相關(guān)的參數(shù),注意這塊開始試了很多網(wǎng)上方法都不好用
import * as pdfjs from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url';
// 設(shè)置 worker 路徑
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;


const show = ref(true);
// html部分涉及的參數(shù)
const loadError = ref(false);
const detail = ref({});
let pdfDoc = null; // 一定不能使用響應(yīng)式的數(shù)據(jù),會報錯Cannot read from private field---pdf.js
const pageNum = ref(0);


const props = defineProps({
  pdfUrl: {
    type: String,
    default: ""
  },
})
const emit= defineEmits(['close'])

watch(() => props.pdfUrl, (newVal) => {
  // console.log("監(jiān)聽", newVal, props.pdfUrl)
  showLoadingToast('加載中');
  nextTick(() => {
    loadingPdf(props.pdfUrl);
  })

}, {immediate: true,deep:true})
// 防抖 debounce 函數(shù)的實現(xiàn)正確。
const debounce(func, wait, options = {}) {
    let timeout;
    const { leading = false, trailing = true } = options;

    return function(...args) {
        const later = () => {
            timeout = null;
            if (!leading) func.apply(this, args);
        };

        const callNow = leading && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);

        if (callNow) func.apply(this, args);
    };
}
// 使用防抖函數(shù),300ms內(nèi)只執(zhí)行一次,避免多次點擊立刻打開又關(guān)閉的情況
const close = debounce(() => {
  show.value = false;
  emit('close')
}, 300, { leading: true, trailing: false });

//加載pdf
const loadingPdf = (url) => {
  const afterUrl = {
    url,
    httpHeaders: {
      token: `Bearer-${localStorage.getItem('token')}`,//微信小程序里面打開這個模塊,發(fā)現(xiàn)請求401,報錯信息是登陸訪問超時,發(fā)現(xiàn)pdfjs加載pdf時沒有攜帶token,于是在加載url時添加token即可
    },
  };
  const loadingTask = pdfjs.getDocument(afterUrl);
  loadingTask.promise
      .then((pdf) => {
        pdfDoc = pdf;
        pageNum.value = pdf.numPages;
        nextTick(() => {
          renderPage();
        });
      })
      .catch(() => {
        loadError.value = true;
      });
}

// 渲染pdf
const renderPage = (num = 1) => {
  pdfDoc.getPage(num).then((page) => {
    const canvas = document.getElementById(`pdf-canvas-${num}`);
    if(!canvas){return}
    const ctx = canvas.getContext('2d');
    const scale = 1.5;
    const viewport = page.getViewport({scale});
    // 畫布大小,默認值是width:300px,height:150px
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    // 畫布的dom大小, 設(shè)置移動端,寬度設(shè)置鋪滿整個屏幕
    const {clientWidth} = document.body;
    // 減去2rem使用因為我的頁面左右加了padding
    canvas.style.width = `calc(${clientWidth}px - 2rem)`;
    // 根據(jù)pdf每頁的寬高比例設(shè)置canvas的高度
    canvas.style.height = `${
        clientWidth * (viewport.height / viewport.width)
    }px`;
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    page.render({
      canvasContext: ctx,
      viewport,
    });
    //隱藏渲染所有的頁面
    if (num < pageNum.value) {
      renderPage(num + 1);
    } else {
      closeToast();
    }
  });
}

</script>

<style scoped>
.pdf-viewer{
  display: flex;
  justify-content: center;
  align-items: center;
  height:100vh;
  width:100vw;
  text-align: center;
}
.custom-indicator {
  position: absolute;
  left: 50%;
  bottom: 15px;
  transform: translateX(-50%);
  padding: 2px 5px;
  font-size: 18px;
  color: #fff;
  background: rgba(0, 0, 0, 0.1);
}
</style>

上傳的頁面可以參考文末補充內(nèi)容,稍微改動一下就可以了。

然后給組件添加一個點擊預(yù)覽的事件 。并把上面寫好的預(yù)覽組件引入

import PdfPreview from "@/components/PdfPreview/index.vue";
// 點擊預(yù)覽文件
const showPreview=(file)=>{
  if(file.absoluteUrl.endsWith('.pdf')){
    pdfUrl.value=file.absoluteUrl;
    preview.value=true;
  }
}

遇到的問題:

如果報錯

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'getContext')

可能是canvas要找的那個id在頁面還沒有渲染出來。所以我用的nextTick,還在獲取canvas后面判斷了一下找到了再繼續(xù) ,注意上面棕色加粗的地方。

如果報錯

vue-router.mjs:3518SyntaxError: The requested module '/node_modules/.vite/deps/pdfjs-dist_build_pdf__worker__entry.js?v=8ae4d11f' does not provide an export named 'default'

檢查一下你引入插件的地方。如果是

import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';

這樣寫的就是錯的,改成

import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url';

問題:如果你點擊第一次彈窗展示了,但是再點擊就沒有彈出。

原因是預(yù)覽的組件渲染是監(jiān)聽的pdf的url的地址。如果你第一個打開沒有把組件銷毀。那么再次顯示的時候沒有走監(jiān)聽。就不會顯示。所以要在每次關(guān)閉彈窗是組件也銷毀。這就是上面我要在子組件中用@close給組件通知讓他不顯示也就是銷毀子組件的原因。

問題:無意間雙擊了文件導致遮罩馬上顯示又隱藏。頁面效果就是黑色遮罩閃了一下。

可以使用防抖的方式。延遲關(guān)閉。參考上面紫色的關(guān)閉函數(shù)

知識補充

vant4+vue3封裝一個上傳公共組件.有上傳和刪除訪問接口的過程。限制上傳的格式和上傳文件大小

效果圖

我的上傳接口需要參數(shù)和返回的參數(shù)

<template>
  <div class="upload-box1">
   
    <van-uploader
        v-model="_fileList"
        list-type="picture-card"
        :class="['upload', self_disabled ? 'disabled' : '']"
        :multiple="false"
        :disabled="self_disabled"
        :max-count="props.limit"
        :after-read="handleHttpUpload"
        :before-read="beforeUpload"
        :before-delete="handleRemove"
        :accept="props.fileType"
        :deletable="props.deletable"
        upload-icon="plus"
        :max-size="500 * 1024* 1000"
        :preview-image="true"
        :preview-size="props.width"
        :reupload="props.reupload"
        @oversize="onOversize"
    >
    </van-uploader>
//我的展示效果里面有個文字。所以在下面加了這個.
    <div v-for="item in _fileList" :key="item.url" v-if="_fileList.length>0" >
      <div name="tips" class="tips" style="width:70px">{{props.tips}}</div>
    </div>

    <div class="tips" :style="{'width':props.width}" v-else>
      <div name="tips">{{props.tips}}</div>
    </div>

  </div>
</template>

<script setup lang="tsx" name="UploadImgs">
import { ref, computed, inject, watch } from "vue";
import { removeImg, uploadFile } from "@/api/modules/upload";//封裝的接口

import {showFailToast,showSuccessToast } from 'vant';
import type { UploaderFileListItem, ImagePreviewOptions } from 'vant';

//上傳的內(nèi)容參數(shù)
interface UploaderFileList {
  url:string,
  name:string,
  absoluteUrl:string,
  businessCode:string,
  businessSubCode:string,
  businessId:string,
  resourceId:string,
}
interface UploadFileProps {
  fileList: UploaderFileList[];
  disabled?: boolean; // 是否禁用上傳組件 ==> 非必傳(默認為 false)
  limit?: number; // 最大圖片上傳數(shù) ==> 非必傳(默認為 5張)
  nowLimit?:number;//當前頁面只有一個圖片的地方。圖片上傳的數(shù)量限制
  fileSize?: number; // 圖片大小限制 ==> 非必傳(默認為 5M)
  fileType?:string; // 類型限制 ==> 非必傳(默認為 "image/jpeg", "image/png", "image/gif","application/pdf")
  height?: string; // 組件高度 ==> 非必傳(默認為 60px)
  width?: string; // 組件寬度 ==> 非必傳(默認為 60px)
  borderRadius?: string; // 組件邊框圓角 ==> 非必傳(默認為 8px)
  uploadParams?: any; //上傳帶的參數(shù)==>必填
  tips?:string;
  deletable?:boolean;
  reupload?:boolean;//是否可重復上傳==>非必填默認false。不可重復上傳。點擊圖片是預(yù)覽效果
}

const props = withDefaults(defineProps<UploadFileProps>(), {
  fileList:()=> [],
  disabled: false,
  limit: 5,
  nowLimit:1,
  fileSize: 5,
  fileType: "image/jpeg, image/png, image/gif, application/pdf",
  height: "60px",
  width: "60px",
  borderRadius: "8px",
  uploadParams: {},
  tips:"上傳",
  deletable:true,
  reupload:false
});
const emit = defineEmits(["update:fileList", "update"]);


// 判斷是否禁用上傳和刪除
const self_disabled = computed(() => {
  return props.disabled
});

const _fileList =  ref<UploaderFileList[]>(props.fileList)
const uploadFileData = ref(); //上傳的文件

// 監(jiān)聽 props.fileList 列表默認值改變
watch(
    () => props.fileList,
    (n) => {
      _fileList.value = n.map(res=>{
        return {
          url:res.absoluteUrl||'',//展示的時候需要url但是接口給我回傳的里面沒有。所以這里自己拼接一下
          name:res.name||'',
          absoluteUrl:res.absoluteUrl||'',
          businessCode:res.businessCode||'',
          businessSubCode:res.businessSubCode||'',
          businessId:res.businessId||'',
          resourceId:res.resourceId||'',
        }
      });
    }
);

/**
 * @description 文件上傳之前判斷
 * @param rawFile 選擇的文件
 * */
const beforeUpload= (rawFile) => {
  console.log(rawFile)
  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize;
  const imgType = props.fileType.includes(rawFile.type);
  if (!imgType)
    showFailToast( "上傳文件不符合所需的格式!");
  if (!imgSize)
    setTimeout(() => {
      showFailToast( `上傳文件大小不能超過 ${props.fileSize}M!`);
    }, 0);
  return imgType && imgSize;
};


const onOversize=()=>{
  showFailToast( `上傳文件大小不能超過 ${props.fileSize}M!`);
}
/**
 * @description 圖片上傳,請求接口
 * @param options upload 所有配置項
 * */
const handleHttpUpload = async (options: any) => {
  // console.log("handleHttpUpload", options.file)
//二進制內(nèi)容上傳參數(shù)
  let formData = new FormData();
  formData.append("uploadFile", options.file);
  for (let key in props.uploadParams) {
    formData.append(key, props.uploadParams[key]);
  }

  try {
    const api = uploadFile;//上傳接口全局一樣,可以直接寫死
    const { data,code } = await api(formData);
    if(code==200){
     //把接口返回的值給到操作的這個file,相當于更新了_fileList.value
      options.businessCode = data.businessCode;
      options.businessSubCode = data.businessSubCode;
      options.resourceId = data.resourceId;
      options.businessId = data.businessId;
      options.absoluteUrl = data.absoluteUrl;
      options.url = data.absoluteUrl;

      emit("update", { data: data });
      showSuccessToast( "上傳成功!");
      console.log("上傳成功fileList:",_fileList.value)
      emit("update:fileList",_fileList.value);
    }else{
      uploadError();
    }
  } catch (error) {
    console.log(error as any);
  }
};



/**
 * @description 刪除圖片
 * @param file 刪除的文件
 * */
// 提取過濾邏輯為獨立函數(shù),增強可讀性和復用性
function shouldRemoveItem(item, file) {
  // 檢查每個屬性是否完全匹配,避免邏輯錯誤
  return (
      item.url === file.url &&
      item.name === file.name &&
      item.absoluteUrl === file.absoluteUrl
  );
}
const handleRemove = async (file: UploaderFileListItem, detail: { index: number }) => {
  // console.log("刪除",file,detail)
  // 應(yīng)用過濾邏輯,過濾掉已經(jīng)刪除的內(nèi)容
  _fileList.value = _fileList.value.filter((item) => !shouldRemoveItem(item, file));
  console.log("刪除",_fileList.value)
  let deleteParam = {
    sysCode: "aged",
    businessCode: file.businessCode,
    businessSubCode: file.businessSubCode,
    businessId: file.businessId,
    resourceIds: [file.resourceId],
  };

 let [code]= await removeImg(deleteParam);
 if(code==200){
   showSuccessToast( "刪除成功!");
 }
  emit("update:fileList", _fileList.value);
};

/**
 * @description 圖片上傳錯誤
 * */
const uploadError = () => {
  showFailToast( "上傳失敗,請您重新上傳!");
};

/**
 * @description 文件數(shù)超出
 * */
const handleExceed = () => {
  showFailToast("當前最多只能上傳"+(props.nowLimit||8)+"份,請移除后上傳!");
};


</script>

<style scoped lang="scss">
</style>

使用方法

<template>
  <Upload :upload-params="serviceOtherParams" v-model:file-list="img3" :limit="6" tips="上傳內(nèi)容">
  </Upload>
</template>

???????<script lang="ts" setup>
import Upload from "@/components/Upload/index.vue";
const img3=ref([])
//定義要傳遞的參數(shù)
const serviceOtherParams = reactive({
  sysCode: "aged",
  businessCode: "serviceOrderRecord",
  businessSubCode: "serviceOther",
});
</script>

img3就是最后上傳的內(nèi)容。不需要再寫函數(shù)接收上傳的返回值了。

到此這篇關(guān)于vue3+vant4實現(xiàn)pdf文件上傳與預(yù)覽組件的文章就介紹到這了,更多相關(guān)vue3 vant4 pdf文件上傳與預(yù)覽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue中methods實現(xiàn)原理是什么

    Vue中methods實現(xiàn)原理是什么

    methods是如何綁定this的 methods綁定上下文執(zhí)行環(huán)境是通過bind來進行的呢,本文給大家介紹Vue中methods實現(xiàn)原理是什么,感興趣的朋友一起看看吧
    2023-11-11
  • webpack dev-server代理websocket問題

    webpack dev-server代理websocket問題

    這篇文章主要介紹了webpack dev-server代理websocket問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Vue.js組件tree實現(xiàn)省市多級聯(lián)動

    Vue.js組件tree實現(xiàn)省市多級聯(lián)動

    這篇文章主要為大家詳細介紹了Vue.js組件tree實現(xiàn)省市多級聯(lián)動的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • vue實現(xiàn)購物車選擇功能

    vue實現(xiàn)購物車選擇功能

    這篇文章主要為大家詳細介紹了vue實現(xiàn)購物車選擇功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • vue中v-model、v-bind和v-on三大指令的區(qū)別詳解

    vue中v-model、v-bind和v-on三大指令的區(qū)別詳解

    v-model和v-bind都是數(shù)據(jù)綁定的方式,下面這篇文章主要給大家介紹了關(guān)于vue中v-model、v-bind和v-on三大指令的區(qū)別,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-11-11
  • 詳解Vue調(diào)用手機相機和相冊以及上傳

    詳解Vue調(diào)用手機相機和相冊以及上傳

    這篇文章主要介紹了Vue調(diào)用手機相機及上傳,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-05-05
  • Element布局組件el-row和el-col的使用

    Element布局組件el-row和el-col的使用

    這篇文章主要介紹了Element布局組件el-row和el-col的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • Vue 無限滾動加載指令實現(xiàn)方法

    Vue 無限滾動加載指令實現(xiàn)方法

    這篇文章主要介紹了Vue 無限滾動加載指令的實現(xiàn)代碼,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-05-05
  • Vue前端利用slice()方法實現(xiàn)分頁器

    Vue前端利用slice()方法實現(xiàn)分頁器

    分頁功能是常見的需求之一,本文主要介紹了Vue前端利用slice()方法實現(xiàn)分頁器,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • 基于vue.js組件實現(xiàn)分頁效果

    基于vue.js組件實現(xiàn)分頁效果

    這篇文章主要為大家詳細介紹了基于vue.js組件實現(xiàn)分頁效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12

最新評論