【Vue】vue3 vue-pdf-embed 實現pdf預覽、縮放、拖拽、旋轉和左側菜單選擇

實際效果

image

安裝插件

pnpm install vue-pdf-embed
pnpm install vue3-pdfjs

左側pdf菜單組件

<template>
  <div class="pdf-view-list">
    <div class="item active-item" v-for="(item, index) in pageTotalNum" @click="itemClcik(index)">
      <div class="pdf-box">
        <vue-pdf-embed :source="testpdf1" class="vue-pdf-embed" :page="index + 1" />
      </div>
      <div class="page">{{ index + 1 }}</div>
      <div class="mask" :class="{ active: activePage === index + 1 }"></div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import VuePdfEmbed from 'vue-pdf-embed';
import { createLoadingTask } from 'vue3-pdfjs/esm'; // 獲得總頁數
import testpdf1 from '@/assets/zhjx-xrkg/testpdf1.pdf';

const props = defineProps<{
  activePage: number; // 當前頁
}>();
const emits = defineEmits<{
  (event: 'update:activePage', index: number): void;
}>();
const pageTotalNum = ref(0); // 總頁數

onMounted(() => {
  // 獲得總頁數
  createLoadingTask(testpdf1).promise.then((pdf) => {
    pageTotalNum.value = pdf.numPages;
  });
});

// 更新當前頁
const itemClcik = (index: number) => {
  emits('update:activePage', index + 1);
};
</script>

<style scoped lang="scss">
.pdf-view-list {
  width: 183px;
  height: 100%;
  background-color: #333333;
  overflow-y: scroll;
  // 隱藏滾動條
  &::-webkit-scrollbar {
    display: none;
  }
  .item {
    width: 100%;
    height: 257px;
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 20px 20px 0 20px;
    cursor: pointer;
    .pdf-box {
      width: 140px;
      height: 203px;
      background: #ffffff;
      border-radius: 4px;
      z-index: 1;
      .vue-pdf-embed {
        width: 100%;
        height: 100%;
      }
    }
    .page {
      font-weight: 600;
      font-size: 12px;
      color: #ffffff;
      line-height: 34px;
      z-index: 1;
    }
    .mask {
      width: 100%;
      height: 100%;
      background-color: transparent;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 0;
    }
    .active {
      background-color: #ffaa46;
      opacity: 0.2;
    }
  }
}
</style

右側預覽組件

<template>
  <div class="zhjxMain">
    <div class="content">
      <!-- 展示容器 -->
      <div class="left-box" :ref="refs.wrapper" @wheel.prevent="scaleWheel($event)">
        <div class="box" :ref="refs.box" @mousedown="dragstart($event)">
          <vue-pdf-embed :source="testpdf1" :style="scaleFun" class="vue-pdf-embed" :page="activePage" />
        </div>
        <div class="zoomin-wrapper">
          <img src="@/assets/XRKG/btn-enlarge.svg" @click="rollBtn('enlarge')" alt="" />
          <img src="@/assets/XRKG/btn-zoomin.svg" @click="rollBtn('zoomin')" alt="" />
          <img src="@/assets/zhjx-xrkg/btn-flip.svg" alt="" @click="rolate" />
        </div>
      </div>
      <div class="right-box">
        <div class="item" v-for="(value, key) in parseObj">
          <div class="label">{{ key }}</div>
          <div class="text">
            {{ value }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, reactive, watch } from 'vue';
import VuePdfEmbed from 'vue-pdf-embed';
import testpdf1 from '@/assets/zhjx-xrkg/testpdf1.pdf';

const props = defineProps<{
  activePage: number;
}>();

const parseObj = ref({
  設備名稱: '1RPEOOIBA冷卻劑疏水泵',
  功能位置: 'ND-4-01-RGL-010AR',
  缺陷原因: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對中複查同軸度 0.08mm平行度0.03mm不合格
拆卸泵蓋螺栓,將泵憋體脫出運至檢修場地,臨時開口設備做好封堵拆卸葉輪及機械密封組件,解體軸承箱,消洗檢查各零部件,測量泵軸最大0.0...大0.02mm,軸與軸承配合間隙最大0.01mm,軸與葉輪配合間隙0.02mm,體口環與葉回裝新軸承及機械密封組件,安裝泵葉輪,將泵整體運往現場回裝,至工作油位,調整對中同軸度 0.03mm平行度 0.025mm,合格
回裝聯軸器及保護單,清理現場
歸還工作票及輻射票完工,修後試驗在工單01447934-02中執行`,
  處理措施: `1RPB002P0 解體檢查工作完成`,
  處理結果: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對中複查同軸度 0.08mm平行度0.03mm不合格
拆卸泵蓋螺栓,將泵憋體脫出運至檢修場地,臨時開口設備做好封堵拆卸葉輪及機械密封組件,解體軸承箱,消洗檢查各零部件,測量泵軸最大0.0...大0.02mm,軸與軸承配合間隙最大0.01mm,軸與葉輪配合間隙0.02mm,體口環與葉回裝新軸承及機械密封組件,安裝泵葉輪,將泵整體運往現場回裝,至工作油位,調整對中同軸度 0.03mm平行度 0.025mm,合格
回裝聯軸器及保護單,清理現場
歸還工作票及輻射票完工,修後試驗在工單01447934-02中執行`,
  其他字段1: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對中複查同軸度 0.08mm平行度0.03mm不合格
拆卸泵蓋螺栓,將泵憋體脫出運至檢修場地,臨時開口設備做好封堵拆卸葉輪及機械密封組件,解體軸承箱,消洗檢查各零部件,測量泵軸最大0.0...大0.02mm,軸與軸承配合間隙最大0.01mm,軸與葉輪配合間隙0.02mm,體口環與葉回裝新軸承及機械密封組件,安裝泵葉輪,將泵整體運往現場回裝,至工作油位,調整對中同軸度 0.03mm平行度 0.025mm,合格
回裝聯軸器及保護單,清理現場
歸還工作票及輻射票完工,修後試驗在工單01447934-02中執行`,
  我是很長的字段名稱: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對`,
  其他字段2: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對中複查同軸度 0.08mm平行度0.03mm不合格
拆卸泵蓋螺栓,將泵憋體脫出運至檢修場地,臨時開口設備做好封堵拆卸葉輪及機械密封組件,解體軸承箱,消洗檢查各零部件,測量泵軸最大0.0...大0.02mm,軸與軸承配合間隙最大0.01mm,軸與葉輪配合間隙0.02mm,體口環與葉回裝新軸承及機械密封組件,安裝泵葉輪,將泵整體運往現場回裝,至工作油位,調整對中同軸度 0.03mm平行度 0.025mm,合格
回裝聯軸器及保護單,清理現場
歸還工作票及輻射票完工,修後試驗在工單01447934-02中執行`,
  其他字段3: `取工作票,現場確認設備位號及安措正確
拆卸聯軸器螺栓,對中複查同軸度 0.08mm平行度0.03mm不合格
拆卸泵蓋螺栓,將泵憋體脫出運至檢修場地,臨時開口設備做好封堵拆卸葉輪及機械密封組件,解體軸承箱,消洗檢查各零部件,測量泵軸最大0.0...大0.02mm,軸與軸承配合間隙最大0.01mm,軸與葉輪配合間隙0.02mm,體口環與葉回裝新軸承及機械密封組件,安裝泵葉輪,將泵整體運往現場回裝,至工作油位,調整對中同軸度 0.03mm平行度 0.025mm,合格
回裝聯軸器及保護單,清理現場
歸還工作票及輻射票完工,修後試驗在工單01447934-02中執行`,
});

// 實現pdf縮放
const scaleFun = computed(() => {
  return `transform:scale(${scaleData.scale});transition: all 0.3s;`;
});
const refs = {
  wrapper: ref<HTMLElement | null>(null), // pdf外層容器
  box: ref<HTMLElement | null>(null), // pdf容器,用於拖拽
};
const dragData = reactive({
  x: 0, // 拖拽初始化時的x座標
  y: 0, // 拖拽初始化時的y座標
  left: 0, // 拖拽結束時的x偏移量
  top: 0, // 拖拽結束時的y偏移量
  firstX: 0, // 初始x座標
  firstY: 0, // 初始y座標
});
const scaleData = reactive({
  scale: 1, // 縮放比例
  scaleNum: 0.1, // 滾輪縮放比例
  scaleMax: 100, // 最大縮放比例
  scaleMin: 0.1, // 最小縮放比例
  scaleBtn: 0.4, // 縮放按鈕縮放比例
  rotate: 0, // 旋轉角度
});

watch(
  () => props.activePage,
  (v) => {
    // 重置pdf大小和位置
    scaleData.scale = 1;
    scaleData.rotate = 0;
    refs.box.value.style.left = '50%';
    refs.box.value.style.top = '50%';
    boxTransform();
  },
);

// box 容器也要跟着變化
const boxTransform = () => {
  refs.box.value.style.transform = `translate(-50%, -50%) rotate(${scaleData.rotate}deg) scale(${scaleData.scale})`;
};

// 旋轉
const rolate = () => {
  scaleData.rotate += 90;
  boxTransform();
};

// 鼠標滾輪縮放
function scaleWheel(e: any) {
  let dy = -e.deltaY || e.wheelDeltaY;
  if (dy < 0) {
    scaleData.scale -= scaleData.scaleNum;
  } else {
    // console.log('放大');
    scaleData.scale += scaleData.scaleNum;
  }
  // 邊界判斷
  if (scaleData.scale >= scaleData.scaleMax) {
    scaleData.scale = scaleData.scaleMax;
    return;
  }
  if (scaleData.scale <= scaleData.scaleMin) {
    scaleData.scale = scaleData.scaleMin;
    return;
  }
  boxTransform();
  return false;
}

// 點擊放大縮小
const rollBtn = (action: 'enlarge' | 'zoomin') => {
  if (action === 'enlarge') {
    scaleData.scale += scaleData.scaleBtn;
  } else {
    scaleData.scale -= scaleData.scaleBtn;
  }
  // 邊界判斷
  if (scaleData.scale >= scaleData.scaleMax) {
    scaleData.scale = scaleData.scaleMax;
    return;
  }
  if (scaleData.scale <= scaleData.scaleMin) {
    scaleData.scale = scaleData.scaleMin;
    return;
  }
  boxTransform();
};

// 拖拽(box容器拖拽)
function dragstart(e: MouseEvent) {
  refs.box.value.style.transition = 'none';
  e.preventDefault(); // 阻止默認事件
  const box = refs.box.value as HTMLElement;
  const wrapper = refs.wrapper.value as HTMLElement;
  dragData.x = e.pageX - box.offsetLeft;
  dragData.y = e.pageY - box.offsetTop;

  // 添加鼠標移動事件
  document.addEventListener('mousemove', move);
  function move(event: any) {
    // 計算元素的位置
    dragData.left = event.pageX - dragData.x;
    dragData.top = event.pageY - dragData.y;
    // 邊界判斷可以在這裏添加 ↓

    // 設置元素的位置
    box.style.left = dragData.left + 'px';
    box.style.top = dragData.top + 'px';
  }
  // 添加鼠標擡起事件,鼠標擡起,將事件移除
  document.addEventListener('mouseup', function () {
    document.removeEventListener('mousemove', move);
  });
  // 鼠標離開父級元素,把事件移除
  document.addEventListener('mouseout', function () {
    document.removeEventListener('mousemove', move);
  });
}
</script>

<style scoped lang="scss">
.zhjxMain {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background-color: #000000;
  overflow: hidden;
  .content {
    width: 100%;
    height: 100%;
    margin: 0 auto;
    background-color: #000000;
    display: flex;
    overflow: hidden;

    .left-box {
      width: 50%;
      height: 100%;
      background-color: #262626;
      margin-right: 10px;
      position: relative;
      overflow: hidden;
      .box {
        width: 80%;
        height: 100%;
        object-fit: contain;
        user-select: none; /* 不可選中,爲了拖拽時不讓文字高亮 */
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: flex;
        align-items: center;
        justify-content: center;
        .vue-pdf-embed {
          width: 100%;
        }
      }
      .zoomin-wrapper {
        position: absolute;
        top: 50%;
        right: 20px;
        transform: translateY(-50%);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        img {
          width: 34px;
          height: 34px;
          cursor: pointer;
          margin: 5px 0;
        }
      }
      .center {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }
      .bottom-left {
        position: absolute;
        bottom: 20px;
        left: 20px;
      }
    }
    .right-box {
      width: 50%;
      height: 100%;
      background-color: #000000;
      margin-left: 10px;
      padding: 10px 0;
      overflow-y: scroll;
      //   修改滾動條
      &::-webkit-scrollbar {
        width: 10px;
      }
      &::-webkit-scrollbar-thumb {
        background: #333333;
        border-radius: 10px;
      }
      .item {
        min-height: 48px;
        display: flex;
        padding-right: 50px;
        & + .item {
          margin-top: 10px;
        }
        .label {
          width: 100px;
          min-height: 48px;
          display: flex;
          align-items: center;
          justify-content: center;
          color: #ffffff;
          font-size: 14px;
          font-weight: 600;
          padding: 0 5px;
        }
        .text {
          flex: 1;
          color: #ffffff;
          background-color: #262626;
          border-radius: 10px;
          font-size: 14px;
          font-weight: 600;
          line-height: 24px;
          padding: 10px 20px;
        }
      }
    }
  }
}
</style>

具體請參考鏈接

Vue3 實現 PDF 文件在線預覽功能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章