Browse Source

✨ feat(全局 markdown 预览组件): 添加 markdown 预览功能

优化图片预览逻辑;添加全局 markdown 预览组件;修改全局基础样式中的无序、有序列表
liyating 3 years ago
parent
commit
26789775f3

+ 2 - 1
jsconfig.json

@@ -6,7 +6,8 @@
 			"_v/*": ["src/views/*"],
 			"_c/*": ["src/components/*"],
 			"_a/*": ["src/assets/*"],
-			"_r": ["src/request/*"]
+			"_r": ["src/request/*"],
+			"_public": ["public/*"]
 		}
 	},
 	"exclude": ["node_modules", "dist"]

File diff suppressed because it is too large
+ 18784 - 1
package-lock.json


+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "core-js": "^3.6.5",
     "element-ui": "^2.15.5",
     "js-cookie": "^2.2.1",
+    "mavon-editor": "^2.10.1",
     "qs": "^6.8.0",
     "spark-md5": "^3.0.1",
     "vue": "^2.6.10",

File diff suppressed because it is too large
+ 2 - 0
public/mavonEditor/css/github-markdown.css


File diff suppressed because it is too large
+ 0 - 0
public/mavonEditor/css/katex.min.css


+ 75 - 0
public/mavonEditor/css/tomorrow-night.css

@@ -0,0 +1,75 @@
+/* Tomorrow Night Theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+/* Original theme - https://github.com/chriskempson/tomorrow-theme */
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+
+/* Tomorrow Comment */
+.hljs-comment,
+.hljs-quote {
+	color: #969896;
+}
+
+/* Tomorrow Red */
+.hljs-variable,
+.hljs-template-variable,
+.hljs-tag,
+.hljs-name,
+.hljs-selector-id,
+.hljs-selector-class,
+.hljs-regexp,
+.hljs-deletion {
+	color: #cc6666;
+}
+
+/* Tomorrow Orange */
+.hljs-number,
+.hljs-built_in,
+.hljs-builtin-name,
+.hljs-literal,
+.hljs-type,
+.hljs-params,
+.hljs-meta,
+.hljs-link {
+	color: #de935f;
+}
+
+/* Tomorrow Yellow */
+.hljs-attribute {
+	color: #f0c674;
+}
+
+/* Tomorrow Green */
+.hljs-string,
+.hljs-symbol,
+.hljs-bullet,
+.hljs-addition {
+	color: #b5bd68;
+}
+
+/* Tomorrow Blue */
+.hljs-title,
+.hljs-section {
+	color: #81a2be;
+}
+
+/* Tomorrow Purple */
+.hljs-keyword,
+.hljs-selector-tag {
+	color: #b294bb;
+}
+
+.hljs {
+	display: block;
+	overflow-x: auto;
+	background: #1d1f21;
+	color: #c5c8c6;
+	padding: 16px;
+}
+
+.hljs-emphasis {
+	font-style: italic;
+}
+
+.hljs-strong {
+	font-weight: bold;
+}

File diff suppressed because it is too large
+ 304 - 0
public/mavonEditor/js/highlight.min.js


File diff suppressed because it is too large
+ 0 - 0
public/mavonEditor/js/katex.min.js


+ 322 - 0
public/mavonEditor/js/lang.hljs.js

@@ -0,0 +1,322 @@
+export default {
+	'1c': '1c',
+	abnf: 'abnf',
+	accesslog: 'accesslog',
+	actionscript: 'actionscript',
+	as: 'actionscript',
+	ada: 'ada',
+	apache: 'apache',
+	apacheconf: 'apache',
+	applescript: 'applescript',
+	osascript: 'applescript',
+	arduino: 'arduino',
+	armasm: 'armasm',
+	arm: 'armasm',
+	asciidoc: 'asciidoc',
+	adoc: 'asciidoc',
+	aspectj: 'aspectj',
+	autohotkey: 'autohotkey',
+	ahk: 'autohotkey',
+	autoit: 'autoit',
+	avrasm: 'avrasm',
+	awk: 'awk',
+	axapta: 'axapta',
+	bash: 'bash',
+	sh: 'bash',
+	zsh: 'bash',
+	basic: 'basic',
+	bnf: 'bnf',
+	brainfuck: 'brainfuck',
+	bf: 'brainfuck',
+	cal: 'cal',
+	capnproto: 'capnproto',
+	capnp: 'capnproto',
+	ceylon: 'ceylon',
+	clean: 'clean',
+	icl: 'clean',
+	dcl: 'clean',
+	'clojure-repl': 'clojure-repl',
+	clojure: 'clojure',
+	clj: 'clojure',
+	cmake: 'cmake',
+	'cmake.in': 'cmake',
+	coffeescript: 'coffeescript',
+	coffee: 'coffeescript',
+	cson: 'coffeescript',
+	iced: 'coffeescript',
+	coq: 'coq',
+	cos: 'cos',
+	cls: 'cos',
+	cpp: 'cpp',
+	c: 'cpp',
+	cc: 'cpp',
+	h: 'cpp',
+	'c++': 'cpp',
+	'h++': 'cpp',
+	hpp: 'cpp',
+	crmsh: 'crmsh',
+	crm: 'crmsh',
+	pcmk: 'crmsh',
+	crystal: 'crystal',
+	cr: 'crystal',
+	cs: 'cs',
+	csharp: 'cs',
+	csp: 'csp',
+	css: 'css',
+	d: 'd',
+	dart: 'dart',
+	delphi: 'delphi',
+	dpr: 'delphi',
+	dfm: 'delphi',
+	pas: 'delphi',
+	pascal: 'delphi',
+	freepascal: 'delphi',
+	lazarus: 'delphi',
+	lpr: 'delphi',
+	lfm: 'delphi',
+	diff: 'diff',
+	patch: 'diff',
+	django: 'django',
+	jinja: 'django',
+	dns: 'dns',
+	bind: 'dns',
+	zone: 'dns',
+	dockerfile: 'dockerfile',
+	docker: 'dockerfile',
+	dos: 'dos',
+	bat: 'dos',
+	cmd: 'dos',
+	dsconfig: 'dsconfig',
+	dts: 'dts',
+	dust: 'dust',
+	dst: 'dust',
+	ebnf: 'ebnf',
+	elixir: 'elixir',
+	elm: 'elm',
+	erb: 'erb',
+	'erlang-repl': 'erlang-repl',
+	erlang: 'erlang',
+	erl: 'erlang',
+	excel: 'excel',
+	xlsx: 'excel',
+	xls: 'excel',
+	fix: 'fix',
+	flix: 'flix',
+	fortran: 'fortran',
+	f90: 'fortran',
+	f95: 'fortran',
+	fsharp: 'fsharp',
+	fs: 'fsharp',
+	gams: 'gams',
+	gms: 'gams',
+	gauss: 'gauss',
+	gss: 'gauss',
+	gcode: 'gcode',
+	nc: 'gcode',
+	gherkin: 'gherkin',
+	feature: 'gherkin',
+	glsl: 'glsl',
+	go: 'go',
+	golang: 'go',
+	golo: 'golo',
+	gradle: 'gradle',
+	groovy: 'groovy',
+	haml: 'haml',
+	handlebars: 'handlebars',
+	hbs: 'handlebars',
+	'html.hbs': 'handlebars',
+	'html.handlebars': 'handlebars',
+	haskell: 'haskell',
+	hs: 'haskell',
+	haxe: 'haxe',
+	hx: 'haxe',
+	hsp: 'hsp',
+	htmlbars: 'htmlbars',
+	http: 'http',
+	https: 'http',
+	hy: 'hy',
+	hylang: 'hy',
+	inform7: 'inform7',
+	i7: 'inform7',
+	ini: 'ini',
+	toml: 'ini',
+	irpf90: 'irpf90',
+	java: 'java',
+	jsp: 'java',
+	javascript: 'javascript',
+	js: 'javascript',
+	jsx: 'javascript',
+	'jboss-cli': 'jboss-cli',
+	'wildfly-cli': 'jboss-cli',
+	json: 'json',
+	'julia-repl': 'julia-repl',
+	julia: 'julia',
+	kotlin: 'kotlin',
+	lasso: 'lasso',
+	ls: 'livescript',
+	lassoscript: 'lasso',
+	ldif: 'ldif',
+	leaf: 'leaf',
+	less: 'less',
+	lisp: 'lisp',
+	livecodeserver: 'livecodeserver',
+	livescript: 'livescript',
+	llvm: 'llvm',
+	lsl: 'lsl',
+	lua: 'lua',
+	makefile: 'makefile',
+	mk: 'makefile',
+	mak: 'makefile',
+	markdown: 'markdown',
+	md: 'markdown',
+	mkdown: 'markdown',
+	mkd: 'markdown',
+	mathematica: 'mathematica',
+	mma: 'mathematica',
+	matlab: 'matlab',
+	maxima: 'maxima',
+	mel: 'mel',
+	mercury: 'mercury',
+	m: 'mercury',
+	moo: 'mercury',
+	mipsasm: 'mipsasm',
+	mips: 'mipsasm',
+	mizar: 'mizar',
+	mojolicious: 'mojolicious',
+	monkey: 'monkey',
+	moonscript: 'moonscript',
+	moon: 'moonscript',
+	n1ql: 'n1ql',
+	nginx: 'nginx',
+	nginxconf: 'nginx',
+	nimrod: 'nimrod',
+	nim: 'nimrod',
+	nix: 'nix',
+	nixos: 'nix',
+	nsis: 'nsis',
+	objectivec: 'objectivec',
+	mm: 'objectivec',
+	objc: 'objectivec',
+	'obj-c': 'objectivec',
+	ocaml: 'ocaml',
+	ml: 'sml',
+	openscad: 'openscad',
+	scad: 'openscad',
+	oxygene: 'oxygene',
+	parser3: 'parser3',
+	perl: 'perl',
+	pl: 'perl',
+	pm: 'perl',
+	pf: 'pf',
+	'pf.conf': 'pf',
+	php: 'php',
+	php3: 'php',
+	php4: 'php',
+	php5: 'php',
+	php6: 'php',
+	pony: 'pony',
+	powershell: 'powershell',
+	ps: 'powershell',
+	processing: 'processing',
+	profile: 'profile',
+	prolog: 'prolog',
+	protobuf: 'protobuf',
+	puppet: 'puppet',
+	pp: 'puppet',
+	purebasic: 'purebasic',
+	pb: 'purebasic',
+	pbi: 'purebasic',
+	python: 'python',
+	py: 'python',
+	gyp: 'python',
+	q: 'q',
+	k: 'q',
+	kdb: 'q',
+	qml: 'qml',
+	qt: 'qml',
+	r: 'r',
+	rib: 'rib',
+	roboconf: 'roboconf',
+	graph: 'roboconf',
+	instances: 'roboconf',
+	routeros: 'routeros',
+	mikrotik: 'routeros',
+	rsl: 'rsl',
+	ruby: 'ruby',
+	rb: 'ruby',
+	gemspec: 'ruby',
+	podspec: 'ruby',
+	thor: 'ruby',
+	irb: 'ruby',
+	ruleslanguage: 'ruleslanguage',
+	rust: 'rust',
+	rs: 'rust',
+	scala: 'scala',
+	scheme: 'scheme',
+	scilab: 'scilab',
+	sci: 'scilab',
+	scss: 'scss',
+	shell: 'shell',
+	console: 'shell',
+	smali: 'smali',
+	smalltalk: 'smalltalk',
+	st: 'smalltalk',
+	sml: 'sml',
+	sqf: 'sqf',
+	sql: 'sql',
+	stan: 'stan',
+	stata: 'stata',
+	do: 'stata',
+	ado: 'stata',
+	step21: 'step21',
+	p21: 'step21',
+	step: 'step21',
+	stp: 'step21',
+	stylus: 'stylus',
+	styl: 'stylus',
+	subunit: 'subunit',
+	swift: 'swift',
+	taggerscript: 'taggerscript',
+	tap: 'tap',
+	tcl: 'tcl',
+	tk: 'tcl',
+	tex: 'tex',
+	thrift: 'thrift',
+	tp: 'tp',
+	twig: 'twig',
+	craftcms: 'twig',
+	typescript: 'typescript',
+	ts: 'typescript',
+	vala: 'vala',
+	vbnet: 'vbnet',
+	vb: 'vbnet',
+	'vbscript-html': 'vbscript-html',
+	vbscript: 'vbscript',
+	vbs: 'vbscript',
+	verilog: 'verilog',
+	v: 'verilog',
+	sv: 'verilog',
+	svh: 'verilog',
+	vhdl: 'vhdl',
+	vim: 'vim',
+	x86asm: 'x86asm',
+	xl: 'xl',
+	tao: 'xl',
+	xml: 'xml',
+	html: 'xml',
+	xhtml: 'xml',
+	rss: 'xml',
+	atom: 'xml',
+	xjb: 'xml',
+	xsd: 'xml',
+	xsl: 'xml',
+	plist: 'xml',
+	xquery: 'xquery',
+	xpath: 'xquery',
+	xq: 'xquery',
+	yaml: 'yaml',
+	yml: 'yaml',
+	YAML: 'yaml',
+	zephir: 'zephir',
+	zep: 'zephir'
+}

BIN
src/assets/images/file/file_markdown.png


+ 0 - 5
src/assets/styles/css/base.css

@@ -17,11 +17,6 @@ body {
 a {
 	text-decoration: none;
 }
-ul,
-ol,
-li {
-	list-style: none;
-}
 img {
 	vertical-align: middle;
 }

+ 11 - 0
src/assets/styles/mediaScreenXs.styl

@@ -387,6 +387,17 @@
       right: calc(50vw - 150px) !important;
     }
   }
+  // markdown 预览组件
+  .markdown-preview-wrapper {
+    .tip-wrapper {
+      padding: 0 16px !important;
+    }
+    .tool-wrapper {
+      .text-wrapper {
+        margin-left: 8px !important;
+      }
+    }
+  }
   // 日期组件
   .el-date-picker.has-sidebar.has-time {
     width: 350px !important;

+ 1 - 0
src/components/Footer.vue

@@ -125,6 +125,7 @@ export default {
     .join-list {
       margin-top: 16px;
       display: flex;
+      list-style: none;
 
       .join-item {
         .iconfont {

+ 1 - 1
src/components/file/AsideMenu.vue

@@ -366,7 +366,7 @@ export default {
     position: absolute;
     top: calc(50% - 50px);
     right: 0;
-    z-index: 3;
+    z-index: 2;
     background: $BorderBase;
     color: #fff;
     width: 12px;

+ 2 - 0
src/components/file/box/contextMenu/Box.vue

@@ -423,6 +423,7 @@ export default {
   z-index: 2;
   padding: 4px 0;
   color: $RegularText;
+  list-style: none;
 
   .right-menu-item,
   .unzip-item {
@@ -449,6 +450,7 @@ export default {
     .unzip-list {
       position: absolute;
       display: none;
+      list-style: none;
       .unzip-item {
         width: 200px;
         setEllipsis(1)

+ 29 - 21
src/components/file/box/imgPreview/BoxMask.vue

@@ -3,13 +3,13 @@
 		<div
 			class="img-preview-wrapper"
 			v-show="visible"
-			@click.self="closeImgReview"
+			@click.self="closeImgPreview"
 			@mousewheel.prevent="rollImg()"
 		>
 			<!-- 顶部信息栏 & 工具栏 -->
 			<div class="tip-wrapper" v-if="visible">
-				<div class="name" :title="activeFileName + activeExtendName">
-					{{ activeFileName }}.{{ activeExtendName }}
+				<div class="name" :title="activeImageName">
+					{{ activeImageName }}
 				</div>
 				<div class="opera-btn-group">
 					<el-input-number
@@ -30,7 +30,7 @@
 						class="item download-link"
 						target="_blank"
 						:href="activeDownloadLink"
-						:download="activeFileName + '.' + activeExtendName"
+						:download="activeImageName"
 					>
 						<i class="el-icon-download" title="保存到本地"></i>
 					</a>
@@ -43,7 +43,7 @@
 						</div>
 						<div class="item text-wrapper">
 							<span class="text">操作提示</span>
-							<i class="el-icon-question"></i>
+							<i class="el-icon-s-opportunity"></i>
 						</div>
 					</el-tooltip>
 				</div>
@@ -86,6 +86,8 @@
 </template>
 
 <script>
+import store from '@/store/index.js'
+
 export default {
 	name: 'ImgPreview',
 	data() {
@@ -99,19 +101,17 @@ export default {
 		}
 	},
 	computed: {
-		// 当前显示的图片名称
-		activeFileName() {
-			return this.imgList[this.activeIndex].fileName
+		activeImage() {
+			return this.imgList[this.activeIndex]
 		},
-		// 当前显示的图片扩展名
-		activeExtendName() {
-			return this.imgList[this.activeIndex].extendName
+		activeImageName() {
+			return this.getFileNameComplete(this.activeImage)
 		},
 		imageHeight() {
-			return this.imgList[this.activeIndex].imageHeight
+			return this.activeImage.imageHeight
 		},
 		imageWidth() {
-			return this.imgList[this.activeIndex].imageWidth
+			return this.activeImage.imageWidth
 		},
 		// 对用户而言 显示的图片索引 从 1 开始 顶部栏输入框控制此值变化
 		inputActiveIndex: {
@@ -124,7 +124,15 @@ export default {
 		},
 		// 当前显示的图片下载链接
 		activeDownloadLink() {
-			return this.imgList[this.activeIndex].downloadLink
+			return this.activeImage.downloadLink
+		},
+		// 屏幕宽度
+		screenWidth() {
+			return store.state.common.screenWidth
+		},
+		// 在原比例上再缩小的比例
+		reduceNumber() {
+			return this.screenWidth > 768 ? 10 : 4
 		}
 	},
 	watch: {
@@ -138,7 +146,7 @@ export default {
 				this.$nextTick(() => {
 					document.addEventListener('keyup', (e) => {
 						if (e.keyCode === 27) {
-							this.closeImgReview()
+							this.closeImgPreview()
 						}
 					})
 				})
@@ -150,7 +158,7 @@ export default {
 								bodyDom.clientHeight / this.imageHeight
 							) *
 								100 -
-							10
+							this.reduceNumber
 						).toFixed(0)
 					)
 					this.$refs.imgLarge[this.activeIndex].style.zoom = `${this.imgZoom}%`
@@ -159,7 +167,7 @@ export default {
 				bodyDom.style.overflow = 'auto'
 				document.removeEventListener('keyup', (e) => {
 					if (e.keyCode === 27) {
-						this.closeImgReview()
+						this.closeImgPreview()
 					}
 				})
 			}
@@ -181,7 +189,7 @@ export default {
 								bodyDom.clientHeight / this.imageHeight
 							) *
 								100 -
-							10
+							this.reduceNumber
 						).toFixed(0)
 					)
 					this.$refs.imgLarge[newValue].style.zoom = `${this.imgZoom}%`
@@ -193,7 +201,7 @@ export default {
 		/**
 		 * 关闭图片预览,恢复旋转角度
 		 */
-		closeImgReview() {
+		closeImgPreview() {
 			this.rotate = 0
 			this.$refs.imgLarge[
 				this.activeIndex
@@ -256,7 +264,7 @@ export default {
   overflow: auto;
   width: 100%;
   height: 100%;
-  z-index: 2010;
+  z-index: 2;
   text-align: center;
   display: flex;
   align-items: center;
@@ -291,7 +299,7 @@ export default {
     position: fixed;
     top: 0;
     left: 0;
-    z-index: 2011;
+    z-index: 2;
     background: rgba(0, 0, 0, 0.5);
     padding: 0 48px;
     width: 100%;

+ 333 - 0
src/components/file/box/markdownPreview/BoxMask.vue

@@ -0,0 +1,333 @@
+<template>
+	<transition name="el-fade-in-linear el-fade-in">
+		<div
+			class="markdown-preview-wrapper"
+			v-show="visible"
+			@click.self="closeMarkdownPreview"
+		>
+			<!-- 顶部信息栏 & 工具栏 -->
+			<div class="tip-wrapper" v-if="visible">
+				<div class="name" :title="getFileNameComplete(fileInfo)">
+					{{ getFileNameComplete(fileInfo) }}
+				</div>
+				<div class="tool-wrapper">
+					<a
+						class="item download-link"
+						target="_blank"
+						:href="getDownloadFilePath(fileInfo)"
+						:download="getFileNameComplete(fileInfo)"
+					>
+						<i class="el-icon-download" title="下载"></i>
+					</a>
+					<el-tooltip effect="dark" placement="bottom">
+						<div slot="content">
+							操作提示:<br />
+							1. 点击文档以外的区域可退出查看;<br />
+							2. 按 Esc 键可退出查看;<br />
+							3. markdown 在线编辑功能即将上线,敬请期待
+						</div>
+						<div class="item text-wrapper">
+							<span class="text">操作提示</span>
+							<i class="el-icon-s-opportunity"></i>
+						</div>
+					</el-tooltip>
+					<i
+						class="item el-icon-close"
+						title="关闭预览"
+						@click="closeMarkdownPreview"
+					></i>
+				</div>
+			</div>
+			<!-- mavon-editor 组件,配置项说明文档 https://www.npmjs.com/package/mavon-editor -->
+			<!-- :editable="false" 这里暂时不允许编辑,等待后台提供更新 markdown 文档内容的接口 -->
+			<mavonEditor
+				ref="mavonEditor"
+				v-model="markdownText"
+				:toolbars="toolbars"
+				:externalLink="externalLink"
+				:editable="false"
+				:subfield="screenWidth > 768 ? true : false"
+				defaultOpen="preview"
+				v-loading="markdownLoading"
+			></mavonEditor>
+		</div>
+	</transition>
+</template>
+
+<script>
+import { mavonEditor } from 'mavon-editor'
+import 'mavon-editor/dist/css/index.css'
+// 代码高亮样式表
+import '_public/mavonEditor/css/tomorrow-night.css'
+import '_public/mavonEditor/css/github-markdown.css'
+import store from '@/store/index.js'
+import { getFilePreview } from '_r/file.js'
+
+export default {
+	name: 'ImgPreview',
+	components: {
+		mavonEditor
+	},
+	data() {
+		return {
+			visible: false, //  markdown 预览遮罩层组件是否显示
+			markdownText: '', //  markdown 原文
+			markdownLoading: false //  markdown 内容是否加载中
+		}
+	},
+	computed: {
+		// 屏幕宽度
+		screenWidth() {
+			return store.state.common.screenWidth
+		},
+		// 工具栏
+		toolbars() {
+			let res = {
+				// bold: true, // 粗体
+				// italic: true, // 斜体
+				// header: true, // 标题
+				// underline: true, // 下划线
+				// strikethrough: true, // 中划线
+				// mark: true, // 标记
+				// superscript: true, // 上角标
+				// subscript: true, // 下角标
+				// quote: true, // 引用
+				// ol: true, // 有序列表
+				// ul: true, // 无序列表
+				// link: true, // 链接
+				// imagelink: true, // 图片链接
+				// code: true, // code
+				// table: true, // 表格
+				fullscreen: true, // 全屏编辑
+				readmodel: true, // 沉浸式阅读
+				htmlcode: true, // 展示html源码
+				help: true, // 帮助
+				// /* 1.3.5 */
+				// undo: true, // 上一步
+				// redo: true, // 下一步
+				// trash: true, // 清空
+				/* 1.4.2 */
+				navigation: true, // 导航目录
+				// /* 2.1.8 */
+				// alignleft: true, // 左对齐
+				// aligncenter: true, // 居中
+				// alignright: true, // 右对齐
+				/* 2.2.1 */
+				subfield: true, // 单双栏模式
+				preview: true // 预览
+			}
+			return res
+			// if (this.screenWidth > 768) {
+			// 	return res
+			// } else {
+			// 	delete res.navigation
+			// 	return res
+			// }
+		},
+		// 外链 cdn 改为本地引入
+		externalLink() {
+			let context =
+				process.env.NODE_ENV === 'production'
+					? '/' //  生产环境
+					: '/' //  开发环境
+			return {
+				markdown_css: function () {
+					// 这是你的markdown css文件路径
+					return `${context}mavonEditor/css/github-markdown.css`
+				},
+				hljs_js: function () {
+					// 这是你的hljs文件路径
+					return `${context}mavonEditor/js/highlight.min.js`
+				},
+				hljs_css: function () {
+					// 这是你的代码高亮配色文件路径
+					return `${context}mavonEditor/css/tomorrow-night.css`
+				},
+				hljs_lang: function () {
+					// 这是你的代码高亮语言解析路径
+					return `${context}mavonEditor/js/lang.hljs.js`
+				},
+				katex_css: function () {
+					// 这是你的katex配色方案路径路径
+					return `${context}mavonEditor/css/katex.min.css`
+				},
+				katex_js: function () {
+					// 这是你的katex.js路径
+					return `${context}mavonEditor/js/katex.min.js`
+				}
+			}
+		}
+	},
+	watch: {
+		// 监听 markdown 查看组件 显隐状态变化
+		visible(val) {
+			if (val) {
+				this.getMarkdownText()
+				// 添加键盘 Esc 事件
+				this.$nextTick(() => {
+					document.addEventListener('keyup', (e) => {
+						if (e.keyCode === 27) {
+							this.closeMarkdownPreview()
+						}
+					})
+				})
+			} else {
+				document.removeEventListener('keyup', (e) => {
+					if (e.keyCode === 27) {
+						this.closeMarkdownPreview()
+					}
+				})
+			}
+		}
+	},
+	methods: {
+		/**
+		 * 获取 markdown 原文
+		 */
+		getMarkdownText() {
+			this.markdownLoading = true
+			getFilePreview({
+				userFileId: this.fileInfo.userFileId,
+				isMin: false,
+				shareBatchNum: this.fileInfo.shareBatchNum,
+				extractionCode: this.fileInfo.extractionCode,
+				token: this.getCookies(this.$config.tokenKeyName)
+			}).then((res) => {
+				this.markdownLoading = false
+				this.markdownText = res
+			})
+		},
+		/**
+		 * 关闭 markdown 预览,恢复旋转角度
+		 */
+		closeMarkdownPreview() {
+			this.visible = false
+			this.callback('cancel')
+		}
+	}
+}
+</script>
+
+<style lang="stylus" scoped>
+@import '~_a/styles/varibles.styl';
+
+.markdown-preview-wrapper {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  overflow: auto;
+  width: 100%;
+  height: 100vh;
+  z-index: 2;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  animation: imgPreviewAnimation 0.3s;
+  -webkit-animation: imgPreviewAnimation 0.3s; /* Safari and Chrome */
+  animation-iteration-count: 0.3;
+  -webkit-animation-iteration-count: 0.3;
+  animation-fill-mode: forwards;
+  -webkit-animation-fill-mode: forwards; /* Safari 和 Chrome */
+  @keyframes imgPreviewAnimation {
+    0% {
+      background: transparent;
+    }
+    100% {
+      background: rgba(0, 0, 0, 0.8);
+    }
+  }
+  @keyframes imgPreviewAnimation {
+    0% {
+      background: transparent;
+    }
+    100% {
+      background: rgba(0, 0, 0, 0.8);
+    }
+  }
+  .tip-wrapper {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 2;
+    background: rgba(0, 0, 0, 0.5);
+    padding: 0 48px;
+    width: 100%;
+    height: 48px;
+    line-height: 48px;
+    color: #fff;
+    font-size: 16px;
+    display: flex;
+    justify-content: space-between;
+    .name {
+      flex: 1;
+      padding-right: 16px;
+      text-align: left;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+    .tool-wrapper {
+      flex: 1;
+      display: flex;
+      justify-content: flex-end;
+      .item {
+        margin-left: 16px;
+        height: 48px;
+        line-height: 48px;
+        cursor: pointer;
+        &:hover {
+          opacity: 0.7;
+        }
+      }
+      .download-link {
+        color: inherit;
+        font-size: 18px;
+      }
+      .text-wrapper {
+        margin-left: 32px;
+        .text {
+          margin-right: 8px;
+        }
+      }
+    }
+  }
+
+  >>> .v-note-wrapper {
+		box-shadow: none !important;
+		border: 1px solid $BorderBase;
+		.v-note-op {
+			border-bottom-color: $BorderBase;
+      .v-left-item {
+        display: none;
+      }
+      .v-right-item {
+        max-width: 100%;
+        width: 100%;
+      }
+		}
+		.v-note-navigation-wrapper {
+			.v-note-navigation-content {
+				h1,
+				h2,
+				h3,
+				h4,
+				h5,
+				h6 {
+					color: $PrimaryText;
+					&:hover {
+						color: $PrimaryText;
+					}
+				}
+			}
+		}
+	}
+	>>> .v-note-wrapper:not(.fullscreen) {
+    margin: 56px auto 0 auto;
+    min-width: 50vw;
+    max-width: 90vw;
+		height: calc(100vh - 80px);
+	}
+}
+</style>

+ 49 - 0
src/components/file/box/markdownPreview/index.js

@@ -0,0 +1,49 @@
+import Vue from 'vue'
+// 导入组件
+import MarkdownPreview from './BoxMask.vue'
+// 使用基础 Vue 构造器,创建一个“子类”
+const MarkdownPreviewConstructor = Vue.extend(MarkdownPreview)
+
+let markdownPreviewInstance = null
+/**
+ * 初始化 markdown 预览实例
+ * @param {string} fileInfo 文件信息
+ */
+const initInstanceMarkdownPreview = (fileInfo) => {
+	markdownPreviewInstance = new MarkdownPreviewConstructor({
+		el: document.createElement('div'),
+		data() {
+			return {
+				fileInfo
+			}
+		}
+	})
+}
+/**
+ *  markdown 预览 Promise 函数
+ * @returns {Promise} 抛出确认和取消回调函数
+ */
+const showMarkdownPreviewBox = (obj) => {
+	// 非首次调用服务时,在 DOM 中移除上个实例
+	if (markdownPreviewInstance !== null) {
+		document.body.removeChild(markdownPreviewInstance.$el)
+	}
+	let { fileInfo } = obj
+	return new Promise((reslove) => {
+		initInstanceMarkdownPreview(fileInfo)
+		markdownPreviewInstance.callback = (res) => {
+			reslove(res)
+			// 服务取消时卸载 DOM
+			if (res === 'cancel' && markdownPreviewInstance !== null) {
+				document.body.removeChild(markdownPreviewInstance.$el)
+				markdownPreviewInstance = null
+			}
+		}
+		document.body.appendChild(markdownPreviewInstance.$el) //  挂载 DOM
+		Vue.nextTick(() => {
+			markdownPreviewInstance.visible = true //  打开 markdown 预览遮罩层
+		})
+	})
+}
+
+export default showMarkdownPreviewBox

+ 1 - 0
src/components/file/box/uploadFile/Box.vue

@@ -533,6 +533,7 @@ export default {
       overflow-y: auto;
       background-color: #fff;
       font-size: 12px;
+      list-style: none;
       setScrollbar(6px, #EBEEF5, #C0C4CC);
 
       .file-item {

+ 1 - 0
src/components/file/box/videoPreview/BoxMask.vue

@@ -241,6 +241,7 @@ export default {
         cursor: pointer;
         height: calc(100% - 42px);
         overflow: auto;
+        list-style: none;
         setScrollbar(8px);
 
         .video-item {

+ 1 - 0
src/components/file/components/FileGrid.vue

@@ -176,6 +176,7 @@ export default {
     flex-wrap: wrap;
     align-items: flex-start;
     align-content: flex-start;
+    list-style: none;
     setScrollbar(6px, transparent, #C0C4CC);
 
     .file-item {

+ 1 - 0
src/components/file/components/FileTimeLine.vue

@@ -106,6 +106,7 @@ export default {
       .image-list {
         display: flex;
         flex-wrap: wrap;
+        list-style: none;
 
         .image-item {
           margin: 0 16px 16px 0;

+ 1 - 4
src/components/file/dialog/fileDetailInfo/Dialog.vue

@@ -23,10 +23,7 @@
 			size="small"
 		>
 			<el-form-item label="文件名" prop="fileName">
-				<el-input
-					:value="getFileNameComplete(fileInfo, true)"
-					readonly
-				></el-input>
+				<el-input :value="getFileNameComplete(fileInfo)" readonly></el-input>
 			</el-form-item>
 			<el-form-item
 				:label="fileType === 6 ? '原路径' : '路径'"

+ 1 - 0
src/components/home/Banner.vue

@@ -121,6 +121,7 @@ export default {
 
         .list {
           padding: 25px 0 25px 20px;
+          list-style: none;
 
           .item {
             line-height: 2.2;

+ 1 - 0
src/components/home/Function.vue

@@ -110,6 +110,7 @@ export default {
     display flex
     flex-wrap wrap
     justify-content space-between
+    list-style: none;
     .function-item {
       margin-bottom 24px
       width: 32%;

+ 19 - 16
src/libs/globalFunction.js

@@ -245,28 +245,29 @@ const globalFunction = {
 			Number(router.currentRoute.query.fileType) === 1
 				? imgInfoList.map((item) => {
 						return {
+							...item,
 							fileUrl: this.getViewFilePath(item),
-							downloadLink: this.getDownloadFilePath(item),
-							fileName: item.fileName,
-							extendName: item.extendName,
-							imageWidth: item.imageWidth,
-							imageHeight: item.imageHeight
+							downloadLink: this.getDownloadFilePath(item)
 						}
 				  })
 				: [
 						{
+							...imgInfo,
 							fileUrl: this.getViewFilePath(imgInfo),
-							downloadLink: this.getDownloadFilePath(imgInfo),
-							fileName: imgInfo.fileName,
-							extendName: imgInfo.extendName,
-							imageWidth: imgInfo.imageWidth,
-							imageHeight: imgInfo.imageHeight
+							downloadLink: this.getDownloadFilePath(imgInfo)
 						}
 				  ]
 		const defaultIndex =
 			Number(router.currentRoute.query.fileType) === 1 ? currentIndex : 0
 		Vue.prototype.$previewImg({ imgList, defaultIndex })
 	},
+	/**
+	 * markdown 文档预览
+	 * @param {object} fileInfo 文件信息
+	 */
+	handleMarkdownPreview(fileInfo) {
+		Vue.prototype.$previewMarkdown({ fileInfo })
+	},
 	/**
 	 * 视频预览
 	 * @param {*} currentIndex 当前视频索引
@@ -281,18 +282,14 @@ const globalFunction = {
 						return {
 							...item,
 							fileUrl: this.getViewFilePath(item),
-							downloadLink: this.getDownloadFilePath(item),
-							fileName: item.fileName,
-							extendName: item.extendName
+							downloadLink: this.getDownloadFilePath(item)
 						}
 				  })
 				: [
 						{
 							...videoInfo,
 							fileUrl: this.getViewFilePath(videoInfo),
-							downloadLink: this.getDownloadFilePath(videoInfo),
-							fileName: videoInfo.fileName,
-							extendName: videoInfo.extendName
+							downloadLink: this.getDownloadFilePath(videoInfo)
 						}
 				  ]
 		const defaultIndex =
@@ -361,6 +358,12 @@ const globalFunction = {
 				window.open(this.getViewFilePath(row), '_blank')
 				return false
 			}
+			//  若当前点击项是 markdown 文档
+			const MARKDOWN = ['markdown', 'md']
+			if (MARKDOWN.includes(row.extendName.toLowerCase())) {
+				this.handleMarkdownPreview(row)
+				return false
+			}
 			//  若当前点击项是视频mp4格式
 			const VIDEO = ['mp4']
 			if (VIDEO.includes(row.extendName.toLowerCase())) {

+ 3 - 1
src/libs/map.js

@@ -39,7 +39,9 @@ export const fileImgMap = new Map([
 	['vue', require('_a/images/file/file_vue.png')],
 	['xls', require('_a/images/file/file_excel.png')],
 	['xlsx', require('_a/images/file/file_excel.png')],
-	['zip', require('_a/images/file/file_zip.png')]
+	['zip', require('_a/images/file/file_zip.png')],
+	['md', require('_a/images/file/file_markdown.png')],
+	['markdown', require('_a/images/file/file_markdown.png')]
 ])
 
 /**

+ 2 - 0
src/plugins/fileOperationPlugins.js

@@ -24,6 +24,7 @@ import showImgPreviewBox from '_c/file/box/imgPreview/index.js'
 import showVideoPreviewBox from '_c/file/box/videoPreview/index.js'
 import showUploadFileBox from '_c/file/box/uploadFile/index.js'
 import showAudioPreviewBox from '_c/file/box/audioPreview/index.js'
+import showMarkdownPreviewBox from '_c/file/box/markdownPreview/index.js'
 
 const operateElement = {
 	install: (Vue) => {
@@ -45,6 +46,7 @@ const operateElement = {
 		Vue.prototype.$previewVideo = showVideoPreviewBox
 		Vue.prototype.$uploadFile = showUploadFileBox
 		Vue.prototype.$preivewAudio = showAudioPreviewBox
+		Vue.prototype.$previewMarkdown = showMarkdownPreviewBox
 	}
 }
 export default operateElement

+ 6 - 0
src/request/file.js

@@ -83,3 +83,9 @@ export const restoreRecoveryFile = (p) => post('/recoveryfile/restorefile', p)
 // 回收站文件批量删除
 export const batchDeleteRecoveryFile = (p) =>
 	post('/recoveryfile/batchdelete', p)
+
+/**
+ * 文件公共接口
+ */
+// 文件预览
+export const getFilePreview = (p) => get('/filetransfer/preview', p)

+ 2 - 1
vue.config.js

@@ -43,7 +43,8 @@ module.exports = {
 			_v: path.resolve(__dirname, './src/views'),
 			_c: path.resolve(__dirname, './src/components'),
 			_a: path.resolve(__dirname, './src/assets'),
-			_r: path.resolve(__dirname, './src/request')
+			_r: path.resolve(__dirname, './src/request'),
+			_public: path.resolve(__dirname, './public')
 		}
 	}
 }

Some files were not shown because too many files changed in this diff