feat: 1.0 viewer
commit
c6805f2dac
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/essential",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": "warn",
|
||||
"arrow-parens": ["warn", "as-needed", { "requireForBlockBody": false }]
|
||||
}
|
||||
}
|
||||
|
|
@ -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?
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
|
|
@ -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.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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>
|
||||
|
|
@ -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.
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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"></span>
|
||||
<div class="name">高度</div>
|
||||
<div class="code-name">&#xe9c4;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">减少</div>
|
||||
<div class="code-name">&#xe62b;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">设计目录</div>
|
||||
<div class="code-name">&#xe614;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">下载</div>
|
||||
<div class="code-name">&#xe7bd;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">加减组件 加号</div>
|
||||
<div class="code-name">&#xe64d;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">旋转</div>
|
||||
<div class="code-name">&#xe609;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">dots</div>
|
||||
<div class="code-name">&#xe605;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">高度</div>
|
||||
<div class="code-name">&#xe600;</div>
|
||||
</li>
|
||||
|
||||
<li class="dib">
|
||||
<span class="icon iconfont"></span>
|
||||
<div class="name">24gf-printer</div>
|
||||
<div class="code-name">&#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"
|
||||
><span class="iconfont">&#x33;</span>
|
||||
</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"><link rel="stylesheet" href="./iconfont.css">
|
||||
</code></pre>
|
||||
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><span class="iconfont pdf-viewer--xxx"></span>
|
||||
</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"><script src="./iconfont.js"></script>
|
||||
</code></pre>
|
||||
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
|
||||
<pre><code class="language-html"><style>
|
||||
.icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</code></pre>
|
||||
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
|
||||
<pre><code class="language-html"><svg class="icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-xxx"></use>
|
||||
</svg>
|
||||
</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>
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -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.
|
|
@ -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>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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,
|
||||
]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import PDFViewer from './PDFViewer.vue'
|
||||
|
||||
if (typeof window !== 'undefined' && window.Vue) {
|
||||
window.PDFViewer = PDFViewer
|
||||
}
|
||||
|
||||
export default PDFViewer
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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()],
|
||||
},
|
||||
]
|
||||
|
|
@ -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',
|
||||
}),
|
||||
],
|
||||
}
|
||||
Loading…
Reference in New Issue