feat: vue3 compatible

master
丁锐 2021-12-06 16:44:34 +08:00
parent ba8da67764
commit 7936bd38fc
9 changed files with 2316 additions and 161 deletions

View File

@ -1,8 +1,12 @@
import Vue from 'vue'
import Vue, { createApp } from 'vue'
import App from './App.vue'
if (typeof createApp === 'function') {
createApp(App).mount('#app')
} else {
new Vue({
el: '#app',
render: (h) => h(App),
render: h => h(App),
})
}

View File

@ -29,6 +29,7 @@
"devDependencies": {
"@babel/core": "^7.14.2",
"@babel/preset-env": "^7.14.2",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/compiler-sfc": "^3.0.11",
"babel-loader": "^8.2.3",
"css-loader": "^5.2.6",
@ -45,11 +46,10 @@
"style-loader": "^3.3.1",
"uglifyjs-webpack-plugin": "^2.2.0",
"url-loader": "^4.1.1",
"vue": "^2.6.12",
"vue-loader": "^15.9.7",
"vue": "^3.2.23",
"vue-loader": "16",
"vue-loader-next": "npm:vue-loader@^16.2.0",
"vue-style-loader": "^4.1.3",
"vue-template-compiler": "^2.6.12",
"webpack": "5",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^3.11.2",

View File

@ -3,14 +3,16 @@
<div class="pdf-viewer__header" :class="{ 'not-ready': !isReady }">
<ViewerPageSelector
:total="total"
:page.sync="page"
:page="page"
:zoom="zoom"
:controls="controls"
:rotate.sync="rotate"
:rotate="rotate"
:isFullpage="isFullpage"
:filename="filename"
:isReady="isReady"
@toggleFullpage="handleToggleFullpage"
@update:page="v => (page = v)"
@update:rotate="v => (rotate = v)"
@update:zoom="handleUpdateZoom"
@toggleCatalog="handleToggleCatalog"
@print="handlePrint"
@ -34,17 +36,19 @@
<Viewer
ref="viewer"
:source="source"
:page.sync="page"
:page="page"
:total="total"
:zoom="zoom"
:catalogVisible="catalogVisible"
:rotate="rotate"
:style="viewerStyle"
:isFullpage="isFullpage"
:filename.sync="filename"
:filename="filename"
@update:page="v => (page = v)"
@update:zoom="handleUpdateZoom"
@update:isLoading="handleUpdateLoadingState"
@update:isRendering="handleUpdateRenderingState"
@update:filename="v => (filename = v)"
@password-requested="handlePasswordRequest"
@loaded="handleLoaded"
@loading-failed="handleLoadingFailed"
@ -85,6 +89,14 @@ export default {
]
},
},
loadingText: {
type: String,
default: 'Loading',
},
renderingText: {
type: String,
default: 'Rendering',
},
},
components: {
Viewer,
@ -112,10 +124,10 @@ export default {
return [this.isLoading, this.isRendering]
},
loadingContent() {
return `${this.loadingText || 'Loading'} ${this.dotText}`
return `${this.loadingText} ${this.dotText}`
},
renderingContent() {
return `${this.renderingText || 'Rendering'} ${this.dotText}`
return `${this.renderingText} ${this.dotText}`
},
dotText() {
const len = (this.seconds % 3) + 1
@ -161,7 +173,7 @@ export default {
})
},
handlePrint() {
this.$refs.viewer.print()
this.$refs.viewer.printPDF()
},
handlePreventDefault(evt) {
evt.preventDefault()

View File

@ -0,0 +1,68 @@
import { createVNode, ref, createApp, onMounted, onBeforeUpdate } from 'vue'
import style from '!!css-loader!!sass-loader!./iframe.scss'
export default {
name: 'IFrame',
props: {
css: {
type: String,
default: '',
},
},
setup(props, { slots, expose }) {
const iframeRef = ref(null)
const iframeBody = ref(null)
const iframeHead = ref(null)
const iframeStyle = ref(null)
let iframeApp = null
const appendStyle = style => {
const cssEl = document.createElement('STYLE')
cssEl.textContent = style
iframeHead.value.appendChild(cssEl)
return cssEl
}
const getContentWindow = () => {
return iframeRef.value.contentWindow
}
expose({
appendStyle,
getContentWindow,
})
onMounted(() => {
iframeBody.value = iframeRef.value.contentDocument.body
iframeHead.value = iframeRef.value.contentDocument.head
const el = document.createElement('div')
iframeBody.value.appendChild(el)
appendStyle(style)
if (props.css) {
iframeStyle.value = appendStyle(props.css)
}
iframeApp = createApp({
name: 'iApp',
setup() {
return () => slots.default()
},
}).mount(el)
})
onBeforeUpdate(() => {
if (!iframeApp || !iframeRef.value) {
return
}
if (props.css) {
iframeStyle.value.innerHTML = props.css
}
})
return () =>
createVNode('iframe', {
ref: iframeRef,
style: { width: '100%', height: '100%', border: 'none' },
})
},
}

View File

@ -0,0 +1,7 @@
import IFrameV3 from './IFrameV3'
import IFrame from './IFrame'
export default {
IFrameV3,
IFrame,
}

View File

@ -9,6 +9,9 @@ import Iconfont from './Iconfont.vue'
export default {
name: 'IconButton',
// vue3 triggers click event twice
// https://github.com/vuejs/vue-next/commit/e1660f4338fbf4d2a434e13193a58e00f844379b
inheritAttrs: false,
components: {
Iconfont,
},

View File

@ -2,13 +2,13 @@
<div class="rotate-wrapper" :style="loaded && contentStyle">
<!-- TODO: slot content (MutationObserver) -->
<img
v-if="src"
class="image"
ref="img"
:src="src"
:style="imageStyle"
@load="handleLoaded"
@load="handleLoaded('template')"
/>
<!-- FIXME: img load event never triggered -->
</div>
</template>
@ -16,7 +16,9 @@
export default {
name: 'RotateWrapper',
props: {
src: String,
src: {
type: String,
},
rotateDeg: Number,
duration: {
type: Number,
@ -48,6 +50,23 @@ export default {
},
},
watch: {
src: {
immediate: true,
handler(n) {
this.loaded = false
if (n) {
const image = document.createElement('img')
image.src = n
const tmp = image.cloneNode()
// blob src does not fire onload
tmp.onload = async () => {
await this.$nextTick()
this.handleLoaded()
}
}
},
},
async rotateDeg() {
await this.$nextTick()
this._timer = setInterval(() => {

View File

@ -1,5 +1,5 @@
<template>
<IFrame :css="$options.viewerStyle" ref="iframe">
<CompIFrame :css="$options.viewerStyle" ref="iframe">
<div class="viewer-container" ref="container">
<div
class="scroller catalog"
@ -10,23 +10,23 @@
>
<div class="catalog-container">
<div
ref="catalogItem"
v-for="page in pages"
:ref="`catalogItem_${idx}`"
v-for="(image, idx) in imageList"
class="catalog-item"
:key="page"
:key="idx"
>
<div>
<div
ref="catalogItemContent"
class="catalog-item__content"
:class="activePage === page && 'active'"
@click="handleSwitchPage(page)"
:class="activePage === idx + 1 && 'active'"
@click="handleSwitchPage(idx + 1)"
>
<RotateWrapper :src="imageList[page - 1]" :rotateDeg="rotate" />
<RotateWrapper :src="image" :rotateDeg="rotate" />
</div>
</div>
<div class="catalog-index">
{{ page }}
{{ idx + 1 }}
</div>
</div>
</div>
@ -39,8 +39,8 @@
>
<div class="viewer-content" ref="viewerContent">
<div
ref="viewerItem"
v-for="page in pages"
:ref="`viewerItem_${idx}`"
v-for="(page, idx) in pages"
:key="page"
:style="viewerItemStyle"
class="viewer-item"
@ -60,12 +60,14 @@
</div>
</div>
</div>
</IFrame>
</CompIFrame>
</template>
<script>
import { version } from 'vue'
import * as PDF from 'pdfjs-dist/es5/build/pdf.js'
import PDFWorker from 'pdfjs-dist/es5/build/pdf.worker.js'
import IFrameV3 from '../IFrame/IFrameV3'
import IFrame from '../IFrame/IFrame.vue'
import throttle from '../../utils/throttle'
import viewerStyle from '!!css-loader!!sass-loader!./Viewer.scss'
@ -73,6 +75,7 @@ import getPageBlobList from './getPageBlobList.js'
import RotateWrapper from '../RotateWrapper/RotateWrapper.vue'
import rotateWrapperStyle from '!!css-loader!!sass-loader!../RotateWrapper/RotateWrapper.scss'
const VUE_VERSION = Number(version.split('.')[0])
PDF.GlobalWorkerOptions.workerPort = new PDFWorker()
const MARGIN_OFFSET = 20
@ -101,7 +104,7 @@ export default {
viewerStyle: viewerStyle.toString(),
rotateWrapperStyle: rotateWrapperStyle.toString(),
components: {
IFrame,
CompIFrame: VUE_VERSION === 3 ? IFrameV3 : IFrame,
RotateWrapper,
},
data() {
@ -199,9 +202,8 @@ export default {
window.removeEventListener('resize', this.handleResize)
},
methods: {
print() {
printPDF() {
const contentWindow = this.$refs.iframe.getContentWindow()
contentWindow?.print()
},
updateZoomFullpage() {
@ -295,14 +297,18 @@ export default {
}
this.imageList = []
this.viewerImageList = []
const promiseList = blobs.map((blobData, idx) => {
const image = getImage(blobData)
this.imageList = [...this.imageList, image.src]
const viewerImg = image.cloneNode()
const url = URL.createObjectURL(blobData.blob)
this.imageList = [...this.imageList, url]
// const catalogImg = image.cloneNode()
// replacePlaceholder(this.$refs.catalogItemContent[idx], catalogImg)
const viewerImg = image.cloneNode()
this.viewerImageList = [...this.viewerImageList, viewerImg.src]
replacePlaceholder(this.$refs.viewerItem[idx], viewerImg)
this.viewerImageList = [...this.viewerImageList, url]
replacePlaceholder(this.$refs[`viewerItem_${idx}`], viewerImg)
// const catalogLoaded = new Promise(resolve => {
// catalogImg.onload = resolve
@ -332,10 +338,10 @@ export default {
},
syncViewerOffset(page) {
if (this.isScrolling) return
this.$refs.viewerItem[page - 1].scrollIntoView()
this.$refs[`viewerItem_${page - 1}`].scrollIntoView()
},
syncCatalogOffset(page) {
const target = this.$refs.catalogItem[page - 1]
const target = this.$refs[`catalogItem_${page - 1}`]
target.scrollIntoView({
block: 'nearest',

2282
yarn.lock

File diff suppressed because it is too large Load Diff