feat: 1.0 viewer

This commit is contained in:
丁锐 committed 2021-12-01 12:08:03 +08:00
commit c6805f2dac
32 files changed
+9109

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
{
"extends": [
"eslint:recommended",
"plugin:vue/essential",
"prettier"
],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "warn",
"arrow-parens": ["warn", "as-needed", { "requireForBlockBody": false }]
}
}
+22
View File
@@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
# Local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+8
View File
@@ -0,0 +1,8 @@
{
"arrowParens": "avoid",
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"htmlWhitespaceSensitivity": "ignore",
"trailingComma": "es5"
}
+7
View File
@@ -0,0 +1,7 @@
Copyright (C) 2021 dingrui12138
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+57
View File
@@ -0,0 +1,57 @@
<template>
<div>
<button @click="handleSwitchUrl('test1')">switch url 1</button>
<button @click="handleSwitchUrl('test2')">switch url 2</button>
<button @click="handleSwitchUrl('base64')">switch base64</button>
<PDFViewer
:source="pdfSource"
style="height: 100vh; width: 100%"
@download="handleDownload"
/>
</div>
</template>
<script>
import PDFViewer from '../src/index.js'
const TEST_URL_MAP = {
test1: '/demo/static/pdf/helloworld.pdf',
test2:
'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf',
base64: `data:application/octet-stream;base64,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 truncated
}
export default {
components: {
PDFViewer,
},
data() {
return {
pdfSource: TEST_URL_MAP.test1,
}
},
methods: {
handleSwitchUrl(key) {
this.pdfSource = TEST_URL_MAP[key]
},
handleDownload(source) {
alert(`custom download file: ${source}`)
},
},
}
</script>
<style lang="scss">
body {
padding: 0;
margin: 0;
// background-color: #ccc;
}
// canvas {
// display: block;
// margin: auto;
// max-width: 800px;
// box-shadow: 0 2px 8px 4px rgba(0, 0, 0, 0.1);
// }
</style>
+12
View File
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="referrer" content="no-referrer-when-downgrade" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>vue-pdf-viewer demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
+8
View File
@@ -0,0 +1,8 @@
import Vue from 'vue'
import App from './App.vue'
new Vue({
el: '#app',
render: (h) => h(App),
})
Binary file not shown.
+61
View File
@@ -0,0 +1,61 @@
{
"name": "vue-pdf-viewer",
"version": "0.2.0",
"description": "PDF viewer component for Vue 2 and Vue 3",
"keywords": [
"vue",
"vuejs",
"pdf"
],
"main": "dist/vue3-pdf-viewer.js",
"types": "types/index.d.ts",
"files": [
"dist/*",
"src/*"
],
"license": "MIT",
"author": "Wayne (dingrui12138@gmail.com)",
"repository": "",
"scripts": {
"test": "webpack -v",
"build": "webpack build --progress",
"serve": "webpack serve --config webpack.dev.config.js --hot",
"lint": "eslint src/**",
"lint:fix": "eslint src/** --fix"
},
"dependencies": {
"pdfjs-dist": "2.5.207"
},
"devDependencies": {
"@babel/core": "^7.14.2",
"@babel/preset-env": "^7.14.2",
"@vue/compiler-sfc": "^3.0.11",
"babel-loader": "^8.2.3",
"css-loader": "^5.2.6",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.10.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.2",
"node-sass": "^6.0.1",
"prettier": "^2.3.0",
"sass-loader": "^12.3.0",
"strip-ansi": "^7.0.1",
"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-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",
"worker-loader": "^3.0.5"
},
"peerDependencies": {
"vue": "^2.x || ^3.x"
}
}
+221
View File
@@ -0,0 +1,221 @@
<template>
<div class="pdf-viewer" @contextmenu="handlePreventDefault">
<div class="pdf-viewer__header">
<ViewerPageSelector
:total="total"
:page.sync="page"
:zoom="zoom"
:controls="controls"
:rotate.sync="rotate"
:isFullpage="isFullpage"
:filename="filename"
@toggleFullpage="handleToggleFullpage"
@update:zoom="handleUpdateZoom"
@toggleCatalog="handleToggleCatalog"
@print="handlePrint"
@download="handleDownload"
/>
</div>
<div class="pdf-viewer__body">
<div class="loading-mask" v-if="isLoading">
<slot name="loading">
<div class="loading-content">Loading...</div>
</slot>
</div>
<Viewer
ref="viewer"
:source="source"
:page.sync="page"
:total="total"
:zoom="zoom"
:catalogVisible="catalogVisible"
:rotate="rotate"
:style="viewerStyle"
:isFullpage="isFullpage"
:filename.sync="filename"
@update:zoom="handleUpdateZoom"
@update:isLoading="handleUpdateLoadingState"
@password-requested="handlePasswordRequest"
@loaded="handleLoaded"
@loading-failed="handleLoadingFailed"
@rendered="handleDocumentRender"
/>
</div>
<iframe ref="iframe" style="display: none" />
</div>
</template>
<script>
import './assets/iconfont/iconfont.css'
import Viewer from './components/Viewer/Viewer.vue'
// import Viewer from './Viewer.vue'
import ViewerPageSelector from './components/ViewerPageSelector.vue'
export default {
name: 'PDFViewer',
props: {
source: {
type: String,
required: true,
},
controls: {
type: Array,
default: () => {
return [
'download',
'print',
'double',
'fullscreen',
'abort',
'fullpage',
'rotate',
'zoom',
'catalog',
'switchPage',
]
},
},
},
components: {
Viewer,
ViewerPageSelector,
},
data() {
return {
isLoading: true,
page: 1,
total: 1,
catalogVisible: true,
zoom: 100,
rotate: 0,
isFullpage: false,
filename: '',
}
},
computed: {
viewerStyle() {
return {
visibility: this.isLoading ? 'hidden' : 'visible',
}
},
},
methods: {
handleDownload() {
this.$emit('download', this.source)
},
handlePrint() {
this.$refs.viewer.print()
},
handlePreventDefault(evt) {
evt.preventDefault()
},
handleSwitchPage(page) {
this.page = page
},
handleUpdateZoom(zoom) {
this.zoom = zoom
},
handleToggleFullpage() {
this.isFullpage = !this.isFullpage
},
handleToggleCatalog() {
this.catalogVisible = !this.catalogVisible
},
handleLoaded(params) {
const { total } = params
this.total = total
this.$emit('loaded', params)
},
handleDocumentRender() {
this.isLoading = false
this.$emit('rendered')
},
handleUpdateLoadingState(isLoading) {
this.isLoading = isLoading
},
handlePasswordRequest({ callback, retry }) {
// TODO: slot dialog ?
callback(prompt(retry ? 'Enter password again' : 'Enter password'))
},
handleLoadingFailed(e) {
this.$emit('loading-failed', e)
},
reload() {
this.$refs.viewer.load()
},
},
}
</script>
<style lang="scss" scoped>
.icon-btn {
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
transition: all 200ms ease;
cursor: pointer;
&:hover {
background: #474545;
}
}
.pdf-viewer {
// css-var
--iron-icon-height: 20px;
--iron-icon-width: 20px;
--viewer-icon-ink-color: rgb(189, 189, 189);
--viewer-pdf-toolbar-background-color: rgb(50, 54, 57);
--viewer-text-input-selection-color: rgba(255, 255, 255, 0.3);
--viewer-pdf-toolbar-height: 56px;
background-color: #ccc;
height: 100%;
// max-height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
min-width: 500px;
&__header {
flex-grow: 0;
flex-shrink: 0;
align-items: center;
background-color: var(--viewer-pdf-toolbar-background-color);
color: white;
display: flex;
height: var(--viewer-pdf-toolbar-height);
padding: 0 16px;
box-shadow: 0px 3px 10px 2px black;
z-index: 999;
}
&__body {
height: calc(100% - 56px);
width: 100%;
overflow: hidden;
position: relative;
.loading-mask {
position: absolute;
z-index: 999;
top: 0;
left: 0;
height: 100%;
width: 100%;
pointer-events: none;
.loading-content {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
</style>
+539
View File
@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
+395
View File
@@ -0,0 +1,395 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2911558" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe9c4;</span>
<div class="name">高度</div>
<div class="code-name">&amp;#xe9c4;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62b;</span>
<div class="name">减少</div>
<div class="code-name">&amp;#xe62b;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe614;</span>
<div class="name">设计目录</div>
<div class="code-name">&amp;#xe614;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe7bd;</span>
<div class="name">下载</div>
<div class="code-name">&amp;#xe7bd;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64d;</span>
<div class="name">加减组件 加号</div>
<div class="code-name">&amp;#xe64d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe609;</span>
<div class="name">旋转</div>
<div class="code-name">&amp;#xe609;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe605;</span>
<div class="name">dots</div>
<div class="code-name">&amp;#xe605;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">高度</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe9c3;</span>
<div class="name">24gf-printer</div>
<div class="code-name">&amp;#xe9c3;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1636102957536') format('woff2'),
url('iconfont.woff?t=1636102957536') format('woff'),
url('iconfont.ttf?t=1636102957536') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont pdf-viewer--fullpage"></span>
<div class="name">
高度
</div>
<div class="code-name">.pdf-viewer--fullpage
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--minus"></span>
<div class="name">
减少
</div>
<div class="code-name">.pdf-viewer--minus
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--catalog"></span>
<div class="name">
设计目录
</div>
<div class="code-name">.pdf-viewer--catalog
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--download"></span>
<div class="name">
下载
</div>
<div class="code-name">.pdf-viewer--download
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--plus"></span>
<div class="name">
加减组件 加号
</div>
<div class="code-name">.pdf-viewer--plus
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--rotate"></span>
<div class="name">
旋转
</div>
<div class="code-name">.pdf-viewer--rotate
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--dots"></span>
<div class="name">
dots
</div>
<div class="code-name">.pdf-viewer--dots
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--auto-width"></span>
<div class="name">
高度
</div>
<div class="code-name">.pdf-viewer--auto-width
</div>
</li>
<li class="dib">
<span class="icon iconfont pdf-viewer--printer"></span>
<div class="name">
24gf-printer
</div>
<div class="code-name">.pdf-viewer--printer
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont pdf-viewer--xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--fullpage"></use>
</svg>
<div class="name">高度</div>
<div class="code-name">#pdf-viewer--fullpage</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--minus"></use>
</svg>
<div class="name">减少</div>
<div class="code-name">#pdf-viewer--minus</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--catalog"></use>
</svg>
<div class="name">设计目录</div>
<div class="code-name">#pdf-viewer--catalog</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--download"></use>
</svg>
<div class="name">下载</div>
<div class="code-name">#pdf-viewer--download</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--plus"></use>
</svg>
<div class="name">加减组件 加号</div>
<div class="code-name">#pdf-viewer--plus</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--rotate"></use>
</svg>
<div class="name">旋转</div>
<div class="code-name">#pdf-viewer--rotate</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--dots"></use>
</svg>
<div class="name">dots</div>
<div class="code-name">#pdf-viewer--dots</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--auto-width"></use>
</svg>
<div class="name">高度</div>
<div class="code-name">#pdf-viewer--auto-width</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#pdf-viewer--printer"></use>
</svg>
<div class="name">24gf-printer</div>
<div class="code-name">#pdf-viewer--printer</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>
+51
View File
@@ -0,0 +1,51 @@
@font-face {
font-family: "iconfont"; /* Project id 2911558 */
src: url('iconfont.woff2?t=1636102957536') format('woff2'),
url('iconfont.woff?t=1636102957536') format('woff'),
url('iconfont.ttf?t=1636102957536') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.pdf-viewer--fullpage:before {
content: "\e9c4";
}
.pdf-viewer--minus:before {
content: "\e62b";
}
.pdf-viewer--catalog:before {
content: "\e614";
}
.pdf-viewer--download:before {
content: "\e7bd";
}
.pdf-viewer--plus:before {
content: "\e64d";
}
.pdf-viewer--rotate:before {
content: "\e609";
}
.pdf-viewer--dots:before {
content: "\e605";
}
.pdf-viewer--auto-width:before {
content: "\e600";
}
.pdf-viewer--printer:before {
content: "\e9c3";
}
+1
View File
@@ -0,0 +1 @@
!function(e){var t,l,a,i,o,d='<svg><symbol id="pdf-viewer--fullpage" viewBox="0 0 1024 1024"><path d="M881.7664 995.5328l-768 0A113.7664 113.7664 0 0 1 0 881.7664l0-768C0 50.9952 50.944 0 113.7664 0l768 0c62.8224 0 113.7664 50.944 113.76640001 113.8176l-1e-8 768a113.7664 113.7664 0 0 1-113.7664 113.7152z m28.4672-853.2992c0-31.4368-25.4976-56.8832-56.8832-56.8832L142.1824 85.3504c-31.3856 0-56.832 25.4464-56.832 56.832l0 711.168c0 31.3856 25.4464 56.8832 56.832 56.8832l711.168 1e-8c31.3856 0 56.832-25.4464 56.832-56.88320001L910.1824 142.1824zM611.84 358.912l-71.42400001-71.3728 1e-8 419.7376 69.4272-69.376a42.6496 42.6496 0 0 1 60.3136 60.3648l-132.19840001 132.1984a41.728 41.728 0 0 1-7.98719999 12.032 42.2912 42.2912 0 0 1-32.2048 12.0832 42.0864 42.0864 0 0 1-32.2048-12.0832 39.6288 39.6288 0 0 1-7.936-11.9808l-132.1984-132.2496a42.7008 42.7008 0 0 1 60.3136-60.3648l69.376 69.376L455.1168 287.5904 383.6928 358.912a42.6496 42.6496 0 1 1-60.3136-60.3136l144.2304-144.2304a42.5984 42.5984 0 0 1 60.3136 0l144.23040001 144.2304A42.6496 42.6496 0 1 1 611.84 358.912z" fill="#333333" ></path></symbol><symbol id="pdf-viewer--minus" viewBox="0 0 1024 1024"><path d="M0 479.936C0 444.64 28.448 416 64.064 416L959.936 416C995.328 416 1024 444.736 1024 479.936L1024 544.064C1024 579.392 995.552 608 959.936 608L64.064 608C28.672 608 0 579.264 0 544.064L0 479.936Z" fill="#333333" ></path></symbol><symbol id="pdf-viewer--catalog" viewBox="0 0 1639 1024"><path d="M141.21142578 215.36914062C141.21142578 174.41296952 174.12742539 141.21142578 215.36914062 141.21142578 256.32531173 141.21142578 289.52685547 174.12742539 289.52685547 215.36914062 289.52685547 256.32531173 256.61085586 289.52685547 215.36914062 289.52685547 174.41296952 289.52685547 141.21142578 256.61085586 141.21142578 215.36914062ZM437.84228516 215.36914062C437.84228516 174.41296952 471.64047914 141.21142578 512.18341433 141.21142578L1253.3937341 141.21142578C1294.45120508 141.21142578 1327.73486328 174.12742539 1327.73486328 215.36914062 1327.73486328 256.32531173 1293.9366693 289.52685547 1253.3937341 289.52685547L512.18341433 289.52685547C471.12594337 289.52685547 437.84228516 256.61085586 437.84228516 215.36914062ZM141.21142578 512C141.21142578 471.0438289 174.12742539 437.84228516 215.36914062 437.84228516 256.32531173 437.84228516 289.52685547 470.75828476 289.52685547 512 289.52685547 552.9561711 256.61085586 586.15771484 215.36914062 586.15771484 174.41296952 586.15771484 141.21142578 553.24171524 141.21142578 512ZM437.84228516 512C437.84228516 471.0438289 471.64047914 437.84228516 512.18341433 437.84228516L1253.3937341 437.84228516C1294.45120508 437.84228516 1327.73486328 470.75828476 1327.73486328 512 1327.73486328 552.9561711 1293.9366693 586.15771484 1253.3937341 586.15771484L512.18341433 586.15771484C471.12594337 586.15771484 437.84228516 553.24171524 437.84228516 512ZM141.21142578 808.63085938C141.21142578 767.67468827 174.12742539 734.47314453 215.36914062 734.47314453 256.32531173 734.47314453 289.52685547 767.38914414 289.52685547 808.63085938 289.52685547 849.58703048 256.61085586 882.78857422 215.36914062 882.78857422 174.41296952 882.78857422 141.21142578 849.87257461 141.21142578 808.63085938ZM437.84228516 808.63085938C437.84228516 767.67468827 471.64047914 734.47314453 512.18341433 734.47314453L1253.3937341 734.47314453C1294.45120508 734.47314453 1327.73486328 767.38914414 1327.73486328 808.63085938 1327.73486328 849.58703048 1293.9366693 882.78857422 1253.3937341 882.78857422L512.18341433 882.78857422C471.12594337 882.78857422 437.84228516 849.87257461 437.84228516 808.63085938Z" fill="#333333" ></path></symbol><symbol id="pdf-viewer--download" viewBox="0 0 1024 1024"><path d="M877.49 381.468H668.638V68.191H355.36v313.277H146.51l365.489 365.49 365.49-365.49zM146.51 851.383v104.425h730.98V851.383H146.51z" fill="#333333" ></path></symbol><symbol id="pdf-viewer--plus" viewBox="0 0 1024 1024"><path d="M426.666667 426.666667H85.546667A85.418667 85.418667 0 0 0 0 512c0 47.445333 38.314667 85.333333 85.546667 85.333333H426.666667v341.12c0 47.274667 38.186667 85.546667 85.333333 85.546667 47.445333 0 85.333333-38.314667 85.333333-85.546667V597.333333h341.12A85.418667 85.418667 0 0 0 1024 512c0-47.445333-38.314667-85.333333-85.546667-85.333333H597.333333V85.546667A85.418667 85.418667 0 0 0 512 0c-47.445333 0-85.333333 38.314667-85.333333 85.546667V426.666667z" fill="#333333" ></path></symbol><symbol id="pdf-viewer--rotate" viewBox="0 0 1024 1024"><path d="M817.93082 36.365911a56.92864 56.92864 0 0 0-45.960737-36.037396 57.973202 57.973202 0 0 0-52.228111 22.458087l-35.515115 47.0053A441.327532 441.327532 0 0 0 537.988148 44.722409 446.550344 446.550344 0 0 0 124.341514 323.620518 52.22811 52.22811 0 0 0 152.022413 392.039342a56.92864 56.92864 0 0 0 20.368963 4.178249A52.22811 52.22811 0 0 0 220.441237 365.925287 343.660966 343.660966 0 0 1 537.988148 149.178629a317.02463 317.02463 0 0 1 79.909009 10.967903l-19.324401 25.591775A55.884078 55.8840Line truncated
+72
View File
@@ -0,0 +1,72 @@
{
"id": "2911558",
"name": "pdf-viewer",
"font_family": "iconfont",
"css_prefix_text": "pdf-viewer--",
"description": "",
"glyphs": [
{
"icon_id": "25497851",
"name": "高度",
"font_class": "fullpage",
"unicode": "e9c4",
"unicode_decimal": 59844
},
{
"icon_id": "1048890",
"name": "减少",
"font_class": "minus",
"unicode": "e62b",
"unicode_decimal": 58923
},
{
"icon_id": "1206330",
"name": "设计目录",
"font_class": "catalog",
"unicode": "e614",
"unicode_decimal": 58900
},
{
"icon_id": "1722990",
"name": "下载",
"font_class": "download",
"unicode": "e7bd",
"unicode_decimal": 59325
},
{
"icon_id": "2939198",
"name": "加减组件 加号",
"font_class": "plus",
"unicode": "e64d",
"unicode_decimal": 58957
},
{
"icon_id": "4138746",
"name": "旋转",
"font_class": "rotate",
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "6403457",
"name": "dots",
"font_class": "dots",
"unicode": "e605",
"unicode_decimal": 58885
},
{
"icon_id": "6745215",
"name": "高度",
"font_class": "auto-width",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "7577336",
"name": "24gf-printer",
"font_class": "printer",
"unicode": "e9c3",
"unicode_decimal": 59843
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+57
View File
@@ -0,0 +1,57 @@
<script>
import Vue from 'vue'
import style from '!!css-loader!!sass-loader!./iframe.scss'
export default {
props: {
css: String,
},
render(h) {
return h('iframe', {
on: { load: this.renderChildren },
style: { width: '100%', height: '100%', border: 'none' },
})
},
beforeUpdate() {
// freezing to prevent unnessessary Reactifiation of vNodes
this.iApp.children = Object.freeze(this.$slots.default)
},
methods: {
getContentWindow() {
return this.$el.contentWindow
},
appendStyle(style) {
const cssEl = document.createElement('STYLE')
cssEl.textContent = style
this.$el.contentDocument.head.appendChild(cssEl)
},
renderChildren() {
// inject iframe styles
this.appendStyle(style)
if (this.css) {
// inject component style
this.appendStyle(this.css)
}
const children = this.$slots.default
const body = this.$el.contentDocument.body
const el = document.createElement('DIV') // we will mount or nested app to this element
body.appendChild(el)
const iApp = new Vue({
name: 'iApp',
// freezing to prevent unnessessary Reactifiation of vNodes
data: { children: Object.freeze(children) },
render(h) {
return h('div', this.children)
},
})
iApp.$mount(el) // mount into iframe
this.iApp = iApp // cache instance for later updates
},
},
}
</script>
+4
View File
@@ -0,0 +1,4 @@
* {
padding: 0;
margin: 0;
}
+48
View File
@@ -0,0 +1,48 @@
<template>
<button :disabled="disabled" class="icon-btn" @click="$emit('click')">
<Iconfont :name="name" />
</button>
</template>
<script>
import Iconfont from './Iconfont.vue'
export default {
name: 'IconButton',
components: {
Iconfont,
},
props: {
name: String,
disabled: Boolean,
},
}
</script>
<style lang="scss" scoped>
.icon-btn {
margin-inline-start: 4px;
width: 36px;
height: 36px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
transition: all 200ms ease;
cursor: pointer;
background: transparent;
border: none;
color: white;
&:hover {
background: #5c5c5c;
}
&:first-child {
margin-inline-start: 0px;
}
&:disabled {
background: transparent;
cursor: not-allowed;
color: rgb(134, 134, 134);
}
}
</style>
+53
View File
@@ -0,0 +1,53 @@
<template>
<i v-if="isFontClass" :class="className" @click="$emit('click')">
<slot />
</i>
<svg v-else class="icon" aria-hidden="true">
<use :xlink:href="icon" />
</svg>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true,
},
useSymbol: {
type: Boolean,
default: false,
},
prefix: {
type: String,
default: 'pdf-viewer--',
},
},
computed: {
icon() {
return this.useSymbol
? `#icon-${this.name}`
: `${this.prefix}${this.name}`
},
className() {
return `iconfont ${this.icon}`
},
isFontClass() {
return !this.useSymbol
},
},
}
</script>
<style lang="scss" scoped>
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.iconfont {
font-size: 14px;
}
</style>
+114
View File
@@ -0,0 +1,114 @@
.viewer-container {
display: flex;
background: rgb(82, 86, 89);
height: 100%;
.scroller {
height: 100%;
// scroll-behavior: smooth;
overflow: auto;
&.catalog {
background: #323639;
width: 300px;
flex-shrink: 0;
flex-grow: 0;
transition: all 500ms ease;
margin-left: -300px;
&.visible {
margin-left: 0;
}
.catalog-content {
padding-bottom: 20px;
.catalog-item {
color: white;
padding-top: 20px;
text-align: center;
font-size: 0;
&:first-child {
// margin-top: 0;
}
.catalog-index {
text-align: center;
margin-top: 10px;
font-size: 13px;
height: 14px;
line-height: 14px;
}
.canvas {
width: 110px;
// height: 152px;
opacity: 0.8;
&.active {
box-shadow: 0 0 0 4px rgb(84, 201, 255);
opacity: 1;
}
&:hover {
opacity: 1;
}
}
}
}
}
&.viewer {
flex: 1;
.viewer-content {
padding-top: 20px;
.viewer-item {
padding-bottom: 20px;
font-size: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
page-break-after: always;
.canvas {
white-space: nowrap;
// margin: 0 auto;
// display: block;
margin: initial;
max-width: initial;
// margin-bottom: 20px;
box-shadow: 0px 0px 7px 6px rgba($color: #000000, $alpha: 0.25);
transition: transform 300ms ease;
}
}
}
}
}
}
@media print {
html, body {
width: 210mm;
height: 297mm;
}
.viewer-container {
height: auto !important;
overflow: initial !important;
background-color: white!important;
.catalog {
display: none !important;
}
.viewer {
overflow: initial !important;
height: auto !important;
&-content {
padding: 0!important;
.viewer-item {
page-break-after: always!important;
padding: 0!important;
.canvas {
width: 100% !important;
box-shadow: none!important;
}
}
}
}
}
}
@media screen and (max-width: 800px) {
.viewer-container {
.catalog {
margin-left: -300px!important;
}
}
}
+328
View File
@@ -0,0 +1,328 @@
<template>
<IFrame :css="$options.style" ref="iframe">
<div class="viewer-container" ref="container">
<div
class="scroller catalog"
ref="catalogScroller"
:class="{
visible: catalogVisible,
}"
>
<div class="catalog-content" ref="catalogContent">
<div
ref="catalogItem"
v-for="page in pages"
class="catalog-item"
:key="page"
>
<div class="test" :style="thumbnailItemStyle">
<canvas
ref="catalogCanvas"
class="canvas"
:class="activePage === page && 'active'"
:style="thumbnailStyle"
@click="handleSwitchPage(page)"
/>
</div>
<div class="catalog-index">
{{ page }}
</div>
</div>
</div>
</div>
<div
class="scroller viewer"
ref="viewerScroller"
@scroll="handleViewerScroll"
>
<div class="viewer-content" ref="viewerContent">
<div
v-for="page in pages"
:key="page"
class="viewer-item"
:style="viewerItemStyle"
>
<canvas ref="viewerCanvas" class="canvas" :style="viewerStyle" />
</div>
</div>
</div>
</div>
</IFrame>
</template>
<script>
import * as PDF from 'pdfjs-dist/es5/build/pdf.js'
import PDFWorker from 'pdfjs-dist/es5/build/pdf.worker.js'
import IFrame from '../IFrame/IFrame.vue'
import throttle from '../../utils/throttle'
import style from '!!css-loader!!sass-loader!./Viewer.scss'
PDF.GlobalWorkerOptions.workerPort = new PDFWorker()
const MARGIN_OFFSET = 20
const NORMAL_RATIO = 2
export default {
name: 'Viewer',
props: {
page: Number,
total: Number,
zoom: Number,
rotate: Number,
catalogVisible: Boolean,
isFullpage: Boolean,
source: {
type: [Object, String],
required: true,
},
},
style: style.toString(),
components: {
IFrame,
},
data() {
return {
document: null,
viewerContentHeight: 0,
viewportHeight: 0,
viewportWidth: 0,
}
},
computed: {
isHorizontalViewer() {
return (this.rotate / 90) % 2 === 1
},
activePage() {
return this.page
},
pages() {
return [...Array(this.total + 1).keys()].slice(1)
},
viewerStyle() {
return {
// height: `${this.zoom / NORMAL_RATIO}%`,
width: `${(this.zoom / NORMAL_RATIO / 100) * this.viewportWidth}px`,
transform: `rotate(${this.rotate}deg)`,
}
},
viewerItemStyle() {
return this.isHorizontalViewer
? {
height: `${
(this.zoom / NORMAL_RATIO / 100) * this.viewportWidth
}px`,
}
: {}
},
thumbnailItemStyle() {
return this.isHorizontalViewer
? {
height: `${
(this.zoom / NORMAL_RATIO / 100) * 300 // + 14 // catalog width: 300px; index height: 14px
}px`,
}
: {}
},
thumbnailStyle() {
return {
transform: `rotate(${this.rotate}deg)`,
}
},
},
watch: {
isFullpage(n, o) {
if (n !== o && n) {
this.updateZoomFullpage()
}
},
viewerStyle: {
deep: true,
async handler() {
await this.$nextTick()
this.viewerContentHeight = this.$refs.viewerContent.clientHeight
},
},
page(n, o) {
if (n === o) return
this.scrollToPage(n)
},
source: {
immediate: true,
async handler() {
await this.load()
this.render()
},
},
},
activated() {
this.$nextTick(() => {
this.handleResize()
})
this.render()
},
mounted() {
// TODO: element resize replace window resize with Observe
this.handleResize = throttle(() => {
this.viewportHeight = this.$refs.container.clientHeight
this.viewportWidth = this.$refs.viewerScroller.clientWidth
}, 100)
window.addEventListener('resize', this.handleResize)
this.$nextTick(() => {
this.handleResize()
})
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
},
methods: {
print() {
const contentWindow = this.$refs.iframe.getContentWindow()
contentWindow?.print()
},
updateZoomFullpage() {
const perViewerHeight = this.viewerContentHeight / this.total
const rate = this.viewportHeight / (perViewerHeight - MARGIN_OFFSET)
this.$emit('update:zoom', this.zoom * rate)
this.scrollToPage(this.page)
},
handleViewerScroll(evt) {
this.isScrolling = true
const yOffset = evt.target.scrollTop
const perHeight = (this.viewerContentHeight + MARGIN_OFFSET) / this.total
const halfViewportOffset = (perHeight - this.viewportHeight) / 2
const currentPosition = (yOffset - halfViewportOffset) / perHeight + 1
const delta = currentPosition - this.page
let pageOffset = 0
if (delta > 0.5) {
pageOffset += 1
} else if (delta < -0.5) {
pageOffset -= 1
}
this.$emit('update:page', this.page + pageOffset)
this.$nextTick(() => {
this.isScrolling = false
})
},
startLoading() {
this.$emit('isLoading', true)
},
endLoading() {
this.$emit('isLoading', false)
},
handleSwitchPage(page) {
this.$emit('update:page', page)
},
async load() {
if (!this.source) {
return
}
this.startLoading()
try {
const filename = PDF.getFilenameFromUrl(this.source)
this.$emit('update:filename', filename)
const documentLoadingTask = PDF.getDocument(this.source)
documentLoadingTask.onPassword = (callback, reason) => {
const retry = reason === PDF.PasswordResponses.INCORRECT_PASSWORD
this.$emit('password-requested', {
callback,
retry,
})
}
this.document = await documentLoadingTask.promise
this.$emit('loaded', {
total: this.document.numPages,
})
} catch (e) {
this.document = null
this.$emit('loading-failed', e)
} finally {
this.endLoading()
}
},
async render() {
if (!this.document) {
return
}
try {
await this.$nextTick()
await Promise.all(
this.pages.map(async (pageNum, i) => {
const page = await this.document.getPage(pageNum)
const pageWidth = page.view[2]
const containerWidth = this.$el.clientWidth
const targetWidth = containerWidth * 0.9
const scale = targetWidth / pageWidth
// const scale = Math.ceil(this.$el.clientWidth / page.view[2]) + 1
const viewport = page.getViewport({
scale: scale,
})
// render viewer
const viewerCanvas = this.$refs.viewerCanvas[i]
viewerCanvas.width = viewport.width
viewerCanvas.height = viewport.height
const renderViewer = page.render({
canvasContext: viewerCanvas.getContext('2d'),
viewport,
}).promise
// render catalog
const catalogScale = 110 / pageWidth
const catalogViewport = page.getViewport({
scale: catalogScale,
})
const catalogCanvas = this.$refs.catalogCanvas[i]
catalogCanvas.width = catalogViewport.width
catalogCanvas.height = catalogViewport.height
const renderCatalog = page.render({
canvasContext: catalogCanvas.getContext('2d'),
viewport: catalogViewport,
})
await Promise.all([renderViewer, renderCatalog])
})
)
this.$emit('rendered')
await this.$nextTick()
this.viewerContentHeight = this.$refs.viewerContent.clientHeight
} catch (e) {
this.document = null
this.$emit('rendering-failed', e)
}
},
scrollToPage(page) {
this.syncViewerOffset(page)
this.syncCatalogOffset(page)
},
syncViewerOffset(page) {
if (this.isScrolling) return
this.$refs.viewerCanvas[page - 1].scrollIntoView()
},
syncCatalogOffset(page) {
const target = this.$refs.catalogItem[page - 1]
target.scrollIntoView({
block: 'nearest',
// FIXME: scroll smooth failed sometimes. wtf?
// behavior: 'smooth',
})
},
},
}
</script>
<style lang="scss" scoped></style>
+352
View File
@@ -0,0 +1,352 @@
<template>
<span class="header__preview">
<div class="start">
<IconButton
v-if="catalogVisible"
name="catalog"
@click="handleToggleCatalog"
/>
<div class="title">
{{ filename }}
</div>
</div>
<div class="center">
<div id="content" v-if="switchPageVisible">
<input
part="input"
type="text"
id="pageselector"
aria-label="页码"
:value="page"
@blur="handleUpdatePage"
@keyup.enter="handleUpdatePage"
/>
<span id="divider">/</span>
<span id="pagelength">{{ total }}</span>
</div>
<span class="vertical-separator"></span>
<span id="zoom-controls" v-if="zoomVisible">
<IconButton
name="minus"
:disabled="isLowest"
@click="handleModifyZoomLevel(false)"
/>
<input
type="text"
aria-label="缩放级别"
:value="formatZoom"
@blur="handleUpdateZoom($event.target.value)"
@keyup.enter="handleUpdateZoom($event.target.value)"
/>
<IconButton
name="plus"
:disabled="isHighest"
@click="handleModifyZoomLevel(true)"
/>
</span>
<span class="vertical-separator"></span>
<IconButton
v-if="fullpageVisible"
:name="mode"
@click="handleToggleFullpage"
/>
<IconButton v-if="rotateVisible" name="rotate" @click="handleRotate" />
</div>
<div class="end">
<IconButton
v-if="downloadVisible"
name="download"
@click="handleDownload"
/>
<IconButton v-if="printVisible" name="printer" @click="handlePrint" />
<!-- TODO: more action. (infos, fullscreen...) -->
<!-- <IconButton v-if="moreVisible" name="dots" @click="handleToggleMore" /> -->
</div>
</span>
</template>
<script>
import IconButton from './IconButton.vue'
import {
DOWNLOAD,
PRINT,
DOUBLE,
FULLSCREEN,
ABOUT,
FULLPAGE,
ROTATE,
ZOOM,
CATALOG,
SWITCH_PAGE,
} from '../constants/controls'
const NORMAL_ZOOM_LEVEL = [
25, 33, 50, 67, 75, 80, 90, 100, 110, 125, 150, 175, 200, 250, 300, 400, 500,
]
const string2Number = str => Number(str.replace(/[^\d]/g, '')) || 1
export default {
name: 'ViewerPageSelector',
components: {
IconButton,
},
props: {
total: Number,
page: Number,
zoom: Number,
rotate: Number,
controls: Array,
isFullpage: Boolean,
filename: String,
},
data() {
return {
tmpZoom: null,
}
},
computed: {
formatZoom() {
return parseInt(this.zoom)
},
mode() {
return !this.isFullpage ? 'fullpage' : 'auto-width'
},
lowestValue() {
return NORMAL_ZOOM_LEVEL[0]
},
highestValue() {
return NORMAL_ZOOM_LEVEL[NORMAL_ZOOM_LEVEL.length - 1]
},
isLowest() {
return this.zoom <= this.lowestValue
},
isHighest() {
return this.zoom >= this.highestValue
},
catalogVisible() {
return this.controls.includes(CATALOG)
},
downloadVisible() {
return this.controls.includes(DOWNLOAD)
},
printVisible() {
return this.controls.includes(PRINT)
},
doubleVisible() {
return this.controls.includes(DOUBLE)
},
fullscreenVisible() {
return this.controls.includes(FULLSCREEN)
},
aboutVisible() {
return this.controls.includes(ABOUT)
},
fullpageVisible() {
return this.controls.includes(FULLPAGE)
},
rotateVisible() {
return this.controls.includes(ROTATE)
},
zoomVisible() {
return this.controls.includes(ZOOM)
},
switchPageVisible() {
return this.controls.includes(SWITCH_PAGE)
},
moreVisible() {
return [
this.doubleVisible,
this.aboutVisible,
this.fullscreenVisible,
].some(i => i)
},
},
methods: {
handleToggleFullpage() {
// Make a copy for recovery
if (!this.isFullpage) {
this.tmpZoom = this.zoom
} else {
this.handleUpdateZoom(this.tmpZoom)
}
this.$emit('toggleFullpage')
},
handleRotate() {
this.$emit('update:rotate', this.rotate + 90)
},
handleDownload() {
this.$emit('download')
},
handlePrint() {
this.$emit('print')
},
handleToggleMore() {
// TODO: toggle more
},
handleFullscreen() {
// TODO: fullscreen
},
handleModifyZoomLevel(isLevelUp) {
const targetLevel = NORMAL_ZOOM_LEVEL.reduce((target, zoom, i) => {
if (target) {
return target
}
const next = NORMAL_ZOOM_LEVEL[i + 1]
const condition = isLevelUp
? zoom <= this.zoom && this.zoom < next
: zoom < this.zoom && this.zoom <= next
if (condition) {
if (isLevelUp) {
return next
} else {
return zoom
}
}
return null
}, null)
this.handleUpdateZoom(targetLevel)
},
handleToggleCatalog() {
this.$emit('toggleCatalog')
},
handleUpdatePage(evt) {
const value = string2Number(evt.target.value)
this.$emit('update:page', value > this.total ? this.total : value)
},
handleUpdateZoom(value) {
if (typeof value !== 'number') {
value = string2Number(value)
}
const finalValue =
value > this.highestValue
? this.highestValue
: value < this.lowestValue
? this.lowestValue
: value
this.$emit('update:zoom', finalValue)
},
},
}
</script>
<style lang="scss" scoped>
.header__preview {
--page-length-digits: 1;
display: flex;
width: 100%;
.start,
.end {
flex: 1;
}
.start {
align-items: center;
display: flex;
overflow: hidden;
padding-inline-end: 20px;
min-width: 36px;
.title {
font-size: 13px;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.catalog-icon {
> svg {
pointer-events: none;
display: block;
width: 20px;
height: 20px;
position: relative;
vertical-align: middle;
fill: var(--iron-icon-fill-color, currentcolor);
stroke: var(--iron-icon-stroke-color, none);
width: var(--iron-icon-width, 24px);
height: var(--iron-icon-height, 24px);
}
}
}
.center {
align-items: center;
display: flex;
#content {
align-items: center;
color: #fff;
direction: ltr;
display: flex;
font-size: 0.81rem;
text-align: center;
--page-selector-spacing: 4px;
#pageselector::selection {
background-color: var(--viewer-text-input-selection-color);
}
input,
#pagelength {
width: calc(max(2, var(--page-length-digits)) * 1ch + 1px);
}
input {
background: rgba(0, 0, 0, 0.5);
border: none;
color: white;
font-family: inherit;
line-height: inherit;
outline: none;
padding: 0 var(--page-selector-spacing);
text-align: center;
max-height: var(--viewer-pdf-toolbar-height);
font-size: 13px;
}
#divider {
margin: 0 var(--page-selector-spacing);
}
}
#zoom-controls {
align-items: center;
display: flex;
padding: 0 4px;
input {
background: rgba(0, 0, 0, 0.5);
border: none;
caret-color: currentColor;
color: inherit;
font-family: inherit;
line-height: inherit;
margin-left: 4px;
outline: none;
padding: 0 4px;
text-align: center;
width: 5ch;
font-size: 13px;
}
}
}
.end {
display: flex;
justify-content: flex-end;
padding-inline-start: 20px;
text-align: end;
white-space: nowrap;
}
}
@media screen and (max-width: 500) {
.header__preview {
.start {
display: none;
}
}
}
</style>
+23
View File
@@ -0,0 +1,23 @@
export const DOWNLOAD = 'download'
export const PRINT = 'print'
export const DOUBLE = 'double'
export const FULLSCREEN = 'fullscreen'
export const ABOUT = 'about'
export const FULLPAGE = 'fullpage'
export const ROTATE = 'rotate'
export const ZOOM = 'zoom'
export const CATALOG = 'catalog'
export const SWITCH_PAGE = 'switchPage'
export default [
DOWNLOAD,
PRINT,
DOUBLE,
FULLSCREEN,
ABOUT,
FULLPAGE,
ROTATE,
ZOOM,
CATALOG,
SWITCH_PAGE,
]
+7
View File
@@ -0,0 +1,7 @@
import PDFViewer from './PDFViewer.vue'
if (typeof window !== 'undefined' && window.Vue) {
window.PDFViewer = PDFViewer
}
export default PDFViewer
+15
View File
@@ -0,0 +1,15 @@
export default function throttle(fn, delay, ctx) {
let isAvail = true
return function () {
const args = arguments
if (isAvail) {
isAvail = false
fn.apply(ctx, args)
setTimeout(function () {
isAvail = true
}, delay)
}
}
}
+29
View File
@@ -0,0 +1,29 @@
import { VueConstructor } from 'vue';
export default VuePdfViewer;
export const VuePdfViewer: VuePdfViewerConstructor;
export interface VuePdfViewerProps {
source: object | string;
controls: string[];
}
export interface VuePdfViewerData {
isLoading: boolean,
page: number,
total: number,
catalogVisible: boolean,
zoom: number,
rotate: number,
}
export interface VuePdfViewerMethods {
handleSwitchPage: (page: number) => void;
handleUpdateZoom: (zoom: number) => void;
reload: () => void;
}
export interface VuePdfViewerConstructor extends VueConstructor {
props: VuePdfViewerProps;
data: () => VuePdfViewerData;
methods: VuePdfViewerMethods;
}
+94
View File
@@ -0,0 +1,94 @@
/* eslint-disable no-undef */
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const { VueLoaderPlugin: Vue2LoaderPlugin } = require('vue-loader')
const { VueLoaderPlugin: Vue3LoaderPlugin } = require('vue-loader-next')
const baseConfig = {
mode: 'production',
entry: './src/index.js',
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
{
test: /\.worker\.js$/,
loader: 'worker-loader',
options: {
inline: 'fallback',
},
},
],
},
optimization: {
minimizer: [
new UglifyJsPlugin({
parallel: true,
}),
],
},
externals: {
vue: 'vue',
},
performance: {
hints: false,
},
}
module.exports = [
{
...baseConfig,
output: {
filename: 'vue2-pdf-viewer.js',
library: {
name: 'vue-pdf-viewer',
type: 'umd',
},
},
plugins: [new Vue2LoaderPlugin()],
},
{
...baseConfig,
output: {
filename: 'vue3-pdf-viewer.js',
library: {
name: 'vue-pdf-viewer',
type: 'umd',
},
},
plugins: [new Vue3LoaderPlugin()],
},
]
+53
View File
@@ -0,0 +1,53 @@
/* eslint-disable no-undef */
const { VueLoaderPlugin } = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './demo/main.js',
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
'style-loader',
// 将 CSS 转化成 CommonJS 模块
'css-loader',
// 将 Sass 编译成 CSS
'sass-loader',
],
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
},
},
],
},
{
test: /\.worker\.js$/,
loader: 'worker-loader',
},
],
},
plugins: [
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: './demo/index.html',
}),
],
}
+6466
View File
File diff suppressed because it is too large. Load diff