@jx3box/jx3box-editor 3.2.6 → 3.2.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jx3box/jx3box-editor",
3
- "version": "3.2.6",
3
+ "version": "3.2.8",
4
4
  "description": "JX3BOX Article & Editor",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -74,9 +74,9 @@
74
74
  "postcss": "^8.5.6",
75
75
  "postcss-safe-parser": "^7.0.1",
76
76
  "prettier": "2.7.1",
77
+ "qrcode.vue": "^3.6.0",
77
78
  "sass": "^1.97.3",
78
79
  "sass-loader": "^16.0.7",
79
- "qrcode.vue": "^3.6.0",
80
80
  "serve": "^14.2.0",
81
81
  "storybook": "^8.6.14",
82
82
  "style-resources-loader": "^1.5.0",
package/src/Article.vue CHANGED
@@ -47,7 +47,12 @@
47
47
  v-show="jx3_element.type == 'skill'"
48
48
  />
49
49
  <jx3-npc :client="npc.client" :id="npc.id" v-show="jx3_element.type === 'npc'" />
50
- <jx3-author :uid="author.id" v-show="jx3_element.type === 'author'" />
50
+ <jx3-author
51
+ :key="author.id"
52
+ :uid="author.id"
53
+ :visible="jx3_element.type === 'author' && jx3_element.style.display !== 'none'"
54
+ v-show="jx3_element.type === 'author'"
55
+ />
51
56
  <jx3-emotion-author :id="emotion.id" v-show="jx3_element.type === 'emotion'" />
52
57
  </div>
53
58
  </div>
@@ -7,7 +7,7 @@
7
7
  class="u-avatar"
8
8
  :uid="uid"
9
9
  :url="data.user_avatar"
10
- :size="68"
10
+ :size="60"
11
11
  :frame="data.user_avatar_frame"
12
12
  />
13
13
  <div class="u-info">
@@ -58,29 +58,63 @@
58
58
  </div>
59
59
  </div>
60
60
  </div>
61
- <div class="u-teams" v-if="teams && teams.length">
62
- <a class="u-team" v-for="(item, i) in teams" :key="i" :href="teamLink(item.team_id)" target="_blank">
63
- <img class="u-team-logo" :src="showTeamLogo(item.team_logo)" />
64
- <span class="u-team-name">{{ item.team_name }}@{{ item.team_server }}</span>
65
- </a>
61
+ <div class="u-author-role" v-if="authorRoles.length">
62
+ <div class="u-author-role-label"></div>
63
+ <div class="u-author-roles">
64
+ <component
65
+ :is="item.url ? 'a' : 'span'"
66
+ class="u-author-role-item"
67
+ :href="item.url"
68
+ :target="item.url ? '_blank' : null"
69
+ :style="roleStyle(item)"
70
+ v-for="item in authorRoles"
71
+ :key="item.key"
72
+ >
73
+ <img class="u-author-role-icon" v-if="item.icon" :src="item.icon" />
74
+ <span class="u-author-role-content">
75
+ <span class="u-author-role-name">{{ item.name }}</span>
76
+ <span class="u-author-role-server" v-if="item.server">@{{ item.server }}</span>
77
+ </span>
78
+ </component>
79
+ </div>
66
80
  </div>
67
81
  </div>
68
82
  </div>
69
83
  </template>
70
84
 
71
85
  <script>
72
- import { authorLink, getLink, getMedalLink, getThumbnail } from "@jx3box/jx3box-common/js/utils";
73
- import { getUserInfo, getUserMedals, getUserPublicTeams } from "../service/author.js";
74
- import { getDecoration, getDecorationJson, getDecorationV2 } from "../service/cms.js";
86
+ import { authorLink, getMedalLink, showSchoolIcon } from "@jx3box/jx3box-common/js/utils";
87
+ import { getUserInfo, getUserMedals } from "../service/author.js";
88
+ import { getDecoration, getDecorationJson, getUserSkin } from "../service/cms.js";
75
89
  import User from "@jx3box/jx3box-common/js/user";
76
90
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
77
91
  import Avatar from "./Avatar.vue";
78
- const ATCARD_KEY = "decoration_atcard_v2";
92
+ const ATCARD_CACHE_KEY = "skin_atcard_pc_authorcard_v2_";
93
+ const ATCARD_SUBTYPE = "pc_authorcard";
94
+ const DEFAULT_ATCARD_IMAGE = "https://cdn.jx3box.com/design/skin/default/pc_authorcard.png";
95
+ const DEFAULT_ATCARD_POSITION = "right top";
79
96
  const DECORATION_JSON = "decoration_json";
80
97
  const DECORATION_KEY = "decoration_me";
81
98
  const HONOR_KEY = "honor_me";
99
+ const POSITION_MAP = {
100
+ lt: "left top",
101
+ mt: "center top",
102
+ ct: "center top",
103
+ rt: "right top",
104
+ lm: "left center",
105
+ ml: "left center",
106
+ mm: "center center",
107
+ cm: "center center",
108
+ o: "center center",
109
+ rm: "right center",
110
+ mr: "right center",
111
+ lb: "left bottom",
112
+ mb: "center bottom",
113
+ cb: "center bottom",
114
+ rb: "right bottom",
115
+ };
82
116
 
83
- const { __server, __imgPath, __userLevelColor, __cdn, __userLevel } = JX3BOX;
117
+ const { __server, __imgPath, __userLevelColor, __cdn, __Root, __userLevel } = JX3BOX;
84
118
  export default {
85
119
  name: "Author",
86
120
  components: {
@@ -91,13 +125,17 @@ export default {
91
125
  type: [String, Number],
92
126
  required: true,
93
127
  },
128
+ visible: {
129
+ type: Boolean,
130
+ default: true,
131
+ },
94
132
  },
95
133
  data: () => ({
96
134
  data: null,
97
135
  medals: [],
98
- teams: [],
99
136
  loading: false,
100
- bg: "",
137
+ bg: DEFAULT_ATCARD_IMAGE,
138
+ bgPosition: DEFAULT_ATCARD_POSITION,
101
139
  honor: "",
102
140
  honorStyle: {},
103
141
  }),
@@ -123,10 +161,15 @@ export default {
123
161
  isSuperAuthor: function () {
124
162
  return !!this.data?.sign;
125
163
  },
164
+ authorRoles: function () {
165
+ const roles = this.normalizeRoles(this.data?.public_roles);
166
+ return roles.map(this.normalizeRole).filter((item) => item.name);
167
+ },
126
168
  authorCardStyle: function () {
127
169
  return this.bg
128
170
  ? {
129
171
  backgroundImage: `url(${this.bg})`,
172
+ backgroundPosition: this.bgPosition,
130
173
  }
131
174
  : {};
132
175
  },
@@ -137,20 +180,28 @@ export default {
137
180
  handler(val) {
138
181
  if (val) {
139
182
  this.loadData();
140
- this.getAtcard();
183
+ if (this.visible) {
184
+ this.getAtcard();
185
+ }
141
186
  // this.getHonor();
142
187
  }
143
188
  },
144
189
  },
190
+ visible: {
191
+ handler(val) {
192
+ if (val && this.uid) {
193
+ this.getAtcard();
194
+ }
195
+ },
196
+ },
145
197
  },
146
198
  methods: {
147
199
  loadData: function () {
148
- const promises = [getUserInfo(this.uid), getUserMedals(this.uid), getUserPublicTeams(this.uid)];
200
+ const promises = [getUserInfo(this.uid), getUserMedals(this.uid)];
149
201
  this.loading = true;
150
202
  Promise.all(promises).then((res) => {
151
203
  this.data = res[0];
152
204
  this.medals = res[1];
153
- this.teams = res[2];
154
205
  this.loading = false;
155
206
  });
156
207
  },
@@ -164,43 +215,102 @@ export default {
164
215
  this.medals = data;
165
216
  });
166
217
  },
167
- loadTeams: function () {
168
- return getUserPublicTeams(this.uid).then((data) => {
169
- this.teams = data && data.slice(0, 5);
170
- });
171
- },
172
218
  getAtcard() {
173
- let decoration_atcard = sessionStorage.getItem(ATCARD_KEY + this.uid);
174
- if (decoration_atcard == "no") {
175
- this.bg = "";
219
+ const uid = this.uid;
220
+ this.setAtcardBackground();
221
+
222
+ const cached = this.getAtcardCache(uid);
223
+ if (cached) {
224
+ this.setAtcardBackground(cached === "no" ? null : cached);
176
225
  return;
177
226
  }
178
- //已有缓存,读取解析
179
- if (decoration_atcard) {
180
- this.setAtcardBackground(decoration_atcard);
227
+
228
+ getUserSkin({
229
+ user_id: uid,
230
+ type: "atcard",
231
+ })
232
+ .then((data) => {
233
+ if (this.uid !== uid) return;
234
+
235
+ let rows = data?.data?.data || [];
236
+ let skin = this.selectAtcardSkin(this.flattenUserSkins(rows));
237
+ let image = this.showDecorationImage(skin?.image);
238
+ if (!image) {
239
+ this.setAtcardCache(uid, "no");
240
+ this.setAtcardBackground();
241
+ return;
242
+ }
243
+ let payload = {
244
+ image,
245
+ position: this.resolveAtcardPosition(skin?.position),
246
+ };
247
+ this.setAtcardCache(uid, payload);
248
+ this.setAtcardBackground(payload);
249
+ })
250
+ .catch(() => {
251
+ if (this.uid === uid) {
252
+ this.setAtcardBackground();
253
+ }
254
+ });
255
+ },
256
+ setAtcardBackground(val = null) {
257
+ if (!val) {
258
+ this.bg = DEFAULT_ATCARD_IMAGE;
259
+ this.bgPosition = DEFAULT_ATCARD_POSITION;
181
260
  return;
182
261
  }
183
- getDecorationV2({
184
- using: 1,
185
- user_id: this.uid,
186
- type: "atcard",
187
- subtype: "pc_atcard",
188
- }).then((data) => {
189
- let res = data?.data?.data || [];
190
- let image = res[0]?.decorations?.[0]?.image;
191
- if (!image) {
192
- //空 则为无主题,不再加载接口,界面设No
193
- sessionStorage.setItem(ATCARD_KEY + this.uid, "no");
194
- this.bg = "";
195
- return;
196
- }
197
- image = this.showDecorationImage(image);
198
- sessionStorage.setItem(ATCARD_KEY + this.uid, image);
199
- this.setAtcardBackground(image);
200
- });
262
+ if (typeof val === "string") {
263
+ this.bg = val || "";
264
+ this.bgPosition = DEFAULT_ATCARD_POSITION;
265
+ return;
266
+ }
267
+ this.bg = val?.image || DEFAULT_ATCARD_IMAGE;
268
+ this.bgPosition = val?.position || DEFAULT_ATCARD_POSITION;
269
+ },
270
+ getAtcardCache(uid) {
271
+ let cached = sessionStorage.getItem(ATCARD_CACHE_KEY + uid);
272
+ if (!cached) return null;
273
+ if (cached === "no") return cached;
274
+ try {
275
+ return JSON.parse(cached);
276
+ } catch (e) {
277
+ sessionStorage.removeItem(ATCARD_CACHE_KEY + uid);
278
+ return null;
279
+ }
280
+ },
281
+ setAtcardCache(uid, val) {
282
+ if (!uid) return;
283
+ sessionStorage.setItem(ATCARD_CACHE_KEY + uid, val === "no" ? "no" : JSON.stringify(val));
284
+ },
285
+ flattenUserSkins(rows = []) {
286
+ return rows.reduce((list, row) => {
287
+ let skins = Array.isArray(row?.skins) ? row.skins : [];
288
+ return list.concat(skins);
289
+ }, []);
290
+ },
291
+ selectAtcardSkin(list = []) {
292
+ let skins = list.filter((item) => item?.subtype === ATCARD_SUBTYPE && item?.image);
293
+ let theme = this.getCurrentTheme();
294
+ return (
295
+ skins.find((item) => item?.theme === "all") ||
296
+ skins.find((item) => item?.theme === theme) ||
297
+ skins.find((item) => !item?.theme) ||
298
+ null
299
+ );
201
300
  },
202
- setAtcardBackground(val) {
203
- this.bg = val || "";
301
+ getCurrentTheme() {
302
+ let theme =
303
+ document.documentElement.getAttribute("data-theme") ||
304
+ document.body?.getAttribute("data-theme") ||
305
+ localStorage.getItem("__theme") ||
306
+ "light";
307
+ return String(theme || "light").toLowerCase() === "dark" ? "dark" : "light";
308
+ },
309
+ resolveAtcardPosition(position) {
310
+ let key = String(position || "rt")
311
+ .trim()
312
+ .toLowerCase();
313
+ return POSITION_MAP[key] || POSITION_MAP.rt;
204
314
  },
205
315
  getHonor() {
206
316
  this.honor = "";
@@ -287,12 +397,6 @@ export default {
287
397
  showMedalDesc: function (item) {
288
398
  return item.medal_desc || medal_map[item.medal];
289
399
  },
290
- teamLink: function (team_id) {
291
- return getLink("org", team_id);
292
- },
293
- showTeamLogo: function (val) {
294
- return getThumbnail(val, 96);
295
- },
296
400
  showLevelColor: function (level) {
297
401
  return __userLevelColor[level];
298
402
  },
@@ -301,6 +405,80 @@ export default {
301
405
  if (/^(https?:)?\/\//.test(val) || /^(data|blob):/.test(val)) return val;
302
406
  return __cdn.replace(/\/$/, "") + "/" + val.replace(/^\//, "");
303
407
  },
408
+ normalizeRoles: function (roles) {
409
+ if (!roles) return [];
410
+ if (Array.isArray(roles)) return roles;
411
+ if (typeof roles === "string") {
412
+ try {
413
+ const parsed = JSON.parse(roles);
414
+ return Array.isArray(parsed) ? parsed : [parsed];
415
+ } catch (e) {
416
+ return roles
417
+ .split(/[,,]/)
418
+ .map((item) => item.trim())
419
+ .filter(Boolean);
420
+ }
421
+ }
422
+ return [roles];
423
+ },
424
+ normalizeRole: function (role, index) {
425
+ if (typeof role === "string") {
426
+ return {
427
+ key: role + index,
428
+ name: role,
429
+ };
430
+ }
431
+
432
+ const info = role?.role_info || role?.info || {};
433
+ const name =
434
+ role?.name ||
435
+ role?.title ||
436
+ role?.label ||
437
+ role?.role_name ||
438
+ role?.display_name ||
439
+ role?.remark ||
440
+ info?.name ||
441
+ info?.title ||
442
+ info?.label ||
443
+ info?.role_name ||
444
+ "";
445
+
446
+ return {
447
+ key: role?.id || role?.key || role?.val || name + index,
448
+ name,
449
+ server: role?.server || role?.role_server || role?.jx3_server || info?.server || info?.role_server || "",
450
+ url: this.normalizeLink(role?.url || role?.link || role?.href || info?.url || info?.link || ""),
451
+ icon: this.normalizeRoleIcon(role, info),
452
+ color: role?.color || info?.color || "",
453
+ backgroundColor: role?.background_color || role?.bg_color || info?.background_color || info?.bg_color || "",
454
+ borderColor: role?.border_color || info?.border_color || "",
455
+ };
456
+ },
457
+ normalizeRoleAsset: function (url) {
458
+ if (!url) return "";
459
+ url = String(url).trim();
460
+ if (/^(https?:)?\/\//.test(url)) return url;
461
+ return __cdn + url.replace(/^\/+/, "");
462
+ },
463
+ normalizeRoleIcon: function (role, info) {
464
+ const mount = role?.mount || role?.school || role?.school_id || info?.mount || info?.school || info?.school_id || "";
465
+ if (mount) return showSchoolIcon(mount);
466
+
467
+ return this.normalizeRoleAsset(role?.icon || role?.logo || role?.image || info?.icon || info?.logo || "");
468
+ },
469
+ normalizeLink: function (url) {
470
+ if (!url) return "";
471
+ url = String(url).trim();
472
+ if (/^(https?:)?\/\//.test(url)) return url;
473
+ return __Root + url.replace(/^\/+/, "");
474
+ },
475
+ roleStyle: function (role) {
476
+ return {
477
+ color: role.color || null,
478
+ backgroundColor: role.backgroundColor || null,
479
+ borderColor: role.borderColor || null,
480
+ };
481
+ },
304
482
  authorLink,
305
483
  },
306
484
  };
@@ -310,19 +488,31 @@ export default {
310
488
  @import "../assets/css/module/author.less";
311
489
  .w-author {
312
490
  .w-author-wrapper {
491
+ width: 300px;
492
+ min-width: 300px;
493
+ max-width: 300px;
494
+ padding: 12px;
495
+ border: 1px solid rgba(132, 146, 166, 0.28);
496
+ border-radius: 8px;
497
+ background-color: #fff;
313
498
  background-repeat: no-repeat;
314
499
  background-position: top right;
315
500
  background-size: 100% auto;
501
+ box-shadow: 0 14px 34px rgba(31, 41, 55, 0.16), 0 2px 8px rgba(31, 41, 55, 0.08);
316
502
  &.is-no-atcard {
317
- background-color: #f8fafc;
318
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
503
+ border-color: rgba(124, 134, 156, 0.34);
504
+ background: linear-gradient(180deg, #ffffff 0%, #f3f6fb 100%);
319
505
  }
320
506
  .u-author {
321
- padding: 5px 0 15px 5px;
507
+ padding: 4px 10px 12px 5px;
322
508
  }
323
509
  .u-avatar {
324
510
  .fl;
325
- .mr(15px);
511
+ .mr(12px);
512
+ .size(60px);
513
+ }
514
+ .u-info {
515
+ height: 60px;
326
516
  }
327
517
  img {
328
518
  border: none;
@@ -340,7 +530,7 @@ export default {
340
530
  .u-displayname {
341
531
  .fz(15px);
342
532
  .nobreak;
343
- max-width: 200px;
533
+ max-width: 190px;
344
534
  color: @color;
345
535
  .bold;
346
536
  }
@@ -356,5 +546,61 @@ export default {
356
546
  .mb(15px);
357
547
  .r(2px);
358
548
  }
549
+ .u-author-role {
550
+ .mt(8px);
551
+ }
552
+ .u-author-role-label {
553
+ margin: 0 0 8px 0;
554
+ height: 1px;
555
+ background-color: rgba(154, 167, 181, 0.28);
556
+ }
557
+ .u-author-roles {
558
+ display: flex;
559
+ flex-direction: column;
560
+ gap: 4px;
561
+ }
562
+ .u-author-role-item {
563
+ .flex(y);
564
+ gap: 7px;
565
+ max-width: 100%;
566
+ min-height: 21px;
567
+ color: #56677a;
568
+ font-size: 12px;
569
+ line-height: 21px;
570
+ box-sizing: border-box;
571
+ text-decoration: none;
572
+
573
+ &:hover {
574
+ .u-author-role-server {
575
+ color: #fba524;
576
+ }
577
+ }
578
+ }
579
+ .u-author-role-icon {
580
+ .size(20px);
581
+ flex-shrink: 0;
582
+ object-fit: contain;
583
+ }
584
+ .u-author-role-content {
585
+ min-width: 0;
586
+ display: flex;
587
+ align-items: baseline;
588
+ }
589
+ .u-author-role-name {
590
+ min-width: 0;
591
+ margin-right: 2px;
592
+ overflow: hidden;
593
+ text-overflow: ellipsis;
594
+ white-space: nowrap;
595
+ color: #42566d;
596
+ font-size: 12px;
597
+ font-weight: 400;
598
+ }
599
+ .u-author-role-server {
600
+ flex-shrink: 0;
601
+ color: #fba524;
602
+ font-size: 12px;
603
+ font-weight: 400;
604
+ }
359
605
  }
360
606
  </style>
@@ -34,6 +34,12 @@ function getDecorationV2(params) {
34
34
  });
35
35
  }
36
36
 
37
+ function getUserSkin(params) {
38
+ return $cms().get(`/api/cms/user/skin`, {
39
+ params,
40
+ });
41
+ }
42
+
37
43
  function getDecorationJson() {
38
44
  let url = __cdn + "design/decoration/index.json";
39
45
  return axios.get(url);
@@ -45,4 +51,4 @@ function getLetterPaper(params) {
45
51
  params,
46
52
  });
47
53
  }
48
- export { uploadFile, loadAuthors, loadEmotions, getDecoration, getDecorationV2, getDecorationJson, getLetterPaper };
54
+ export { uploadFile, loadAuthors, loadEmotions, getDecoration, getDecorationV2, getUserSkin, getDecorationJson, getLetterPaper };