@jx3box/jx3box-ui 2.3.16 → 2.3.18

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-ui",
3
- "version": "2.3.16",
3
+ "version": "2.3.18",
4
4
  "description": "JX3BOX Vue3 UI",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -34,7 +34,7 @@
34
34
  "@element-plus/icons-vue": "^2.3.2",
35
35
  "@jx3box/jx3box-common": "^9.2.9",
36
36
  "@jx3box/jx3box-data": "^3.9.6",
37
- "@jx3box/jx3box-editor": "^3.2.9",
37
+ "@jx3box/jx3box-editor": "^3.2.11",
38
38
  "@jx3box/jx3box-emotion": "^1.3.1",
39
39
  "@jx3box/jx3box-macro": "^1.0.4",
40
40
  "@jx3box/jx3box-talent": "^1.3.14",
package/service/author.js CHANGED
@@ -34,9 +34,10 @@ async function getDouyu(ids) {
34
34
  return res.data.data;
35
35
  }
36
36
 
37
+ // 兼容备用:公共作者组件已改用 /user/skin 聚合数据
37
38
  async function getUserMedals(uid, params) {
38
39
  return $next({ mute: true })
39
- .get("/api/next2/user/" + uid + "/medals", {params})
40
+ .get("/api/next2/user/" + uid + "/medals", { params })
40
41
  .then((res) => {
41
42
  return res.data.data;
42
43
  });
package/service/cms.js CHANGED
@@ -170,6 +170,7 @@ function refreshAuth() {
170
170
  return $cms().post(`/api/cms/user/account/token/refresh`);
171
171
  }
172
172
 
173
+ // 兼容备用:公共作者组件已改用 /user/skin 聚合数据
173
174
  function getUserHonor(uid) {
174
175
  return $cms({ mute: true }).get(`/api/cms/user/honor/${uid}/using`);
175
176
  }
package/service/header.js CHANGED
@@ -6,6 +6,7 @@ const GLOBAL_CONFIG_STORAGE_KEY = "jx3box:global-config";
6
6
  const GLOBAL_CONFIG_TTL = 6 * 60 * 60 * 1000;
7
7
  let globalConfigCache = null;
8
8
  let globalConfigPending = null;
9
+ let globalConfigRefreshPending = null;
9
10
 
10
11
  function getGlobalConfigUrl() {
11
12
  const root = JX3BOX.__ossRoot || "https://cdn.jx3box.com/";
@@ -74,7 +75,23 @@ function getGames() {
74
75
  }
75
76
 
76
77
  // 获取全局配置
77
- function getGlobalConfig() {
78
+ function getGlobalConfig({ force = false } = {}) {
79
+ if (force) {
80
+ if (!globalConfigRefreshPending) {
81
+ globalConfigRefreshPending = axios
82
+ .get(getGlobalConfigUrl())
83
+ .then((res) => {
84
+ globalConfigCache = res.data || {};
85
+ writeGlobalConfigStorage(globalConfigCache);
86
+ return globalConfigCache;
87
+ })
88
+ .finally(() => {
89
+ globalConfigRefreshPending = null;
90
+ });
91
+ }
92
+ return globalConfigRefreshPending;
93
+ }
94
+
78
95
  if (globalConfigCache) return Promise.resolve(globalConfigCache);
79
96
 
80
97
  const storageConfig = readGlobalConfigStorage();
@@ -1,76 +1,47 @@
1
1
  <template>
2
- <a class="c-author-honor" :style="{ backgroundImage: `url(${imgUrl()})` }" v-if="honor" :href="url" target="_blank">
3
- <span :style="{ color: honor.color }">{{ honor.honor }}</span>
2
+ <a
3
+ class="c-author-honor"
4
+ :style="{ backgroundImage: `url(${imgUrl()})` }"
5
+ v-if="displayHonor"
6
+ :href="url"
7
+ target="_blank"
8
+ >
9
+ <span :style="{ color: displayHonor.color }">{{ displayHonor.honor }}</span>
4
10
  </a>
5
11
  </template>
6
12
  <script>
7
13
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
8
- import { getUserHonor } from "../../service/cms";
9
14
  import { inRange } from "lodash";
10
15
 
11
16
  const { __cdn, __Root } = JX3BOX;
12
- const HONOR_IMG_KEY = "honor_img";
13
17
  export default {
14
18
  props: {
15
- uid: {
16
- type: [Number, String],
17
- default: 0,
18
- },
19
- },
20
- data: function () {
21
- return {
22
- honor: "",
23
- };
24
- },
25
- watch: {
26
- uid: {
27
- immediate: true,
28
- handler: function (val) {
29
- val && this.getHonor();
30
- },
19
+ honor: {
20
+ type: Object,
21
+ default: null,
31
22
  },
32
23
  },
33
24
  computed: {
34
- isJdt() {
35
- return this.honor?.val?.toLowerCase()?.includes("jdt");
25
+ displayHonor() {
26
+ return this.formatHonor(this.honor);
36
27
  },
37
28
  url() {
38
- return this.honor?.honor_info?.url ? __Root + this.honor?.honor_info?.url : "";
29
+ return this.displayHonor?.honor_info?.url ? __Root + this.displayHonor.honor_info.url : "";
39
30
  },
40
31
  },
41
32
  methods: {
42
33
  imgUrl: function () {
43
- let item = this.honor?.honor_info;
34
+ let item = this.displayHonor?.honor_info;
44
35
  if (!item) return;
45
- return __cdn + `design/decoration/honor/${item.img}/${item.img}.${item.img_ext}`;
46
- },
47
- getHonor() {
48
- let user_id = this.uid;
49
- if (!user_id) return;
50
- let honor_local = sessionStorage.getItem(HONOR_IMG_KEY + user_id) || "";
51
- //解析本地缓存
52
- if (honor_local == "no") return;
53
- try {
54
- this.honor = JSON.parse(honor_local);
55
- } catch (err) {
56
- getUserHonor(user_id).then((res) => {
57
- this.honor = res.data.data;
58
- if (!this.honor) {
59
- //空 则为无主题,不再加载接口,界面设No
60
- sessionStorage.setItem(HONOR_IMG_KEY + user_id, "no");
61
- return;
62
- }
63
- this.getHonorStyle(this.honor);
64
- });
65
- }
36
+ return __cdn + `design/decoration/honor/${item.img}/${item.img}.${item.img_ext || "png"}`;
66
37
  },
67
- //有称号后,获取样式配置
68
- getHonorStyle(honor) {
69
- const data = honor;
38
+ formatHonor(honor) {
39
+ if (!honor?.honor_info) return null;
40
+ const data = { ...honor };
70
41
  let honorConfig = honor?.honor_info;
71
42
  let only = honorConfig.only;
72
- let prefix = honorConfig.prefix;
73
- let regPrefix = honorConfig.prefix.match(/\{([^{}]+?)\}/g);
43
+ let prefix = honorConfig.prefix || "";
44
+ let regPrefix = prefix.match(/\{([^{}]+?)\}/g);
74
45
  let ranking = honorConfig.ranking || [];
75
46
  let honorStr = honorConfig.year || "";
76
47
 
@@ -103,14 +74,13 @@ export default {
103
74
  }
104
75
  }
105
76
  }
106
- data.honor = honorStr + honorConfig.suffix;
77
+ data.honor = honorStr + (honorConfig.suffix || "");
107
78
  data.color = honorConfig.color;
108
79
  data.img = honorConfig.img;
109
80
  data.img_ext = honorConfig.img_ext;
110
81
  data.isHave = true;
111
82
  data.isImgIndex = ranking?.length > 0;
112
- sessionStorage.setItem(HONOR_IMG_KEY + this.uid, JSON.stringify(data));
113
- this.honor = data;
83
+ return data;
114
84
  },
115
85
  },
116
86
  };
@@ -63,7 +63,7 @@
63
63
  </div>
64
64
  </div>
65
65
  </div>
66
- <Honor :uid="uid"></Honor>
66
+ <Honor :honor="honor"></Honor>
67
67
  <div class="u-bio">{{ data.user_bio }}</div>
68
68
  </div>
69
69
  </template>
@@ -91,6 +91,10 @@ export default {
91
91
  type: [Number, String],
92
92
  default: 0,
93
93
  },
94
+ honor: {
95
+ type: Object,
96
+ default: null,
97
+ },
94
98
  },
95
99
  components: {
96
100
  Avatar,
@@ -24,7 +24,6 @@
24
24
  <script>
25
25
  import * as utilModule from "@jx3box/jx3box-common/js/utils";
26
26
  const { getMedalLink } = utilModule;
27
- import { getUserMedals } from "../../service/author";
28
27
  import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
29
28
  import i18nMixin from "../../i18n/mixin";
30
29
  const { __cdn, __Root } = JX3BOX;
@@ -32,32 +31,19 @@ const { __cdn, __Root } = JX3BOX;
32
31
  export default {
33
32
  name: "AuthorMedals",
34
33
  mixins: [i18nMixin],
35
- props: ["uid"],
36
- components: {},
37
- data: function () {
38
- return {
39
- medals: [],
40
- };
34
+ props: {
35
+ medals: {
36
+ type: Array,
37
+ default: () => [],
38
+ },
41
39
  },
40
+ components: {},
42
41
  computed: {
43
42
  ready: function () {
44
- return this.uid && this.medals && this.medals.length;
45
- },
46
- },
47
- watch: {
48
- uid: {
49
- immediate: true,
50
- handler: function (val) {
51
- val && this.loadMedals();
52
- },
43
+ return this.medals && this.medals.length;
53
44
  },
54
45
  },
55
46
  methods: {
56
- loadMedals: function () {
57
- getUserMedals(this.uid, {is_wear: 1}).then((data) => {
58
- this.medals = data || [];
59
- });
60
- },
61
47
  showIcon(medal) {
62
48
  return __cdn + "design/medals/user/" + medal + ".webp";
63
49
  },
@@ -165,10 +165,17 @@
165
165
  import { ArrowDown, ArrowUp, Download } from "@element-plus/icons-vue";
166
166
  import { copyText } from "../../utils";
167
167
  import i18nMixin from "../../i18n/mixin";
168
- import { getConfig } from "../../service/cms";
168
+ import { getGlobalConfig } from "../../service/header";
169
169
 
170
170
  const APP_LOGO_FALLBACK = "https://cdn.jx3box.com/design/app/logo/jx3box-icon-512.png";
171
- const APP_CONFIG_KEYS = ["android_apk", "android_versions", "app_logo", "app_version_desc", "apple_url", "harmony_url"];
171
+ const APP_CONFIG_KEYS = [
172
+ "android_apk",
173
+ "android_versions",
174
+ "app_logo",
175
+ "app_version_desc",
176
+ "apple_url",
177
+ "harmony_url",
178
+ ];
172
179
 
173
180
  export default {
174
181
  name: "FooterResource",
@@ -305,20 +312,11 @@ export default {
305
312
  window.open(this.activeDownloadUrl, "_blank", "noopener,noreferrer");
306
313
  },
307
314
  loadAppConfig() {
308
- const storeConfig = this.getStoreAppConfig();
309
- if (storeConfig) {
310
- this.appConfig = {
311
- ...this.appConfig,
312
- ...storeConfig,
313
- };
314
- this.appConfigLoaded = true;
315
- return Promise.resolve();
316
- }
317
315
  if (this.appConfigLoading || this.appConfigLoaded) return Promise.resolve();
318
316
  this.appConfigLoading = true;
319
- return getConfig({ subtype: "app" })
320
- .then((items) => {
321
- this.appConfig = this.getAppConfigFromItems(items);
317
+ return getGlobalConfig({ force: true })
318
+ .then((config) => {
319
+ this.appConfig = this.getAppConfig(config);
322
320
  this.appConfigLoaded = true;
323
321
  })
324
322
  .catch(() => {
@@ -328,22 +326,13 @@ export default {
328
326
  this.appConfigLoading = false;
329
327
  });
330
328
  },
331
- getStoreAppConfig() {
332
- const config = this.$store?.state?.config || {};
329
+ getAppConfig(config) {
330
+ if (!config || typeof config !== "object") return {};
333
331
  const appConfig = APP_CONFIG_KEYS.reduce((result, key) => {
334
332
  if (config[key]) result[key] = config[key];
335
333
  return result;
336
334
  }, {});
337
- return Object.keys(appConfig).length ? appConfig : null;
338
- },
339
- getAppConfigFromItems(items) {
340
- if (!Array.isArray(items)) return {};
341
- return items.reduce((config, item) => {
342
- if (!item?.key || !APP_CONFIG_KEYS.includes(item.key)) return config;
343
- if (item.subtype && item.subtype !== "app") return config;
344
- config[item.key] = item.val;
345
- return config;
346
- }, {});
335
+ return appConfig;
347
336
  },
348
337
  getDescLines(description) {
349
338
  return String(description || "")
@@ -372,7 +361,9 @@ export default {
372
361
  window.open("/qqbot", "_blank");
373
362
  },
374
363
  },
375
- created: function () {},
364
+ created: function () {
365
+ this.loadAppConfig();
366
+ },
376
367
  mounted: function () {},
377
368
  };
378
369
  </script>
@@ -23,8 +23,7 @@
23
23
  </template>
24
24
 
25
25
  <script>
26
- import { getConfig, getUserMeta } from "../../service/cms";
27
- import User from "@jx3box/jx3box-common/js/user";
26
+ import { getConfig } from "../../service/cms";
28
27
  import i18nMixin from "../../i18n/mixin";
29
28
  // import shopIcon from "@/assets/img/components/common/header/gift.svg";
30
29
  export default {
@@ -72,25 +71,18 @@ export default {
72
71
  /**
73
72
  * 只在点击入口后记录本地版本;仅看到气泡不算已处理。
74
73
  */
75
- let meta = null;
76
- if (User.isLogin()) {
77
- meta = await getUserMeta({ key: "mall_pop" });
78
- }
79
74
  let config = this.config || (this.configManaged ? null : await getConfig({ key: "mall" }));
80
75
  if (!config) return;
81
76
  this.initialized = true;
82
77
  this.popValue = config.val;
83
78
 
84
- this.pop = this.shouldShowPop(meta, config.val);
79
+ this.pop = this.shouldShowPop(config.val);
85
80
  },
86
- shouldShowPop(meta, value) {
81
+ shouldShowPop(value) {
87
82
  if (!~~value) return false;
88
83
 
89
84
  const local = localStorage.getItem("mall_pop");
90
- if (String(local) === String(value)) return false;
91
-
92
- if (meta == null || meta == 1) return true;
93
- return ~~value > ~~local;
85
+ return String(local) !== String(value);
94
86
  },
95
87
  markPopRead() {
96
88
  if (!this.pop || this.popValue == null) return;
@@ -23,8 +23,7 @@
23
23
  </template>
24
24
 
25
25
  <script>
26
- import { getConfig, getUserMeta } from "../../service/cms";
27
- import User from "@jx3box/jx3box-common/js/user";
26
+ import { getConfig } from "../../service/cms";
28
27
  import i18nMixin from "../../i18n/mixin";
29
28
  // import vipIcon from "@/assets/img/components/common/header/vip.svg";
30
29
  export default {
@@ -72,25 +71,18 @@ export default {
72
71
  /**
73
72
  * 只在点击入口后记录本地版本;仅看到气泡不算已处理。
74
73
  */
75
- let meta = null;
76
- if (User.isLogin()) {
77
- meta = await getUserMeta({ key: "vip_pop" });
78
- }
79
74
  let config = this.config || (this.configManaged ? null : await getConfig({ key: "vip" }));
80
75
  if (!config) return;
81
76
  this.initialized = true;
82
77
  this.popValue = config.val;
83
78
 
84
- this.pop = this.shouldShowPop(meta, config.val);
79
+ this.pop = this.shouldShowPop(config.val);
85
80
  },
86
- shouldShowPop(meta, value) {
81
+ shouldShowPop(value) {
87
82
  if (!~~value) return false;
88
83
 
89
84
  const local = localStorage.getItem("vip_pop");
90
- if (String(local) === String(value)) return false;
91
-
92
- if (meta == null || meta == 1) return true;
93
- return ~~value > ~~local;
85
+ return String(local) !== String(value);
94
86
  },
95
87
  markPopRead() {
96
88
  if (!this.pop || this.popValue == null) return;
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="c-author" :style="decorationStyles">
3
- <AuthorInfo :uid="uid" :anonymous="anonymous" @ready="installModules" />
3
+ <AuthorInfo :uid="uid" :anonymous="anonymous" :honor="activeHonor" @ready="installModules" />
4
4
  <template v-if="data">
5
5
  <div class="u-interact">
6
6
  <!-- <AuthorFollow style="margin-right: 8px;" :uid="uid" /> -->
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  <!-- <AuthorLink class="u-block u-links" :uid="uid" :data="data" /> -->
15
15
  <AuthorRole class="u-block u-roles" :data="data" />
16
- <AuthorMedals class="u-block u-trophy" :uid="uid" />
16
+ <AuthorMedals class="u-block u-trophy" :medals="activeMedals" />
17
17
  <!-- <AuthorTeams class="u-block u-teams" :uid="uid" /> -->
18
18
  <!-- <AuthorFans class="u-block u-fans" :uid="uid" /> -->
19
19
  <slot></slot>
@@ -73,6 +73,8 @@ export default {
73
73
  data: "",
74
74
  sidebarSkin: "",
75
75
  sidebarSkinPosition: "",
76
+ activeHonor: null,
77
+ activeMedals: [],
76
78
  };
77
79
  },
78
80
  computed: {
@@ -89,6 +91,8 @@ export default {
89
91
  uid() {
90
92
  this.sidebarSkin = "";
91
93
  this.sidebarSkinPosition = "";
94
+ this.activeHonor = null;
95
+ this.activeMedals = [];
92
96
  this.loadSidebarSkin();
93
97
  },
94
98
  },
@@ -146,6 +150,8 @@ export default {
146
150
 
147
151
  getUserSkin(this.uid).then((res) => {
148
152
  const records = res.data.data || [];
153
+ this.activeHonor = records.find((item) => item?.type === "honor")?.honor || null;
154
+ this.activeMedals = records.find((item) => item?.type === "medals")?.medals || [];
149
155
  const record = records.find((item) => this.resolveSkinDetail(item));
150
156
  if (record && this.setSidebarSkin(record)) {
151
157
  return;
@@ -5,6 +5,8 @@ const root = path.resolve(__dirname, "..");
5
5
  const service = fs.readFileSync(path.join(root, "service/cms.js"), "utf8");
6
6
  const author = fs.readFileSync(path.join(root, "src/single/Author.vue"), "utf8");
7
7
  const authorInfo = fs.readFileSync(path.join(root, "src/author/AuthorInfo.vue"), "utf8");
8
+ const authorHonor = fs.readFileSync(path.join(root, "src/author/AuthorHonor.vue"), "utf8");
9
+ const authorMedals = fs.readFileSync(path.join(root, "src/author/AuthorMedals.vue"), "utf8");
8
10
  const authorStyle = fs.readFileSync(path.join(root, "assets/css/single/author.less"), "utf8");
9
11
 
10
12
  function assert(condition, message) {
@@ -23,6 +25,22 @@ assert(author.includes('item.subtype === "pc_sidebar"'), "Author should consume
23
25
 
24
26
  assert(author.includes("record.skins"), "Author should resolve active skin resources from skins[]");
25
27
 
28
+ assert(author.includes('item?.type === "honor"'), "Author should read active honor from the user skin response");
29
+
30
+ assert(author.includes('item?.type === "medals"'), "Author should read active medals from the user skin response");
31
+
32
+ assert(author.includes(':medals="activeMedals"'), "Author should pass active medals to AuthorMedals");
33
+
34
+ assert(!authorMedals.includes("getUserMedals"), "AuthorMedals should not request medals independently");
35
+
36
+ assert(!authorMedals.includes("getUserMedals"), "AuthorMedals should not request the legacy medal endpoint");
37
+
38
+ assert(authorInfo.includes(':honor="honor"'), "AuthorInfo should pass the active honor to AuthorHonor");
39
+
40
+ assert(!authorHonor.includes("sessionStorage"), "AuthorHonor should not use session storage");
41
+
42
+ assert(!authorHonor.includes("getUserHonor"), "AuthorHonor should not request the legacy active honor endpoint");
43
+
26
44
  assert(author.includes("backgroundPosition: this.sidebarSkinPosition"), "Author should apply skin position");
27
45
 
28
46
  assert(author.includes('rt: "right top"'), "Author should map rt to right top");