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' import App from './App.vue'
if (typeof createApp === 'function') {
createApp(App).mount('#app')
} else {
new Vue({ new Vue({
el: '#app', el: '#app',
render: (h) => h(App), render: h => h(App),
}) })
}

View File

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

View File

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

View File

@ -2,13 +2,13 @@
<div class="rotate-wrapper" :style="loaded && contentStyle"> <div class="rotate-wrapper" :style="loaded && contentStyle">
<!-- TODO: slot content (MutationObserver) --> <!-- TODO: slot content (MutationObserver) -->
<img <img
v-if="src"
class="image" class="image"
ref="img" ref="img"
:src="src" :src="src"
:style="imageStyle" :style="imageStyle"
@load="handleLoaded" @load="handleLoaded('template')"
/> />
<!-- FIXME: img load event never triggered -->
</div> </div>
</template> </template>
@ -16,7 +16,9 @@
export default { export default {
name: 'RotateWrapper', name: 'RotateWrapper',
props: { props: {
src: String, src: {
type: String,
},
rotateDeg: Number, rotateDeg: Number,
duration: { duration: {
type: Number, type: Number,
@ -48,6 +50,23 @@ export default {
}, },
}, },
watch: { 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() { async rotateDeg() {
await this.$nextTick() await this.$nextTick()
this._timer = setInterval(() => { this._timer = setInterval(() => {

View File

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

2282
yarn.lock

File diff suppressed because it is too large Load Diff