index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <div>
  3. <div class="mdeditor-box">
  4. <mavon-editor
  5. ref="markdown"
  6. class="markdown"
  7. :style="markdownStyle"
  8. v-model="content"
  9. :boxShadow="false"
  10. :toolbars="toolbars"
  11. @imgAdd="imgAdd">
  12. <template slot="left-toolbar-after">
  13. <span class="op-icon-divider"></span>
  14. <Dropdown @on-click="customClick" transfer>
  15. <button type="button" class="op-icon fa fa-mavon-picture-o" aria-hidden="true"></button>
  16. <DropdownMenu slot="list">
  17. <DropdownItem name="image-browse">{{$L('浏览图片')}}</DropdownItem>
  18. <DropdownItem name="image-upload">{{$L('上传图片')}}</DropdownItem>
  19. </DropdownMenu>
  20. </Dropdown>
  21. <div class="left-toolbar-item" :title="$L('上传文件')" @click="customClick('file-upload')">
  22. <svg t="1599285632421" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="45640" width="16" height="16"><path d="M127.519 892.879v-763.34h655.448V514.69h63.612V81.831c0-8.783-7.12-15.903-15.903-15.903H79.81c-8.783 0-15.903 7.12-15.903 15.903v858.757c0 8.783 7.12 15.903 15.903 15.903h493.993v-63.612H127.519z" fill="#999999" p-id="45641"></path><path d="M231.608 228.388h447.269V292H231.608zM231.608 384.409h447.269v63.612H231.608zM231.608 540.43h245.141v63.612H231.608zM231.608 696.451h245.141v63.612H231.608zM923.269 762.938L745.315 584.984c-3.545-3.545-8.34-5.074-12.966-4.596a15.931 15.931 0 0 0-9.848 4.616L544.586 762.918c-6.248 6.248-6.248 16.379 0 22.627l22.353 22.353c6.248 6.248 16.379 6.248 22.627 0l112.555-112.555v245.148c0 8.837 7.163 16 16 16h31.612c8.837 0 16-7.163 16-16V695.363l112.555 112.555c6.248 6.248 16.379 6.248 22.627 0l22.353-22.353c6.249-6.248 6.249-16.378 0.001-22.627z" fill="#999999" p-id="45642"></path></svg>
  23. </div>
  24. <span class="op-icon-divider"></span>
  25. <div class="left-toolbar-item text" :title="$L('html转markdown')" @click="customClick('html2md')">HTML2MD</div>
  26. </template>
  27. </mavon-editor>
  28. <ImgUpload
  29. ref="myUpload"
  30. class="upload-control"
  31. type="callback"
  32. :uploadIng.sync="uploadIng"
  33. @on-callback="editorImage"
  34. num="50"/>
  35. <Upload
  36. name="files"
  37. ref="fileUpload"
  38. class="upload-control"
  39. :action="actionUrl"
  40. :data="params"
  41. multiple
  42. :format="uploadFormat"
  43. :show-upload-list="false"
  44. :max-size="maxSize"
  45. :on-progress="handleProgress"
  46. :on-success="handleSuccess"
  47. :on-error="handleError"
  48. :on-format-error="handleFormatError"
  49. :on-exceeded-size="handleMaxSize"
  50. :before-upload="handleBeforeUpload"/>
  51. </div>
  52. <Spin fix v-if="uploadIng > 0">
  53. <Icon type="ios-loading" class="upload-control-spin-icon-load"></Icon>
  54. <div>{{$L('正在上传文件...')}}</div>
  55. </Spin>
  56. <Modal v-model="html2md" title="html转markdown" okText="转换成markdown" width="680" class-name="simple-modal" @on-ok="htmlOk" transfer>
  57. <Input type="textarea" v-model="htmlValue" :rows="14" placeholder="请输入html代码..." />
  58. </Modal>
  59. </div>
  60. </template>
  61. <style lang="scss" scoped>
  62. .mdeditor-box {
  63. position: relative;
  64. }
  65. .upload-control {
  66. display: none;
  67. width: 0;
  68. height: 0;
  69. overflow: hidden;
  70. }
  71. .left-toolbar-item {
  72. box-sizing: border-box;
  73. display: inline-block;
  74. cursor: pointer;
  75. height: 28px;
  76. width: 28px;
  77. margin: 6px 0 5px 0;
  78. font-size: 14px;
  79. padding: 4.5px 6px 5px 3.5px;
  80. color: #757575;
  81. border-radius: 5px;
  82. text-align: center;
  83. background: none;
  84. border: none;
  85. outline: none;
  86. line-height: 1;
  87. vertical-align: middle;
  88. transition: all 0.2s linear 0s;
  89. &:hover {
  90. color: rgba(0,0,0,0.8);
  91. background: #e9e9eb;
  92. }
  93. &.text {
  94. width: auto;
  95. line-height: 28px;
  96. padding: 0 3px;
  97. }
  98. }
  99. </style>
  100. <script>
  101. import Vue from 'vue'
  102. import mavonEditor from 'mavon-editor'
  103. import 'mavon-editor/dist/css/index.css'
  104. import ImgUpload from "../ImgUpload";
  105. import * as axios from "axios";
  106. Vue.use(mavonEditor)
  107. export default {
  108. name: 'MEditor',
  109. components: {ImgUpload},
  110. props: {
  111. value: {
  112. default: ''
  113. },
  114. height: {
  115. default: 380,
  116. },
  117. toolbars: {
  118. type: Object,
  119. default: () => {
  120. return {
  121. bold: true, // 粗体
  122. italic: true, // 斜体
  123. header: true, // 标题
  124. underline: true, // 下划线
  125. strikethrough: true, // 中划线
  126. mark: true, // 标记
  127. superscript: true, // 上角标
  128. subscript: true, // 下角标
  129. quote: true, // 引用
  130. ol: true, // 有序列表
  131. ul: true, // 无序列表
  132. link: true, // 链接
  133. imagelink: false, // 图片链接
  134. code: true, // code
  135. table: true, // 表格
  136. fullscreen: true, // 全屏编辑
  137. readmodel: true, // 沉浸式阅读
  138. htmlcode: true, // 展示html源码
  139. help: false, // 帮助
  140. undo: true, // 上一步
  141. redo: true, // 下一步
  142. trash: true, // 清空
  143. save: false, // 保存(触发events中的save事件)
  144. navigation: true, // 导航目录
  145. alignleft: true, // 左对齐
  146. aligncenter: true, // 居中
  147. alignright: true, // 右对齐
  148. subfield: true, // 单双栏模式
  149. preview: false, // 预览
  150. };
  151. }
  152. }
  153. },
  154. data() {
  155. return {
  156. content: '',
  157. html2md: false,
  158. htmlValue: '',
  159. uploadIng: 0,
  160. uploadFormat: ['jpg', 'jpeg', 'png', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'esp', 'pdf', 'rar', 'zip', 'gz'],
  161. actionUrl: $A.apiUrl('system/fileupload'),
  162. params: { token: $A.getToken() },
  163. maxSize: 10240
  164. };
  165. },
  166. mounted() {
  167. this.content = this.value;
  168. },
  169. activated() {
  170. this.content = this.value;
  171. },
  172. watch: {
  173. value(newValue) {
  174. if (newValue == null) {
  175. newValue = "";
  176. }
  177. this.content = newValue;
  178. },
  179. content(val) {
  180. this.$emit('input', val);
  181. },
  182. },
  183. computed: {
  184. markdownStyle() {
  185. const {height} = this;
  186. const style = {};
  187. if (height) {
  188. style.height = /^\d+$/.test(height) ? (height + 'px') : height
  189. }
  190. return style;
  191. }
  192. },
  193. methods: {
  194. imgAdd(pos, file) {
  195. //删除原本的地址
  196. let reg = eval("/(!\\[\[^\\[\]*?\\]\(?=\\(\)\)\\(\\s*\(" + pos + "\)\\s*\\)/g");
  197. let val = this.$refs.markdown.d_value;
  198. let obj = this.$refs.markdown.getTextareaDom();
  199. let match = val.match(reg);
  200. if (match) {
  201. obj.focus();
  202. let startPos = -1;
  203. if (typeof obj.selectionStart === 'number') {
  204. startPos = obj.selectionStart - match[0].length;
  205. }
  206. this.$refs.markdown.d_value = val.replace(reg, "");
  207. this.$refs.markdown.$refs.toolbar_left.img_file = [];
  208. this.$nextTick(() => {
  209. if (startPos > -1) {
  210. obj.selectionStart = startPos
  211. obj.selectionEnd = startPos;
  212. obj.focus();
  213. }
  214. });
  215. }
  216. //上传文件
  217. this.$refs.myUpload.handleManual(file);
  218. },
  219. editorImage(lists) {
  220. for (let i = 0; i < lists.length; i++) {
  221. let item = lists[i];
  222. if (typeof item === 'object' && typeof item.url === "string") {
  223. this.$refs.markdown.insertText(this.$refs.markdown.getTextareaDom(), {
  224. prefix: (i > 0 ? '\n' : '') + '![image](' + item.url + ')',
  225. subfix: '',
  226. str: ''
  227. });
  228. }
  229. }
  230. },
  231. customClick(act) {
  232. switch (act) {
  233. case "image-browse": {
  234. this.$refs.myUpload.browsePicture();
  235. break;
  236. }
  237. case "image-upload": {
  238. this.$refs.myUpload.handleClick();
  239. break;
  240. }
  241. case "file-upload": {
  242. this.$refs.fileUpload.handleClick();
  243. break;
  244. }
  245. case "html2md": {
  246. this.html2md = true;
  247. break;
  248. }
  249. }
  250. },
  251. htmlOk() {
  252. this.loadScript(window.location.origin + '/js/html2md.js', () => {
  253. if (typeof toMarkdown !== 'function') {
  254. alert("组件加载失败!");
  255. return;
  256. }
  257. this.$refs.markdown.insertText(this.$refs.markdown.getTextareaDom(), {
  258. prefix: '\n' + toMarkdown(this.htmlValue, { gfm: true }),
  259. subfix: '',
  260. str: ''
  261. });
  262. this.htmlValue = "";
  263. });
  264. },
  265. loadScript(url, callback) {
  266. let script = document.createElement("script");
  267. script.type = "text/javascript";
  268. if (script.readyState) {
  269. script.onreadystatechange = () => {
  270. if (script.readyState === "loaded" || script.readyState === "complete") {
  271. script.onreadystatechange = null;
  272. callback();
  273. }
  274. };
  275. } else {
  276. script.onload = () => {
  277. callback();
  278. };
  279. }
  280. script.src = url;
  281. document.body.appendChild(script);
  282. },
  283. /********************文件上传部分************************/
  284. handleProgress() {
  285. //开始上传
  286. this.uploadIng++;
  287. },
  288. handleSuccess(res, file) {
  289. //上传完成
  290. this.uploadIng--;
  291. if (res.ret === 1) {
  292. this.$refs.markdown.insertText(this.$refs.markdown.getTextareaDom(), {
  293. prefix: `[${res.data.name} (${$A.bytesToSize(res.data.size * 1024)})](${res.data.url})`,
  294. subfix: '',
  295. str: ''
  296. });
  297. } else {
  298. this.$Modal.warning({
  299. title: this.$L('上传失败'),
  300. content: this.$L('文件 % 上传失败,%', file.name, res.msg)
  301. });
  302. }
  303. },
  304. handleError() {
  305. //上传错误
  306. this.uploadIng--;
  307. },
  308. handleFormatError(file) {
  309. //上传类型错误
  310. this.$Modal.warning({
  311. title: this.$L('文件格式不正确'),
  312. content: this.$L('文件 % 格式不正确,仅支持上传:%', file.name, this.uploadFormat.join(','))
  313. });
  314. },
  315. handleMaxSize(file) {
  316. //上传大小错误
  317. this.$Modal.warning({
  318. title: this.$L('超出文件大小限制'),
  319. content: this.$L('文件 % 太大,不能超过%。', file.name, $A.bytesToSize(this.maxSize * 1024))
  320. });
  321. },
  322. handleBeforeUpload() {
  323. //上传前判断
  324. this.params = {
  325. token: $A.getToken(),
  326. };
  327. return true;
  328. },
  329. }
  330. }
  331. </script>