Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

343 lignes
6.6 KiB

  1. <template>
  2. <view class="up-markdown" :class="theme">
  3. <up-parse
  4. :content="parsedContent"
  5. :previewImg="previewImg"
  6. :copyLink="copyLink"
  7. :domain="domain"
  8. @load="emitLoad"
  9. @ready="emitReady"
  10. @imgtap="emitImgtap"
  11. @linktap="emitLinktap"
  12. @play="emitPlay"
  13. @error="emitError"
  14. ></up-parse>
  15. </view>
  16. </template>
  17. <script>
  18. import { marked } from './marked.esm.mjs';
  19. export default {
  20. name: 'up-markdown',
  21. // #ifdef VUE3
  22. emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
  23. // #endif
  24. props: {
  25. // markdown内容
  26. content: {
  27. type: String,
  28. default: ''
  29. },
  30. // 是否启用图片预览
  31. previewImg: {
  32. type: Boolean,
  33. default: true
  34. },
  35. // 是否允许外部链接被点击时自动打开
  36. copyLink: {
  37. type: [Boolean, String],
  38. default: true
  39. },
  40. // 主域名,用于 up-parse 处理相对链接
  41. domain: {
  42. type: String,
  43. default: ''
  44. },
  45. // 是否显示代码块行号
  46. showLineNumber: {
  47. type: Boolean,
  48. default: false
  49. },
  50. // 主题样式 'light' | 'dark'
  51. theme: {
  52. type: String,
  53. default: 'light'
  54. }
  55. },
  56. data() {
  57. return {
  58. parsedContent: ''
  59. };
  60. },
  61. watch: {
  62. content: {
  63. handler(newVal) {
  64. this.parseMarkdown(newVal);
  65. },
  66. immediate: true
  67. }
  68. },
  69. methods: {
  70. // 解析markdown内容
  71. parseMarkdown(content) {
  72. if (!content) {
  73. this.parsedContent = '';
  74. return;
  75. }
  76. // 使用marked解析markdown
  77. let parsed = marked(content);
  78. // 处理代码块
  79. parsed = this.handleCodeBlock(parsed);
  80. // 应用主题样式
  81. parsed = this.applyTheme(parsed);
  82. this.parsedContent = parsed;
  83. },
  84. // 处理代码块
  85. handleCodeBlock(html) {
  86. // 添加代码块样式和行号
  87. return html.replace(/<pre><code([^>]*)>([^<]+)<\/code><\/pre>/g, (match, lang, code) => {
  88. const language = lang.match(/class="language-([^"]+)"/);
  89. const langClass = language ? `language-${language[1]}` : '';
  90. let result = `<pre class="up-markdown-code ${langClass}">`;
  91. if (this.showLineNumber) {
  92. // 添加行号
  93. const lines = code.split('\n').filter(line => line.trim() !== '');
  94. result += '<span class="up-markdown-line-numbers">';
  95. lines.push('');
  96. lines.forEach((_, index) => {
  97. result += `<span class="up-markdown-line-number">${index + 1}</span>`;
  98. });
  99. result += '</span>';
  100. }
  101. result += `<code class='code-lang ${langClass}'>${code}</code></pre>`;
  102. return result;
  103. });
  104. },
  105. // 应用主题样式
  106. applyTheme(html) {
  107. // 可以根据theme属性添加不同的样式类
  108. return html;
  109. },
  110. emitLoad(event) {
  111. this.$emit('load', event)
  112. },
  113. emitReady(event) {
  114. this.$emit('ready', event)
  115. },
  116. emitImgtap(event) {
  117. this.$emit('imgtap', event)
  118. },
  119. emitLinktap(event) {
  120. this.$emit('linktap', event)
  121. },
  122. emitPlay(event) {
  123. this.$emit('play', event)
  124. },
  125. emitError(event) {
  126. this.$emit('error', event)
  127. }
  128. }
  129. };
  130. </script>
  131. <style lang="scss" scoped>
  132. .up-markdown {
  133. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  134. font-size: 16px;
  135. line-height: 1.6;
  136. color: #333;
  137. padding: 16px;
  138. word-wrap: break-word;
  139. /* 标题样式 */
  140. ::v-deep h1 {
  141. font-size: 32px;
  142. margin: 8px 0;
  143. font-weight: bold;
  144. }
  145. ::v-deep h2 {
  146. font-size: 24px;
  147. margin: 8px 0;
  148. font-weight: bold;
  149. }
  150. ::v-deep h3 {
  151. font-size: 18px;
  152. margin: 7px 0;
  153. font-weight: bold;
  154. }
  155. ::v-deep h4 {
  156. font-size: 16px;
  157. margin: 7px 0;
  158. font-weight: bold;
  159. }
  160. ::v-deep h5 {
  161. font-size: 13px;
  162. margin: 6px 0;
  163. font-weight: bold;
  164. }
  165. ::v-deep h6 {
  166. font-size: 10px;
  167. margin: 5px 0;
  168. font-weight: bold;
  169. }
  170. /* 段落样式 */
  171. ::v-deep p {
  172. margin: 16px 0;
  173. }
  174. /* 链接样式 */
  175. ::v-deep a {
  176. color: #007AFF;
  177. text-decoration: none;
  178. &:hover {
  179. text-decoration: underline;
  180. }
  181. }
  182. /* 列表样式 */
  183. ::v-deep ul,
  184. ::v-deep ol {
  185. margin: 16px 0;
  186. padding-left: 32px;
  187. li {
  188. margin: 8px 0;
  189. }
  190. }
  191. ::v-deep ul li {
  192. list-style-type: disc;
  193. }
  194. ::v-deep ol li {
  195. list-style-type: decimal;
  196. }
  197. /* 引用样式 */
  198. ::v-deep blockquote {
  199. margin: 8px 0;
  200. padding: 0 10px;
  201. border-left: 4px solid #ccc;
  202. color: #666;
  203. }
  204. /* 代码样式 */
  205. ::v-deep &-code {
  206. font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
  207. font-size: 14px;
  208. background-color: #f6f8fa;
  209. padding: 3px 6px;
  210. border-radius: 3px;
  211. display: flex;
  212. }
  213. ::v-deep .code-lang {
  214. width: 100%;
  215. overflow-x: auto;
  216. }
  217. ::v-deep pre {
  218. font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
  219. font-size: 14px;
  220. background-color: #f6f8fa;
  221. padding: 16px;
  222. overflow: auto;
  223. border-radius: 6px;
  224. margin: 16px 0;
  225. ::v-deep code {
  226. background: none;
  227. padding: 0;
  228. }
  229. }
  230. /* 表格样式 */
  231. ::v-deep table {
  232. border-collapse: collapse;
  233. margin: 16px 0;
  234. width: 100%;
  235. th,
  236. td {
  237. padding: 6px 13px;
  238. border: 1px solid #dfe2e5;
  239. }
  240. th {
  241. font-weight: 600;
  242. }
  243. tr:nth-child(2n) {
  244. background-color: #f6f8fa;
  245. }
  246. }
  247. /* 图片样式 */
  248. ::v-deep img {
  249. max-width: 100%;
  250. box-sizing: content-box;
  251. background-color: #fff;
  252. margin: 16px 0;
  253. }
  254. /* 分割线样式 */
  255. ::v-deep hr {
  256. height: 1px;
  257. padding: 0;
  258. margin: 24px 0;
  259. background-color: #e1e4e8;
  260. border: 0;
  261. }
  262. /* 深色主题 */
  263. &.dark {
  264. color: #ccc;
  265. background-color: #1e1e1e;
  266. ::v-deep &-code {
  267. background-color: #2d2d2d;
  268. color: #dcdcdc;
  269. }
  270. ::v-deep pre {
  271. background-color: #2d2d2d;
  272. color: #dcdcdc;
  273. }
  274. ::v-deep blockquote {
  275. margin: 8px 0;
  276. padding: 0 10px;
  277. border-left: 4px solid #ccc;
  278. color: #bbb;
  279. }
  280. ::v-deep a {
  281. color: #4da6ff;
  282. }
  283. }
  284. }
  285. /* 代码块行号样式 */
  286. ::v-deep .up-markdown-line-numbers {
  287. display: flex;
  288. flex-direction: column;
  289. align-items: flex-start;
  290. padding-right: 10px;
  291. margin-right: 10px;
  292. border-right: 1px solid #ddd;
  293. user-select: none;
  294. .up-markdown-line-number {
  295. color: #999;
  296. font-size: 14px;
  297. line-height: 1.6;
  298. }
  299. }
  300. </style>