@jx3box/jx3box-editor 2.2.47 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/config/global.js +79 -0
  2. package/config/global.less +16 -0
  3. package/index.js +21 -8
  4. package/package.json +64 -63
  5. package/readme.md +25 -99
  6. package/src/Article.vue +96 -57
  7. package/src/ArticleMarkdown.vue +54 -47
  8. package/src/BoxResource.vue +67 -42
  9. package/src/Buff.vue +18 -7
  10. package/src/GameText.vue +32 -45
  11. package/src/Item.vue +143 -235
  12. package/src/ItemSimple.vue +27 -37
  13. package/src/Markdown.vue +362 -210
  14. package/src/Npc.vue +51 -30
  15. package/src/Resource.vue +296 -252
  16. package/src/Skill.vue +36 -26
  17. package/src/Tinymce.vue +162 -126
  18. package/src/Upload.vue +238 -155
  19. package/src/UploadAlbum.vue +189 -118
  20. package/{assets → src/assets}/css/article.less +1 -0
  21. package/src/assets/css/markdown.less +4 -0
  22. package/{assets → src/assets}/css/module/author.less +4 -3
  23. package/{assets → src/assets}/css/module/directory.less +23 -32
  24. package/{assets → src/assets}/css/module/talent.less +2 -2
  25. package/{assets → src/assets}/css/resource.less +56 -22
  26. package/src/assets/css/tinymce/_.less +28 -0
  27. package/src/assets/css/tinymce/a.less +21 -0
  28. package/{assets → src/assets}/css/tinymce/code.less +1 -1
  29. package/{assets/css → src/assets/css/tinymce}/combo.less +123 -18
  30. package/{assets → src/assets}/css/tinymce/fold.less +3 -6
  31. package/src/assets/css/tinymce/h.less +90 -0
  32. package/{assets → src/assets}/css/tinymce/latex.less +14 -14
  33. package/{assets → src/assets}/css/tinymce/macro.less +3 -3
  34. package/{assets → src/assets}/css/tinymce/pz.less +2 -2
  35. package/{assets → src/assets}/css/tinymce/table.less +5 -10
  36. package/{assets → src/assets}/css/tinymce.less +8 -4
  37. package/src/assets/css/upload.less +195 -0
  38. package/src/assets/css/upload_album.less +164 -0
  39. package/src/assets/css/var.less +2 -0
  40. package/src/assets/img/other/qr-code.svg +1 -0
  41. package/{assets → src/assets}/js/audio.js +2 -2
  42. package/{assets → src/assets}/js/directory.js +51 -23
  43. package/src/assets/js/hljs_languages.js +177 -0
  44. package/src/assets/js/katex.js +211 -0
  45. package/src/assets/js/renderImgPreview.js +49 -0
  46. package/{assets → src/assets}/js/xss.js +48 -5
  47. package/src/components/Author.vue +32 -13
  48. package/src/components/Avatar.vue +22 -3
  49. package/src/components/Combo.vue +118 -72
  50. package/src/components/PostAuthor.vue +13 -11
  51. package/src/components/QRcode.vue +136 -0
  52. package/src/components/SkillMartial.vue +13 -12
  53. package/src/pages/article.js +14 -0
  54. package/src/pages/index.js +5 -0
  55. package/src/pages/markdown.js +14 -0
  56. package/src/pages/tinymce.js +49 -0
  57. package/src/pages/upload.js +14 -0
  58. package/{service → src/service}/author.js +2 -2
  59. package/{service → src/service}/cms.js +5 -3
  60. package/{service → src/service}/database.js +4 -2
  61. package/{service → src/service}/item.js +1 -1
  62. package/{service → src/service}/node.js +1 -1
  63. package/{service → src/service}/resource.js +1 -1
  64. package/src/views/article.vue +72 -0
  65. package/src/views/index.vue +11 -0
  66. package/src/views/markdown.vue +58 -0
  67. package/src/views/tinymce.vue +58 -0
  68. package/src/views/upload.vue +111 -0
  69. package/.env +0 -2
  70. package/.vscode/settings.json +0 -5
  71. package/assets/css/katex-fix.css +0 -20
  72. package/assets/css/tinymce/_.less +0 -30
  73. package/assets/css/tinymce/a.less +0 -30
  74. package/assets/css/tinymce/combo.less +0 -111
  75. package/assets/css/tinymce/h.less +0 -58
  76. package/assets/css/upload.less +0 -105
  77. package/assets/js/katex.js +0 -191
  78. package/assets/js/renderImgPreview.js +0 -25
  79. package/babel.config.js +0 -3
  80. package/docs/markdown.md +0 -16
  81. package/jsconfig.json +0 -9
  82. package/public/article.html +0 -15
  83. package/public/css/article.css +0 -2481
  84. package/public/css/article.less +0 -3
  85. package/public/favicon.ico +0 -0
  86. package/public/tinymce.html +0 -33
  87. package/src/Equip.vue +0 -301
  88. package/src/components/LetterDemo.vue +0 -93
  89. package/src/components/medal.vue +0 -43
  90. package/test-audio.html +0 -121
  91. package/vue.config.js +0 -147
  92. /package/{assets → src/assets}/css/markdown/_.less +0 -0
  93. /package/{assets → src/assets}/css/markdown/macro.less +0 -0
  94. /package/{assets/css/article_markdown.less → src/assets/css/markdown/markdown-article.less} +0 -0
  95. /package/{assets/css/markdown.less → src/assets/css/markdown/markdown-editor.less} +0 -0
  96. /package/{assets → src/assets}/css/markdown/talent.less +0 -0
  97. /package/{assets → src/assets}/css/markdown/video.less +0 -0
  98. /package/{assets → src/assets}/css/module/buff.less +0 -0
  99. /package/{assets → src/assets}/css/module/icon.less +0 -0
  100. /package/{assets → src/assets}/css/module/item.less +0 -0
  101. /package/{assets → src/assets}/css/module/item_simple.less +0 -0
  102. /package/{assets → src/assets}/css/module/jx3_element.less +0 -0
  103. /package/{assets → src/assets}/css/module/macro.less +0 -0
  104. /package/{assets → src/assets}/css/module/npc.less +0 -0
  105. /package/{assets → src/assets}/css/module/resource.less +0 -0
  106. /package/{assets → src/assets}/css/module/skill.less +0 -0
  107. /package/{assets → src/assets}/css/tinymce/hr.less +0 -0
  108. /package/{assets → src/assets}/css/tinymce/img.less +0 -0
  109. /package/{assets → src/assets}/css/tinymce/imgpreview.less +0 -0
  110. /package/{assets → src/assets}/css/tinymce/list.less +0 -0
  111. /package/{assets → src/assets}/css/tinymce/nextpage.less +0 -0
  112. /package/{assets → src/assets}/css/tinymce/p.less +0 -0
  113. /package/{assets → src/assets}/css/tinymce/plugin.less +0 -0
  114. /package/{assets → src/assets}/css/tinymce/qixue.less +0 -0
  115. /package/{assets → src/assets}/css/tinymce/quote.less +0 -0
  116. /package/{assets → src/assets}/css/tinymce/video.less +0 -0
  117. /package/{assets → src/assets}/css/tinymce/voice.less +0 -0
  118. /package/{assets → src/assets}/data/detach_type.json +0 -0
  119. /package/{assets → src/assets}/data/game_font.json +0 -0
  120. /package/{assets → src/assets}/data/markdown_whitelist.json +0 -0
  121. /package/{assets → src/assets}/data/weapon_type.json +0 -0
  122. /package/{assets → src/assets}/img/buff.svg +0 -0
  123. /package/{assets → src/assets}/img/equip_bg.png +0 -0
  124. /package/{assets → src/assets}/img/file.svg +0 -0
  125. /package/{assets → src/assets}/img/icons.svg +0 -0
  126. /package/{assets → src/assets}/img/item/pve.png +0 -0
  127. /package/{assets → src/assets}/img/item/pvp.png +0 -0
  128. /package/{assets → src/assets}/img/item/pvx.png +0 -0
  129. /package/{assets → src/assets}/img/item/std.png +0 -0
  130. /package/{assets → src/assets}/img/item/wujie.png +0 -0
  131. /package/{assets → src/assets}/img/item.svg +0 -0
  132. /package/{assets → src/assets}/img/jx3.svg +0 -0
  133. /package/{assets → src/assets}/img/jx3box.svg +0 -0
  134. /package/{assets → src/assets}/img/npc/attack.svg +0 -0
  135. /package/{assets → src/assets}/img/npc/buff.svg +0 -0
  136. /package/{assets → src/assets}/img/npc/energy.svg +0 -0
  137. /package/{assets → src/assets}/img/npc/miss.svg +0 -0
  138. /package/{assets → src/assets}/img/npc/npc.svg +0 -0
  139. /package/{assets → src/assets}/img/npc/radar.svg +0 -0
  140. /package/{assets → src/assets}/img/npc/shield.svg +0 -0
  141. /package/{assets → src/assets}/img/npc/sight.svg +0 -0
  142. /package/{assets → src/assets}/img/npc/skull.svg +0 -0
  143. /package/{assets → src/assets}/img/npc/target.svg +0 -0
  144. /package/{assets → src/assets}/img/skill.svg +0 -0
  145. /package/{assets → src/assets}/img/skillset.png +0 -0
  146. /package/{assets → src/assets}/js/a.js +0 -0
  147. /package/{assets → src/assets}/js/code.js +0 -0
  148. /package/{assets → src/assets}/js/combo.js +0 -0
  149. /package/{assets → src/assets}/js/drag.js +0 -0
  150. /package/{assets → src/assets}/js/filter2.js +0 -0
  151. /package/{assets → src/assets}/js/fold.js +0 -0
  152. /package/{assets → src/assets}/js/gallery.js +0 -0
  153. /package/{assets → src/assets}/js/iframe.js +0 -0
  154. /package/{assets → src/assets}/js/img.js +0 -0
  155. /package/{assets → src/assets}/js/item/attribute_percent.js +0 -0
  156. /package/{assets → src/assets}/js/item/bind.js +0 -0
  157. /package/{assets → src/assets}/js/item/border.js +0 -0
  158. /package/{assets → src/assets}/js/item/border_quest.js +0 -0
  159. /package/{assets → src/assets}/js/item/color.js +0 -0
  160. /package/{assets → src/assets}/js/item/hljs_languages.js +0 -0
  161. /package/{assets → src/assets}/js/item/icon_url.js +0 -0
  162. /package/{assets → src/assets}/js/item/second_format.js +0 -0
  163. /package/{assets → src/assets}/js/jx3_element.js +0 -0
  164. /package/{assets → src/assets}/js/macro.js +0 -0
  165. /package/{assets → src/assets}/js/nextpage.js +0 -0
  166. /package/{assets → src/assets}/js/pswp.js +0 -0
  167. /package/{assets → src/assets}/js/pswp_template.js +0 -0
  168. /package/{assets → src/assets}/js/pz_iframe.js +0 -0
  169. /package/{assets → src/assets}/js/qixue.js +0 -0
  170. /package/{assets → src/assets}/js/script.js +0 -0
  171. /package/{assets → src/assets}/js/talent2.js +0 -0
  172. /package/{assets → src/assets}/js/tex-mml-chtml.js +0 -0
  173. /package/{service → src/service}/enum/CollectionPublic.js +0 -0
  174. /package/{service → src/service}/enum/EquipPosition.js +0 -0
  175. /package/{service → src/service}/enum/EquipType.js +0 -0
@@ -0,0 +1,211 @@
1
+ import $ from 'jquery';
2
+ import katex from 'katex';
3
+ import 'katex/dist/katex.min.css';
4
+
5
+ function renderKatexBlock(selector = ".w-latex") {
6
+ try {
7
+ $(selector).each(function() {
8
+ const $katex = $(this);
9
+
10
+ // 避免重复渲染
11
+ if ($katex.data('katex-rendered')) return;
12
+
13
+ let raw = $katex.html();
14
+
15
+ // 统一处理换行符
16
+ raw = raw
17
+ .replace(/\\\\\s*<br\s*\/?>/gi, '\\\\')
18
+ .replace(/\\\s*<br\s*\/?>/gi, '\\\\')
19
+ .replace(/<br\s*\/?>/gi, '\\\\')
20
+ .replace(/<[^>]+>/g, '');
21
+
22
+ // 解码HTML实体
23
+ raw = $('<div>').html(raw).text().trim();
24
+
25
+ try {
26
+ katex.render(raw, $katex.get(0), {
27
+ displayMode: true,
28
+ throwOnError: false,
29
+ strict: false
30
+ });
31
+ $katex.data('katex-rendered', true);
32
+ } catch (e) {
33
+ console.error('KaTeX render error:', e.message, raw);
34
+ }
35
+ });
36
+ } catch (e) {
37
+ console.error('KaTeX block render error:', e);
38
+ }
39
+ }
40
+
41
+ function renderKatexInline(container = document.body) {
42
+ // 改进的正则:不匹配换行符,支持转义
43
+ const inlineRegex = /(?<!\\)(\\\((.+?)\\\)|(?<!\\)\$([^\n$]+?)(?<!\\)\$)/g;
44
+
45
+ const walker = document.createTreeWalker(
46
+ container,
47
+ NodeFilter.SHOW_TEXT,
48
+ {
49
+ acceptNode: function (node) {
50
+ // 跳过已渲染的节点
51
+ if (
52
+ node.parentNode &&
53
+ (node.parentNode.classList?.contains('katex') ||
54
+ node.parentNode.closest("pre, code, .katex"))
55
+ ) {
56
+ return NodeFilter.FILTER_REJECT;
57
+ }
58
+
59
+ const value = node.nodeValue || '';
60
+ if (value.includes("\\(") || value.includes("$")) {
61
+ return NodeFilter.FILTER_ACCEPT;
62
+ }
63
+ return NodeFilter.FILTER_REJECT;
64
+ },
65
+ }
66
+ );
67
+
68
+ const nodesToReplace = [];
69
+ while (walker.nextNode()) {
70
+ nodesToReplace.push(walker.currentNode);
71
+ }
72
+
73
+ nodesToReplace.forEach((node) => {
74
+ const text = node.nodeValue;
75
+ const frag = document.createDocumentFragment();
76
+ let lastIndex = 0;
77
+
78
+ // 重置正则状态
79
+ inlineRegex.lastIndex = 0;
80
+
81
+ const matches = [...text.matchAll(inlineRegex)];
82
+
83
+ matches.forEach((match) => {
84
+ const fullMatch = match[0];
85
+ const parenContent = match[2];
86
+ const dollarContent = match[3];
87
+ const raw = parenContent || dollarContent;
88
+ const matchStart = match.index;
89
+
90
+ // 添加匹配前的文本
91
+ if (matchStart > lastIndex) {
92
+ frag.appendChild(document.createTextNode(text.slice(lastIndex, matchStart)));
93
+ }
94
+
95
+ try {
96
+ const span = document.createElement("span");
97
+ span.className = "katex-inline";
98
+ span.innerHTML = katex.renderToString(raw, {
99
+ displayMode: false,
100
+ throwOnError: false,
101
+ strict: false,
102
+ trust: true
103
+ });
104
+ frag.appendChild(span);
105
+ } catch (e) {
106
+ frag.appendChild(document.createTextNode(fullMatch));
107
+ console.error("Inline render error:", raw, e.message);
108
+ }
109
+
110
+ lastIndex = matchStart + fullMatch.length;
111
+ });
112
+
113
+ // 添加剩余文本
114
+ if (lastIndex < text.length) {
115
+ frag.appendChild(document.createTextNode(text.slice(lastIndex)));
116
+ }
117
+
118
+ if (frag.hasChildNodes()) {
119
+ node.parentNode.replaceChild(frag, node);
120
+ }
121
+ });
122
+ }
123
+
124
+ function renderKatexDisplayBlock(container = document.body) {
125
+ // 使用非贪婪匹配,允许多行但不跨过多段落
126
+ const blockRegex = /(?<!\\)(\$\$([\s\S]+?)\$\$|(?<!\\)\\\[([\s\S]+?)\\\])/g;
127
+
128
+ const walker = document.createTreeWalker(
129
+ container,
130
+ NodeFilter.SHOW_TEXT,
131
+ {
132
+ acceptNode: function (node) {
133
+ // 跳过已渲染的节点
134
+ if (
135
+ node.parentNode &&
136
+ (node.parentNode.classList?.contains('katex') ||
137
+ node.parentNode.closest("pre, code, .katex"))
138
+ ) {
139
+ return NodeFilter.FILTER_REJECT;
140
+ }
141
+
142
+ const value = node.nodeValue || '';
143
+ if (value.includes("$$") || value.includes("\\[")) {
144
+ return NodeFilter.FILTER_ACCEPT;
145
+ }
146
+ return NodeFilter.FILTER_REJECT;
147
+ },
148
+ }
149
+ );
150
+
151
+ const nodesToReplace = [];
152
+ while (walker.nextNode()) {
153
+ nodesToReplace.push(walker.currentNode);
154
+ }
155
+
156
+ nodesToReplace.forEach((node) => {
157
+ const text = node.nodeValue;
158
+ const frag = document.createDocumentFragment();
159
+ let lastIndex = 0;
160
+
161
+ // 重置正则状态
162
+ blockRegex.lastIndex = 0;
163
+
164
+ const matches = [...text.matchAll(blockRegex)];
165
+
166
+ matches.forEach((match) => {
167
+ const fullMatch = match[0];
168
+ const dollarContent = match[2];
169
+ const bracketContent = match[3];
170
+ const raw = (dollarContent || bracketContent).trim();
171
+ const matchStart = match.index;
172
+
173
+ // 添加匹配前的文本
174
+ if (matchStart > lastIndex) {
175
+ frag.appendChild(document.createTextNode(text.slice(lastIndex, matchStart)));
176
+ }
177
+
178
+ try {
179
+ const div = document.createElement("div");
180
+ div.className = "katex-block";
181
+ div.innerHTML = katex.renderToString(raw, {
182
+ displayMode: true,
183
+ throwOnError: false,
184
+ strict: false,
185
+ trust: true
186
+ });
187
+ frag.appendChild(div);
188
+ } catch (e) {
189
+ frag.appendChild(document.createTextNode(fullMatch));
190
+ console.error("Block render error:", raw, e.message);
191
+ }
192
+
193
+ lastIndex = matchStart + fullMatch.length;
194
+ });
195
+
196
+ // 添加剩余文本
197
+ if (lastIndex < text.length) {
198
+ frag.appendChild(document.createTextNode(text.slice(lastIndex)));
199
+ }
200
+
201
+ if (frag.hasChildNodes()) {
202
+ node.parentNode.replaceChild(frag, node);
203
+ }
204
+ });
205
+ }
206
+
207
+ export default function renderKatexAll(container = document.body) {
208
+ renderKatexBlock(".w-latex");
209
+ renderKatexDisplayBlock(container);
210
+ renderKatexInline(container);
211
+ }
@@ -0,0 +1,49 @@
1
+ import $ from "jquery";
2
+ import "viewerjs/dist/viewer.css";
3
+ import Viewer from "viewerjs";
4
+
5
+ const viewerMap = new WeakMap();
6
+
7
+ function initViewer(ele) {
8
+ if (!ele) return null;
9
+ if (viewerMap.has(ele)) return viewerMap.get(ele);
10
+ const viewer = new Viewer(ele, {
11
+ toolbar: false,
12
+ navbar: false,
13
+ });
14
+ viewerMap.set(ele, viewer);
15
+ return viewer;
16
+ }
17
+
18
+ export function getImgViewer(ele) {
19
+ return viewerMap.get(ele) || null;
20
+ }
21
+
22
+ export function showImgPreview(ele) {
23
+ const viewer = initViewer(ele);
24
+ if (viewer) viewer.show();
25
+ return viewer;
26
+ }
27
+
28
+ export default function renderImgPreview(rootEl, selector = "img") {
29
+ if (!rootEl) return;
30
+
31
+ const $root = $(rootEl);
32
+ const imgs = $root
33
+ .find(selector)
34
+ .filter(function () {
35
+ const src = $(this).attr("src");
36
+ if (!src) return false;
37
+ // 保留本项目的业务规则:表情图片不启用预览
38
+ if (this.classList && this.classList.contains("e-jx3-emotion-img"))
39
+ return false;
40
+ // 业务规则:图标不启用预览
41
+ if (this.classList && this.classList.contains("e-jx3-icon"))
42
+ return false;
43
+ return true;
44
+ });
45
+
46
+ imgs.each((_, ele) => {
47
+ initViewer(ele);
48
+ });
49
+ }
@@ -17,18 +17,60 @@ const EXTRA_TAGS = [
17
17
 
18
18
  // 必须顶层的 at-rule(你说不需要动画,但 keyframes 也可能被编辑器/作者写进来,留着更稳)
19
19
  const TOP_LEVEL_AT = new Set(["keyframes", "-webkit-keyframes", "font-face"]);
20
+ const CSS_URL_ALLOWED_HOSTS = new Set(["cdn.jx3box.com"]);
21
+
22
+ function unquoteCssUrl(raw = "") {
23
+ const s = String(raw).trim();
24
+ if (!s) return "";
25
+ if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
26
+ return s.slice(1, -1).trim();
27
+ }
28
+ return s;
29
+ }
30
+
31
+ function isSafeCssUrl(url = "") {
32
+ const s = String(url).trim();
33
+ if (!s) return false;
34
+ if (/[\u0000-\u001F\u007F]/.test(s)) return false;
35
+ if (/^(javascript|vbscript|data|file):/i.test(s)) return false;
36
+
37
+ // 站内相对地址保留
38
+ if (/^(\/|\.\/|\.\.\/|#)/.test(s)) return true;
39
+
40
+ // 绝对地址与协议相对地址:仅允许白名单域名
41
+ try {
42
+ const normalized = s.startsWith("//") ? `https:${s}` : s;
43
+ const u = new URL(normalized);
44
+ if (!/^https?:$/i.test(u.protocol)) return false;
45
+ return CSS_URL_ALLOWED_HOSTS.has(u.hostname.toLowerCase());
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ function sanitizeCssUrls(css = "") {
52
+ return String(css).replace(/url\s*\(\s*([^)]*?)\s*\)/gi, (all, rawUrl) => {
53
+ const clean = unquoteCssUrl(rawUrl);
54
+ if (!isSafeCssUrl(clean)) return "";
55
+ const escaped = clean.replace(/"/g, '\\"');
56
+ return `url("${escaped}")`;
57
+ });
58
+ }
20
59
 
21
60
  function stripDangerousCss(css) {
22
61
  if (!css) return css;
23
62
  return css
24
63
  .replace(/@import\s+[^;]+;?/gi, "")
25
- .replace(/url\s*\(\s*[^)]+\s*\)/gi, "")
26
- .replace(/expression\s*\([^)]*\)/gi, "");
64
+ .replace(/expression\s*\([^)]*\)/gi, "")
65
+ .replace(/-moz-binding\s*:\s*[^;]+;?/gi, "")
66
+ .replace(/behavior\s*:\s*[^;]+;?/gi, "")
67
+ .replace(/@charset\s+[^;]+;?/gi, "");
27
68
  }
28
69
 
29
70
  // 暴力把 style 内容 nest 到 .c-article
30
71
  function nestCssBrutally(cssText, scope = ".c-article") {
31
72
  let css = stripDangerousCss(cssText || "");
73
+ css = sanitizeCssUrls(css);
32
74
  if (!css.trim()) return css;
33
75
 
34
76
  const root = postcss.parse(css, { parser: safeParser });
@@ -101,11 +143,12 @@ export default function sanitizeRichText(html) {
101
143
  // 移除 on*
102
144
  for (const k of Object.keys(out)) if (/^on/i.test(k)) delete out[k];
103
145
 
104
- // style 属性:禁 @import / url(
146
+ // style 属性:禁 @import / expression,url() 仅保留安全协议
105
147
  if (typeof out.style === "string" && out.style) {
106
148
  let s = out.style;
107
149
  s = s.replace(/@import\s+[^;]+;?/gi, "");
108
- s = s.replace(/url\s*\(\s*[^)]+\s*\)/gi, "");
150
+ s = sanitizeCssUrls(s);
151
+ s = s.replace(/expression\s*\([^)]*\)/gi, "");
109
152
  s = s.replace(/;;+/g, ";").trim();
110
153
  if (!s) delete out.style;
111
154
  else out.style = s;
@@ -127,4 +170,4 @@ export default function sanitizeRichText(html) {
127
170
  },
128
171
  },
129
172
  });
130
- }
173
+ }
@@ -1,4 +1,5 @@
1
1
  <template>
2
+ <!-- @圈人pop:作者卡片 -->
2
3
  <div class="w-author" v-loading="loading">
3
4
  <div class="w-author-wrapper el-popover" v-if="data" :style="{ backgroundImage: `url(${bg})` }">
4
5
  <div class="u-author">
@@ -20,9 +21,9 @@
20
21
  </a>
21
22
  <div class="u-extend">
22
23
  <el-tooltip class="item" effect="dark" placement="top">
23
- <div slot="content">
24
+ <template #content>
24
25
  <span class="u-tips">经验值:{{ data.experience }}</span>
25
- </div>
26
+ </template>
26
27
  <span
27
28
  class="u-level"
28
29
  :class="'lv-' + level"
@@ -43,7 +44,18 @@
43
44
  <!-- <div class="u-honor" :style="honorStyle" v-if="honor">{{ honor }}</div> -->
44
45
  <div class="u-trophy" v-if="hasTrophy">
45
46
  <div class="u-medals" v-if="medals && medals.length">
46
- <medal :medals="medals" :showIcon="showMedalIcon"></medal>
47
+ <div class="m-medal">
48
+ <a
49
+ v-for="item in medals"
50
+ :key="item.id"
51
+ :href="medalLink(item)"
52
+ target="_blank"
53
+ class="u-medal"
54
+ :title="item.medal_desc"
55
+ >
56
+ <img class="u-medal-img" :src="showMedalIcon(item.medal)" />
57
+ </a>
58
+ </div>
47
59
  </div>
48
60
  </div>
49
61
  <div class="u-teams" v-if="teams && teams.length">
@@ -57,25 +69,29 @@
57
69
  </template>
58
70
 
59
71
  <script>
60
- import { authorLink, getLink, getThumbnail } from "@jx3box/jx3box-common/js/utils";
61
- import { getUserInfo, getUserMedals, getUserPublicTeams } from "../../service/author";
62
- import { getDecoration, getDecorationJson } from "../../service/cms";
63
- import { __server, __imgPath, __userLevelColor, __cdn } from "@jx3box/jx3box-common/data/jx3box.json";
72
+ import { authorLink, getLink, getMedalLink, getThumbnail } from "@jx3box/jx3box-common/js/utils";
73
+ import { getUserInfo, getUserMedals, getUserPublicTeams } from "../service/author";
74
+ import { getDecoration, getDecorationJson } from "../service/cms";
64
75
  import User from "@jx3box/jx3box-common/js/user";
65
- import { __userLevel } from "@jx3box/jx3box-common/data/jx3box.json";
76
+ import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
66
77
  import Avatar from "./Avatar.vue";
67
- import medal from "./medal.vue";
68
78
  const ATCARD_KEY = "decoration_atcard";
69
79
  const DECORATION_JSON = "decoration_json";
70
80
  const DECORATION_KEY = "decoration_me";
71
81
  const HONOR_KEY = "honor_me";
82
+
83
+ const { __server, __imgPath, __userLevelColor, __cdn, __userLevel } = JX3BOX;
72
84
  export default {
73
85
  name: "Author",
74
86
  components: {
75
- medal,
76
87
  Avatar,
77
88
  },
78
- props: ["uid"],
89
+ props: {
90
+ uid: {
91
+ type: [String, Number],
92
+ required: true,
93
+ },
94
+ },
79
95
  data: () => ({
80
96
  data: null,
81
97
  medals: [],
@@ -249,7 +265,10 @@ export default {
249
265
  },
250
266
 
251
267
  showMedalIcon: function (val) {
252
- return __imgPath + "image/medals/user/" + val + ".gif";
268
+ return __cdn + "/design/medals/user/" + val + ".gif";
269
+ },
270
+ medalLink: function ({ rank_id, medal_type = "rank" }) {
271
+ return getMedalLink(rank_id, medal_type);
253
272
  },
254
273
  showMedalDesc: function (item) {
255
274
  return item.medal_desc || medal_map[item.medal];
@@ -272,7 +291,7 @@ export default {
272
291
  </script>
273
292
 
274
293
  <style lang="less">
275
- @import "../../assets/css/module/author.less";
294
+ @import "../assets/css/module/author.less";
276
295
  .w-author {
277
296
  .w-author-wrapper {
278
297
  background-repeat: no-repeat;
@@ -9,11 +9,30 @@
9
9
  </template>
10
10
 
11
11
  <script>
12
- import { __server, __imgPath } from "@jx3box/jx3box-common/data/jx3box.json";
12
+ import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
13
13
  import { showAvatar, authorLink } from "@jx3box/jx3box-common/js/utils";
14
+
15
+ const { __server, __imgPath } = JX3BOX;
14
16
  export default {
15
17
  name: "Avatar",
16
- props: ["uid", "url", "size", "frame"],
18
+ props: {
19
+ uid: {
20
+ type: [String, Number],
21
+ required: true,
22
+ },
23
+ url: {
24
+ type: String,
25
+ default: "",
26
+ },
27
+ frame: {
28
+ type: String,
29
+ default: "",
30
+ },
31
+ size: {
32
+ type: [String, Number],
33
+ default: "s",
34
+ },
35
+ },
17
36
  components: {},
18
37
  data: function() {
19
38
  return {
@@ -49,7 +68,7 @@ export default {
49
68
  }
50
69
  .c-avatar-pic {
51
70
  .db;
52
- .full;
71
+ .size(100%);
53
72
  .r(100%);
54
73
  }
55
74
  .c-avatar-frame {