vue+vue-outline实现类似于word的网页版文档

基于项目需求,需要在后台系统中加入操作手册文档,用来介绍项目详细操作,基于vue-outline(一个Vue页面生成导航的小工具)加上自己优化与改进,实现了网页版文档,可以根据目录定位到具体内容,包含文档下载与返回功能。

操作文档界面

代码实现

vue-outline安装

1
npm install vue-outline

或者

1
yarn add vue-outline

vue-outline引入

方法1: outline本身是一个指令插件, 你可以调用Vue.use把outline注册为全局指令(main.js):

本次使用方法1

1
2
import outline from 'vue-outline'
Vue.use(outline)

方法2:通过解构赋值将outline解构出来,通过Vue.directive注册为全局指令

1
2
import { outline } from 'vue-outline'
Vue.directive('your-directive-name', outline)

方法3:注册为组件局部指令

1
2
3
4
5
6
7
8
9
10
11
<script>
// ···
import { outline } from 'vue-outline'
export default {
// ···
directives: {
'your-directive-name': outline
}
// ···
}
</script>

使用

  • 整体页面代码结构

DocumentOperation.vue 封装成组件,可多系统复用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
<template>
<div class="main">
<Row class="breadcrumb-bar">
<Col span="22">
<Breadcrumb style="line-height:26px;">
<BreadcrumbItem to='/document-Operation'>操作文档</BreadcrumbItem>
</Breadcrumb>
</Col >
<Col span="2">
<Button class="buttonStyle" type="default" @click="getBack" >返回</Button>
<Button class="buttonStyle" type="default" @click="exportDocument">下载</Button>
</Col>
</Row>
<!-- 内容 -->
<section class="document-container">
<div v-outline="{
callback: refreshNavTree,
selectors: ['h1', 'h2','h3'],
exceptSelector: '[un-nav]'
}" class="content">
<!-- 一级标题 -->
<div v-for="item in dataTitle" :key="item.title">
<h1 >{{item.title || ''}}</h1>
<div class="content-mes" :style="{ margin: '.5rem 2rem' }" v-html="item.content || ''"></div>
<img :src="item.src" :style="{width:item.imgstyle|| ''}" alt="">
<!-- 二级标题 -->
<div v-for="subItem in item.children || []" :key="subItem.title">
<h2>{{subItem.title || ''}}</h2>
<div class="content-mes" :style="{ margin: '.5rem 2rem'}" v-html="subItem.content || ''"></div>
<img :src="subItem.src" :style="{width:subItem.imgstyle || ''}" alt="">
<!-- 三级标题 -->
<div v-for="grandItem in subItem.children || []" :key="grandItem.title">
<h3>{{grandItem.title || ''}}</h3>
<div class="content-mes" :style="{ margin: '.5rem 2rem' }" v-html="grandItem.content || ''"></div>
<img :src="grandItem.src" :style="{width:grandItem.imgstyle|| ''}" alt="">
<!-- 四级级标题 -->
<div v-for="fourItem in grandItem.children || []" :key="fourItem.title">
<h4 style="font-size:15px;">{{fourItem.title || ''}}</h4>
<div class="content-mes" :style="{ margin: '.5rem 2rem' }" v-html="fourItem.content || ''"></div>
<img :src="fourItem.src" :style="{width:fourItem.imgstyle|| ''}" alt="">
</div>
</div>
</div>
</div>
</div>
<!-- 导航 -->
<div class="navigation">
<div class="title">导航目录</div>
<outline-tree
:treeData="navTree"
:expand="false"
class="tree">
<div slot-scope="{ data, parentData }">
<div
class="node-render-content"
@click.stop="jumpToAnchor(data.id)">
{{ data.title }}
</div>
</div>
</outline-tree>
</div>
</section>
</div>
</template>

<script>
// 导入数据文件
import {documentCfg} from '@/constant/documentCfg'
export default {
name: 'HelloWorld',
props: {
msg: String
},
data () {
return {
navTree: []
}
},
created() {
this.dataTitle = documentCfg
},
methods: {
refreshNavTree (treeData) {
this.navTree = treeData
},
jumpToAnchor (id) {
let element = document.getElementById(id)
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
}
},
getBack() {
history.go(-1)
},
exportDocument() {
window.open(this.$CGI.downloadDocumentAPI + '?filename=document.pdf')
}
}
}
</script>

<style scoped>
.document-container{
min-height: 800px;
border: 1px solid #D9DEEB;
background-color: #fff;
height: 100%;
width: 100%;
overflow: hidden;
position: relative;
}
.content {
position: absolute;
top: 20px;;
bottom: 0;
left: 2rem;
right: -17px;
overflow-y: scroll;
margin-right:16rem;
}
.navigation {
background-color: #fff;
width: 15rem;
border: 1px solid #a2a2a2;
padding: .5rem;
border-radius: 2px;
position: absolute;
right: 2rem;
top: 0px;
bottom: 0;
overflow: auto;
max-height: 1000px;
}
h3 {
margin: 40px 0 0;

}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
.title {
font-size: 1rem;
border-bottom: 1px solid #c9c9c9;
margin: .5rem;
}
.node-render-content {
color: #3361D8;
user-select: none;
cursor: pointer;
margin: 2px 0;
}
.node-render-content:hover {
text-decoration: underline;
}
.node-render-content:active {
position: relative;
left: 1px;
top: 1px;
}
img{
display: block;
margin: .5rem 2rem;
}
.content-mes{
font-size: 15px;
}
h3[data-v-1e6de42c] {
font-size: 17px;
}
.buttonStyle{
width: 60px;
background-color: #fff;
color:#3f68ff;
text-align: center;
}
</style>
  • 页面数据

documentCfg.js 主要写导航和页面所有的数据

本地图片需要使用import方式引入,使用wenpack打包时才会启动,也可以使用CDN方式引入图片,title、content、src、imgstyle、children根据文档内容选填,当没有内容时可以不写,在DocumentOperation.vue中为空时有做处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import login from '@/assets/img/document/login.png'
export const documentCfg = [
{
title: '一级标题',
content: '内容(可带html标签)',
src: login,
imgstyle: '图片宽度',
children:[{
title: '二级标题',
content: '内容(可带html标签)',
src: login,
imgstyle: '图片宽度',
children:[{
title: '三级标题',
content: '内容(可带html标签)',
src: login,
imgstyle: '图片宽度',
children:[{
title: '四级标题',
content: '内容(可带html标签)',
src: login,
imgstyle: '图片宽度',
}]
}]
}]
}]

注意:使用本地图片打包时webpack配置

1
2
3
4
5
6
7
8
9
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
//以字节为单位,最小打包3500字节
limit: 3500,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}

文件下载

使用jsPDF+html2canvas 导出PDF文档,文档页面不美观,用户体验效果不佳,所以使用文件下载的方式下载写好的PDF文档,存在不足的是后期改变文档内容时需要改动两个地方,这块后期需要继续进一步优化

经过一次次的研究,这个网页版的操作文档就实现好了

参考