@jx3box/jx3box-ui 2.3.8 → 2.3.10
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/docs/agents/global-config-cdn.md +30 -0
- package/package.json +1 -1
- package/service/cms.js +50 -2
- package/service/header.js +63 -3
- package/service/thx.js +8 -5
- package/src/CommonHeader.vue +40 -1
- package/src/header/manage.vue +66 -29
- package/src/header/message.vue +1 -1
- package/src/header/shop.vue +49 -35
- package/src/header/user.vue +21 -3
- package/src/header/vip.vue +49 -31
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 全局配置 CDN 读取约定
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
`/api/cms/config` 中有一批低频更新的全局配置,后端会构建到 CDN JSON:
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
https://cdn.jx3box.com/config/global.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
这类配置在公共组件库和其它魔盒前端项目中应优先读取 CDN 静态 JSON,减少页面内多组件重复请求 config 接口。
|
|
12
|
+
|
|
13
|
+
## 实现原则
|
|
14
|
+
|
|
15
|
+
- 在项目服务层封装统一 helper,不要让具体组件直接请求 CDN。
|
|
16
|
+
- helper 优先读取内存缓存,再读 `sessionStorage`,没有缓存时请求 `global.json`。
|
|
17
|
+
- 同一页面生命周期内要复用 pending promise,避免多个组件并发触发多次请求。
|
|
18
|
+
- 对旧调用保持返回结构兼容,比如继续返回 `{ key, val }` 或数组,减少组件改动面。
|
|
19
|
+
- CDN 请求失败、缺少目标 key、或查询不适合静态 JSON 时,fallback 到旧 `/api/cms/config`。
|
|
20
|
+
- 公共头可以预热全局配置,但其它组件不能隐式依赖公共头加载顺序。
|
|
21
|
+
|
|
22
|
+
## 本仓落点
|
|
23
|
+
|
|
24
|
+
- `service/header.js`:`getGlobalConfig()` 负责读取 CDN JSON,并做内存缓存、`sessionStorage` 缓存、TTL 和 pending promise 复用。
|
|
25
|
+
- `service/cms.js`:`getConfig()` 优先从 `getGlobalConfig()` 合成旧接口兼容结构,失败时回退旧接口。
|
|
26
|
+
- `service/thx.js`:`getBoxcoinStatus()` 走统一 `getConfig({ key: "boxcoin" })`。
|
|
27
|
+
|
|
28
|
+
## 适用范围
|
|
29
|
+
|
|
30
|
+
后续其它魔盒项目如果提到“全局配置读取换 CDN”或“config 改成读 `global.json`”,默认按本约定处理:优先服务层兼容改造,组件侧尽量少动,迁移期保留旧接口 fallback。
|
package/package.json
CHANGED
package/service/cms.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { $cms } from "@jx3box/jx3box-common/js/api";
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
4
|
+
import { getGlobalConfig } from "./header";
|
|
5
|
+
|
|
6
|
+
const APP_CONFIG_KEYS = ["android_apk", "android_versions", "app_logo", "app_version_desc", "apple_url", "harmony_url"];
|
|
7
|
+
|
|
4
8
|
function getPostAuthors(post_id) {
|
|
5
9
|
return $cms({ mute: true }).get(`/api/cms/post/${post_id}/authors`);
|
|
6
10
|
}
|
|
@@ -80,8 +84,7 @@ function getTopicBucket(params) {
|
|
|
80
84
|
return $cms().get(`/api/cms/topic/bucket`, { params });
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
function getConfig(params) {
|
|
87
|
+
function getLegacyConfig(params) {
|
|
85
88
|
return $cms()
|
|
86
89
|
.get(`/api/cms/config`, { params })
|
|
87
90
|
.then((res) => {
|
|
@@ -89,6 +92,51 @@ function getConfig(params) {
|
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
function normalizeConfigItem(key, value, subtype = "") {
|
|
96
|
+
return {
|
|
97
|
+
key,
|
|
98
|
+
val: value,
|
|
99
|
+
subtype,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getConfigFromGlobal(params = {}) {
|
|
104
|
+
return getGlobalConfig().then((config) => {
|
|
105
|
+
if (!config || typeof config !== "object") return null;
|
|
106
|
+
|
|
107
|
+
if (params.key) {
|
|
108
|
+
const keys = String(params.key)
|
|
109
|
+
.split(",")
|
|
110
|
+
.map((key) => key.trim())
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
|
|
113
|
+
if (!keys.length) return null;
|
|
114
|
+
if (!keys.every((key) => Object.prototype.hasOwnProperty.call(config, key))) return null;
|
|
115
|
+
|
|
116
|
+
const items = keys.map((key) => normalizeConfigItem(key, config[key]));
|
|
117
|
+
return keys.length === 1 ? items[0] : items;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (params.subtype === "app") {
|
|
121
|
+
if (!APP_CONFIG_KEYS.every((key) => Object.prototype.hasOwnProperty.call(config, key))) return null;
|
|
122
|
+
return APP_CONFIG_KEYS.map((key) => normalizeConfigItem(key, config[key], "app"));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 获取config
|
|
130
|
+
function getConfig(params = {}) {
|
|
131
|
+
return getConfigFromGlobal(params)
|
|
132
|
+
.then((data) => {
|
|
133
|
+
return data || getLegacyConfig(params);
|
|
134
|
+
})
|
|
135
|
+
.catch(() => {
|
|
136
|
+
return getLegacyConfig(params);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
92
140
|
// 获取用户config
|
|
93
141
|
function getUserConfig(params) {
|
|
94
142
|
return $cms()
|
package/service/header.js
CHANGED
|
@@ -2,6 +2,47 @@ import axios from "axios";
|
|
|
2
2
|
import { $cms, $next } from "@jx3box/jx3box-common/js/api";
|
|
3
3
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
4
4
|
|
|
5
|
+
const GLOBAL_CONFIG_STORAGE_KEY = "jx3box:global-config";
|
|
6
|
+
const GLOBAL_CONFIG_TTL = 6 * 60 * 60 * 1000;
|
|
7
|
+
let globalConfigCache = null;
|
|
8
|
+
let globalConfigPending = null;
|
|
9
|
+
|
|
10
|
+
function getGlobalConfigUrl() {
|
|
11
|
+
const root = JX3BOX.__ossRoot || "https://cdn.jx3box.com/";
|
|
12
|
+
return `${root.replace(/\/?$/, "/")}config/global.json`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getNow() {
|
|
16
|
+
return Date.now ? Date.now() : new Date().getTime();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readGlobalConfigStorage() {
|
|
20
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
21
|
+
try {
|
|
22
|
+
const raw = sessionStorage.getItem(GLOBAL_CONFIG_STORAGE_KEY);
|
|
23
|
+
if (!raw) return null;
|
|
24
|
+
const cache = JSON.parse(raw);
|
|
25
|
+
if (!cache?.data || !cache?.time) return null;
|
|
26
|
+
if (getNow() - cache.time > GLOBAL_CONFIG_TTL) return null;
|
|
27
|
+
return cache.data;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeGlobalConfigStorage(data) {
|
|
34
|
+
if (typeof sessionStorage === "undefined") return;
|
|
35
|
+
try {
|
|
36
|
+
sessionStorage.setItem(
|
|
37
|
+
GLOBAL_CONFIG_STORAGE_KEY,
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
time: getNow(),
|
|
40
|
+
data,
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
}
|
|
45
|
+
|
|
5
46
|
function getLetter() {
|
|
6
47
|
return $next({ mute: true }).get("/api/letter/unread/count");
|
|
7
48
|
}
|
|
@@ -34,9 +75,28 @@ function getGames() {
|
|
|
34
75
|
|
|
35
76
|
// 获取全局配置
|
|
36
77
|
function getGlobalConfig() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
if (globalConfigCache) return Promise.resolve(globalConfigCache);
|
|
79
|
+
|
|
80
|
+
const storageConfig = readGlobalConfigStorage();
|
|
81
|
+
if (storageConfig) {
|
|
82
|
+
globalConfigCache = storageConfig;
|
|
83
|
+
return Promise.resolve(globalConfigCache);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!globalConfigPending) {
|
|
87
|
+
globalConfigPending = axios
|
|
88
|
+
.get(getGlobalConfigUrl())
|
|
89
|
+
.then((res) => {
|
|
90
|
+
globalConfigCache = res.data || {};
|
|
91
|
+
writeGlobalConfigStorage(globalConfigCache);
|
|
92
|
+
return globalConfigCache;
|
|
93
|
+
})
|
|
94
|
+
.finally(() => {
|
|
95
|
+
globalConfigPending = null;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return globalConfigPending;
|
|
40
100
|
}
|
|
41
101
|
|
|
42
102
|
export { getLetter, getMsg, getNav, getPanel, getBox, getMenu, getGames, getGlobalConfig };
|
package/service/thx.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { $pay
|
|
1
|
+
import { $pay } from "@jx3box/jx3box-common/js/api";
|
|
2
|
+
import { getConfig } from "./cms";
|
|
2
3
|
|
|
3
4
|
function getPostBoxcoinRecords(postType, postId, params) {
|
|
4
5
|
return $pay().get(`/api/inspire/article/${postType}/${postId}/history`, {
|
|
@@ -23,10 +24,12 @@ function getPostBoxcoinConfig(postType) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
function getBoxcoinStatus() {
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return getConfig({ key: "boxcoin" }).then((data) => {
|
|
28
|
+
return {
|
|
29
|
+
data: {
|
|
30
|
+
data,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
|
package/src/CommonHeader.vue
CHANGED
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
20
|
<!-- user -->
|
|
21
|
-
<header-user
|
|
21
|
+
<header-user
|
|
22
|
+
ref="user"
|
|
23
|
+
class="c-header__user"
|
|
24
|
+
:client="client"
|
|
25
|
+
:asset="asset"
|
|
26
|
+
:header-config="headerConfig"
|
|
27
|
+
:header-config-loaded="headerConfigLoaded"
|
|
28
|
+
:header-config-managed="true"
|
|
29
|
+
/>
|
|
22
30
|
</div>
|
|
23
31
|
<header-box v-if="isMobile" class="c-header__box c-header-jx3box" :overlayEnable="overlayEnable" />
|
|
24
32
|
<header-box2 v-else class="c-header__box c-header__box--desktop" />
|
|
@@ -45,9 +53,12 @@ import miniprogram from "@jx3box/jx3box-common/data/miniprogram.json";
|
|
|
45
53
|
|
|
46
54
|
// 数据
|
|
47
55
|
import { getGlobalConfig } from "../service/header";
|
|
56
|
+
import { getConfig } from "../service/cms";
|
|
48
57
|
import User from "@jx3box/jx3box-common/js/user.js";
|
|
49
58
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
50
59
|
|
|
60
|
+
const HEADER_CONFIG_KEYS = ["important_notice", "important_notice_url", "vip", "mall"];
|
|
61
|
+
|
|
51
62
|
export default {
|
|
52
63
|
name: "Header",
|
|
53
64
|
components: {
|
|
@@ -67,6 +78,8 @@ export default {
|
|
|
67
78
|
isMobile: window.innerWidth <= 768,
|
|
68
79
|
|
|
69
80
|
asset: {},
|
|
81
|
+
headerConfig: {},
|
|
82
|
+
headerConfigLoaded: false,
|
|
70
83
|
};
|
|
71
84
|
},
|
|
72
85
|
computed: {
|
|
@@ -133,6 +146,7 @@ export default {
|
|
|
133
146
|
// 检查
|
|
134
147
|
init: function () {
|
|
135
148
|
this.checkIsWebView();
|
|
149
|
+
this.loadHeaderConfig();
|
|
136
150
|
|
|
137
151
|
const token = this.getUrlParam("__token");
|
|
138
152
|
const env = this.getUrlParam("__env");
|
|
@@ -199,6 +213,31 @@ export default {
|
|
|
199
213
|
}
|
|
200
214
|
});
|
|
201
215
|
},
|
|
216
|
+
loadHeaderConfig: function () {
|
|
217
|
+
getConfig({
|
|
218
|
+
key: HEADER_CONFIG_KEYS.join(","),
|
|
219
|
+
})
|
|
220
|
+
.then((data) => {
|
|
221
|
+
this.headerConfig = this.normalizeHeaderConfig(data);
|
|
222
|
+
})
|
|
223
|
+
.catch(() => {
|
|
224
|
+
this.headerConfig = {};
|
|
225
|
+
})
|
|
226
|
+
.finally(() => {
|
|
227
|
+
this.headerConfigLoaded = true;
|
|
228
|
+
});
|
|
229
|
+
},
|
|
230
|
+
normalizeHeaderConfig: function (data) {
|
|
231
|
+
if (Array.isArray(data)) {
|
|
232
|
+
return data.reduce((result, item) => {
|
|
233
|
+
if (item?.key) {
|
|
234
|
+
result[item.key] = item;
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}, {});
|
|
238
|
+
}
|
|
239
|
+
return data?.key ? { [data.key]: data } : {};
|
|
240
|
+
},
|
|
202
241
|
updateScreen() {
|
|
203
242
|
this.isMobile = window.innerWidth <= 768;
|
|
204
243
|
},
|
package/src/header/manage.vue
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="c-header-panel c-header-manage" id="c-header-manage">
|
|
3
3
|
<span class="u-post u-manage">
|
|
4
|
-
<i class="u-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
<i class="u-icon u-icon-msg">
|
|
5
|
+
<i class="u-pop" style="display: none" v-show="showPop || !isAuth"></i>
|
|
6
|
+
<!-- <manageIcon class="u-add" /> -->
|
|
7
|
+
<img
|
|
8
|
+
class="u-add"
|
|
9
|
+
svg-inline
|
|
10
|
+
src="../../assets/img/common/manage.svg"
|
|
11
|
+
:alt="$jx3boxT('jx3boxUi.commonHeader.manageCenter', '扩展中心')"
|
|
12
|
+
/>
|
|
13
|
+
</i>
|
|
12
14
|
</span>
|
|
13
15
|
<ul class="u-menu u-pop-content">
|
|
14
16
|
<template v-for="item in userPanel">
|
|
@@ -42,6 +44,7 @@ import i18nMixin from "../../i18n/mixin";
|
|
|
42
44
|
import { getConfig } from "../../service/cms";
|
|
43
45
|
// import manageIcon from "@/assets/img/components/common/header/manage.svg";
|
|
44
46
|
const { __imgPath } = JX3BOX;
|
|
47
|
+
const NOTICE_POP_KEY = "notice_pop";
|
|
45
48
|
const defaultPanel = [
|
|
46
49
|
{
|
|
47
50
|
key: "manageCenter",
|
|
@@ -67,6 +70,22 @@ export default {
|
|
|
67
70
|
type: Boolean,
|
|
68
71
|
default: false,
|
|
69
72
|
},
|
|
73
|
+
importantNotice: {
|
|
74
|
+
type: Object,
|
|
75
|
+
default: null,
|
|
76
|
+
},
|
|
77
|
+
importantNoticeUrl: {
|
|
78
|
+
type: Object,
|
|
79
|
+
default: null,
|
|
80
|
+
},
|
|
81
|
+
headerConfigLoaded: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: true,
|
|
84
|
+
},
|
|
85
|
+
headerConfigManaged: {
|
|
86
|
+
type: Boolean,
|
|
87
|
+
default: false,
|
|
88
|
+
},
|
|
70
89
|
},
|
|
71
90
|
computed: {
|
|
72
91
|
userPanel: function () {
|
|
@@ -84,7 +103,16 @@ export default {
|
|
|
84
103
|
},
|
|
85
104
|
},
|
|
86
105
|
mounted() {
|
|
87
|
-
this.
|
|
106
|
+
if (this.headerConfigLoaded) {
|
|
107
|
+
this.loadPanel();
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
watch: {
|
|
111
|
+
headerConfigLoaded: function (val) {
|
|
112
|
+
if (val) {
|
|
113
|
+
this.loadPanel();
|
|
114
|
+
}
|
|
115
|
+
},
|
|
88
116
|
},
|
|
89
117
|
methods: {
|
|
90
118
|
getPanelLabel(item) {
|
|
@@ -94,19 +122,16 @@ export default {
|
|
|
94
122
|
loadPanel: async function () {
|
|
95
123
|
try {
|
|
96
124
|
const panel = JSON.parse(sessionStorage.getItem("panel"));
|
|
97
|
-
|
|
125
|
+
const noticeConfig = await this.getNoticeConfig("important_notice", this.importantNotice);
|
|
126
|
+
const noticeUrlConfig = await this.getNoticeConfig("important_notice_url", this.importantNoticeUrl);
|
|
98
127
|
if (panel) {
|
|
99
|
-
this.panel = panel;
|
|
128
|
+
this.panel = this.applyNoticeConfig(panel, noticeConfig, noticeUrlConfig);
|
|
100
129
|
const item = this.panel?.find((i) => i.meta);
|
|
101
130
|
this.initMeta(item);
|
|
131
|
+
sessionStorage.setItem("panel", JSON.stringify(this.panel));
|
|
102
132
|
} else {
|
|
103
133
|
getMenu("panel").then((res) => {
|
|
104
|
-
this.panel = res.data?.data?.val
|
|
105
|
-
return {
|
|
106
|
-
...item,
|
|
107
|
-
link: item.remark == 'feature' ? config.val : item.link
|
|
108
|
-
};
|
|
109
|
-
});
|
|
134
|
+
this.panel = this.applyNoticeConfig(res.data?.data?.val, noticeConfig, noticeUrlConfig);
|
|
110
135
|
const item = this.panel?.find((i) => i.meta);
|
|
111
136
|
this.initMeta(item);
|
|
112
137
|
sessionStorage.setItem("panel", JSON.stringify(this.panel));
|
|
@@ -117,22 +142,33 @@ export default {
|
|
|
117
142
|
console.log("loadPanel error", e);
|
|
118
143
|
}
|
|
119
144
|
},
|
|
145
|
+
getNoticeConfig: async function (key, config) {
|
|
146
|
+
if (config) return config;
|
|
147
|
+
if (this.headerConfigManaged) return null;
|
|
148
|
+
return await getConfig({ key }).catch(() => null);
|
|
149
|
+
},
|
|
150
|
+
applyNoticeConfig: function (panel = [], noticeConfig, noticeUrlConfig) {
|
|
151
|
+
const items = panel?.length ? panel : defaultPanel;
|
|
152
|
+
return items.map((item) => {
|
|
153
|
+
if (item.remark !== "feature") return item;
|
|
154
|
+
return {
|
|
155
|
+
...item,
|
|
156
|
+
link: noticeUrlConfig?.val || item.link,
|
|
157
|
+
meta: noticeConfig?.val || item.meta,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
},
|
|
120
161
|
resolveImg: function (img) {
|
|
121
162
|
return img ? __imgPath + "image/header/panel/" + img : __imgPath + "image/header/panel/default.svg";
|
|
122
163
|
},
|
|
123
164
|
initMeta(item) {
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.showPop = item?.meta && item?.meta != local;
|
|
128
|
-
} else {
|
|
129
|
-
localStorage.setItem("jb_panel_meta", item?.meta);
|
|
130
|
-
this.showPop = true;
|
|
131
|
-
}
|
|
165
|
+
const meta = item?.meta;
|
|
166
|
+
const local = localStorage.getItem(NOTICE_POP_KEY);
|
|
167
|
+
this.showPop = !!meta && meta != local;
|
|
132
168
|
},
|
|
133
169
|
onClick(item) {
|
|
134
170
|
if (item.meta) {
|
|
135
|
-
localStorage.setItem(
|
|
171
|
+
localStorage.setItem(NOTICE_POP_KEY, item.meta);
|
|
136
172
|
this.showPop = false;
|
|
137
173
|
}
|
|
138
174
|
},
|
|
@@ -180,6 +216,7 @@ export default {
|
|
|
180
216
|
|
|
181
217
|
.u-icon-msg {
|
|
182
218
|
.pr;
|
|
219
|
+
.size(18px);
|
|
183
220
|
}
|
|
184
221
|
|
|
185
222
|
.u-pop {
|
|
@@ -188,11 +225,11 @@ export default {
|
|
|
188
225
|
color: #fff;
|
|
189
226
|
background-image: linear-gradient(#fcd14f, #d7a20b);
|
|
190
227
|
background-clip: padding-box;
|
|
191
|
-
border: 2px solid
|
|
228
|
+
border: 2px solid @header-bg;
|
|
192
229
|
border-radius: 50%;
|
|
193
230
|
position: absolute;
|
|
194
|
-
right:
|
|
195
|
-
top:
|
|
231
|
+
right: -5px;
|
|
232
|
+
top: -4px;
|
|
196
233
|
z-index: 1;
|
|
197
234
|
}
|
|
198
235
|
|
package/src/header/message.vue
CHANGED
package/src/header/shop.vue
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
placement="bottom"
|
|
7
7
|
popper-class="c-header-tooltip"
|
|
8
8
|
>
|
|
9
|
-
<a class="u-present" href="/vip/mall">
|
|
9
|
+
<a class="u-present" href="/vip/mall" @click="markPopRead">
|
|
10
10
|
<i class="u-icon u-icon-msg">
|
|
11
11
|
<i class="u-pop" style="display: none" v-show="pop"></i>
|
|
12
12
|
<!-- <shopIcon class="u-icon" /> -->
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
25
|
<script>
|
|
26
|
-
import { getConfig, getUserMeta
|
|
26
|
+
import { getConfig, getUserMeta } from "../../service/cms";
|
|
27
27
|
import User from "@jx3box/jx3box-common/js/user";
|
|
28
28
|
import i18nMixin from "../../i18n/mixin";
|
|
29
29
|
// import shopIcon from "@/assets/img/components/common/header/gift.svg";
|
|
@@ -36,52 +36,66 @@ export default {
|
|
|
36
36
|
data: function () {
|
|
37
37
|
return {
|
|
38
38
|
pop: false,
|
|
39
|
+
initialized: false,
|
|
40
|
+
popValue: "",
|
|
39
41
|
};
|
|
40
42
|
},
|
|
43
|
+
props: {
|
|
44
|
+
config: {
|
|
45
|
+
type: Object,
|
|
46
|
+
default: null,
|
|
47
|
+
},
|
|
48
|
+
configLoaded: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: true,
|
|
51
|
+
},
|
|
52
|
+
configManaged: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
watch: {
|
|
58
|
+
configLoaded: function (val) {
|
|
59
|
+
if (val) {
|
|
60
|
+
this.init();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
41
64
|
mounted() {
|
|
42
|
-
this.
|
|
65
|
+
if (this.configLoaded) {
|
|
66
|
+
this.init();
|
|
67
|
+
}
|
|
43
68
|
},
|
|
44
69
|
methods: {
|
|
45
70
|
async init() {
|
|
71
|
+
if (this.initialized) return;
|
|
46
72
|
/**
|
|
47
|
-
*
|
|
48
|
-
* 2. 用户第二次进入页面,此时meta有记录,但是用户并未进入会员中心,则显示
|
|
49
|
-
*
|
|
50
|
-
* meta如果为null,说明用户未登录,不显示
|
|
51
|
-
* meta如果为0,说明用户已经看过,不显示,同时比较config的值,如果config的值大于local,更新local为config的值和meta为1
|
|
52
|
-
* meta如果为1,说明用户未看过,显示
|
|
73
|
+
* 只在点击入口后记录本地版本;仅看到气泡不算已处理。
|
|
53
74
|
*/
|
|
54
75
|
let meta = null;
|
|
55
76
|
if (User.isLogin()) {
|
|
56
77
|
meta = await getUserMeta({ key: "mall_pop" });
|
|
57
78
|
}
|
|
58
|
-
let config = await getConfig({ key: "mall" });
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (val) {
|
|
64
|
-
this.pop = true;
|
|
65
|
-
localStorage.setItem("mall_pop", config.val);
|
|
66
|
-
setUserMeta("mall_pop", { val: 1 });
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
if (meta == 1) {
|
|
70
|
-
this.pop = true;
|
|
71
|
-
|
|
72
|
-
localStorage.setItem("mall_pop", config.val);
|
|
73
|
-
} else {
|
|
74
|
-
const local = localStorage.getItem("mall_pop");
|
|
79
|
+
let config = this.config || (this.configManaged ? null : await getConfig({ key: "mall" }));
|
|
80
|
+
if (!config) return;
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
this.popValue = config.val;
|
|
75
83
|
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
this.pop = this.shouldShowPop(meta, config.val);
|
|
85
|
+
},
|
|
86
|
+
shouldShowPop(meta, value) {
|
|
87
|
+
if (!~~value) return false;
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
const local = localStorage.getItem("mall_pop");
|
|
90
|
+
if (String(local) === String(value)) return false;
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
if (meta == null || meta == 1) return true;
|
|
93
|
+
return ~~value > ~~local;
|
|
94
|
+
},
|
|
95
|
+
markPopRead() {
|
|
96
|
+
if (!this.pop || this.popValue == null) return;
|
|
97
|
+
localStorage.setItem("mall_pop", this.popValue);
|
|
98
|
+
this.pop = false;
|
|
85
99
|
},
|
|
86
100
|
},
|
|
87
101
|
};
|
|
@@ -111,11 +125,11 @@ export default {
|
|
|
111
125
|
color: #fff;
|
|
112
126
|
background-image: linear-gradient(#fcd14f, #d7a20b);
|
|
113
127
|
background-clip: padding-box;
|
|
114
|
-
border: 2px solid
|
|
128
|
+
border: 2px solid @header-bg;
|
|
115
129
|
border-radius: 50%;
|
|
116
130
|
position: absolute;
|
|
117
131
|
right: -5px;
|
|
118
|
-
top: -
|
|
132
|
+
top: -4px;
|
|
119
133
|
z-index: 1;
|
|
120
134
|
}
|
|
121
135
|
}
|
package/src/header/user.vue
CHANGED
|
@@ -8,16 +8,22 @@
|
|
|
8
8
|
<publish />
|
|
9
9
|
|
|
10
10
|
<!-- vip -->
|
|
11
|
-
<vip />
|
|
11
|
+
<vip :config="headerConfig.vip" :config-loaded="headerConfigLoaded" :config-managed="headerConfigManaged" />
|
|
12
12
|
|
|
13
13
|
<!-- 商城 -->
|
|
14
|
-
<shop />
|
|
14
|
+
<shop :config="headerConfig.mall" :config-loaded="headerConfigLoaded" :config-managed="headerConfigManaged" />
|
|
15
15
|
|
|
16
16
|
<!-- 我的资产 -->
|
|
17
17
|
<asset :asset="asset" />
|
|
18
18
|
|
|
19
19
|
<!-- manage -->
|
|
20
|
-
<manage
|
|
20
|
+
<manage
|
|
21
|
+
:isTeammate="isTeammate"
|
|
22
|
+
:important-notice="headerConfig.important_notice"
|
|
23
|
+
:important-notice-url="headerConfig.important_notice_url"
|
|
24
|
+
:header-config-loaded="headerConfigLoaded"
|
|
25
|
+
:header-config-managed="headerConfigManaged"
|
|
26
|
+
/>
|
|
21
27
|
|
|
22
28
|
<!-- 语言切换 -->
|
|
23
29
|
<lang />
|
|
@@ -77,6 +83,18 @@ export default {
|
|
|
77
83
|
};
|
|
78
84
|
},
|
|
79
85
|
},
|
|
86
|
+
headerConfig: {
|
|
87
|
+
type: Object,
|
|
88
|
+
default: () => ({}),
|
|
89
|
+
},
|
|
90
|
+
headerConfigLoaded: {
|
|
91
|
+
type: Boolean,
|
|
92
|
+
default: true,
|
|
93
|
+
},
|
|
94
|
+
headerConfigManaged: {
|
|
95
|
+
type: Boolean,
|
|
96
|
+
default: false,
|
|
97
|
+
},
|
|
80
98
|
},
|
|
81
99
|
components: {
|
|
82
100
|
message,
|
package/src/header/vip.vue
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
placement="bottom"
|
|
7
7
|
popper-class="c-header-tooltip"
|
|
8
8
|
>
|
|
9
|
-
<a class="u-post u-vip" href="/vip/premium">
|
|
9
|
+
<a class="u-post u-vip" href="/vip/premium" @click="markPopRead">
|
|
10
10
|
<i class="u-icon u-icon-msg">
|
|
11
11
|
<i class="u-pop" style="display: none" v-show="pop"></i>
|
|
12
12
|
<!-- <vipIcon class="u-add" /> -->
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
25
|
<script>
|
|
26
|
-
import { getConfig, getUserMeta
|
|
26
|
+
import { getConfig, getUserMeta } from "../../service/cms";
|
|
27
27
|
import User from "@jx3box/jx3box-common/js/user";
|
|
28
28
|
import i18nMixin from "../../i18n/mixin";
|
|
29
29
|
// import vipIcon from "@/assets/img/components/common/header/vip.svg";
|
|
@@ -36,48 +36,66 @@ export default {
|
|
|
36
36
|
data: function () {
|
|
37
37
|
return {
|
|
38
38
|
pop: false,
|
|
39
|
+
initialized: false,
|
|
40
|
+
popValue: "",
|
|
39
41
|
};
|
|
40
42
|
},
|
|
43
|
+
props: {
|
|
44
|
+
config: {
|
|
45
|
+
type: Object,
|
|
46
|
+
default: null,
|
|
47
|
+
},
|
|
48
|
+
configLoaded: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
default: true,
|
|
51
|
+
},
|
|
52
|
+
configManaged: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
watch: {
|
|
58
|
+
configLoaded: function (val) {
|
|
59
|
+
if (val) {
|
|
60
|
+
this.init();
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
41
64
|
mounted() {
|
|
42
|
-
this.
|
|
65
|
+
if (this.configLoaded) {
|
|
66
|
+
this.init();
|
|
67
|
+
}
|
|
43
68
|
},
|
|
44
69
|
methods: {
|
|
45
70
|
async init() {
|
|
71
|
+
if (this.initialized) return;
|
|
46
72
|
/**
|
|
47
|
-
*
|
|
48
|
-
* 2. 用户第二次进入页面,此时meta有记录,但是用户并未进入会员中心,则显示
|
|
73
|
+
* 只在点击入口后记录本地版本;仅看到气泡不算已处理。
|
|
49
74
|
*/
|
|
50
75
|
let meta = null;
|
|
51
76
|
if (User.isLogin()) {
|
|
52
77
|
meta = await getUserMeta({ key: "vip_pop" });
|
|
53
78
|
}
|
|
54
|
-
let config = await getConfig({ key: "vip" });
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (val) {
|
|
60
|
-
this.pop = true;
|
|
61
|
-
localStorage.setItem("vip_pop", config.val);
|
|
62
|
-
setUserMeta("vip_pop", { val: 1 });
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
if (meta == 1) {
|
|
66
|
-
this.pop = true;
|
|
67
|
-
|
|
68
|
-
localStorage.setItem("vip_pop", config.val);
|
|
69
|
-
} else {
|
|
70
|
-
const local = localStorage.getItem("vip_pop");
|
|
79
|
+
let config = this.config || (this.configManaged ? null : await getConfig({ key: "vip" }));
|
|
80
|
+
if (!config) return;
|
|
81
|
+
this.initialized = true;
|
|
82
|
+
this.popValue = config.val;
|
|
71
83
|
|
|
72
|
-
|
|
73
|
-
|
|
84
|
+
this.pop = this.shouldShowPop(meta, config.val);
|
|
85
|
+
},
|
|
86
|
+
shouldShowPop(meta, value) {
|
|
87
|
+
if (!~~value) return false;
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
const local = localStorage.getItem("vip_pop");
|
|
90
|
+
if (String(local) === String(value)) return false;
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
if (meta == null || meta == 1) return true;
|
|
93
|
+
return ~~value > ~~local;
|
|
94
|
+
},
|
|
95
|
+
markPopRead() {
|
|
96
|
+
if (!this.pop || this.popValue == null) return;
|
|
97
|
+
localStorage.setItem("vip_pop", this.popValue);
|
|
98
|
+
this.pop = false;
|
|
81
99
|
},
|
|
82
100
|
},
|
|
83
101
|
};
|
|
@@ -99,11 +117,11 @@ export default {
|
|
|
99
117
|
color: #fff;
|
|
100
118
|
background-image: linear-gradient(#fcd14f, #d7a20b);
|
|
101
119
|
background-clip: padding-box;
|
|
102
|
-
border: 2px solid
|
|
120
|
+
border: 2px solid @header-bg;
|
|
103
121
|
border-radius: 50%;
|
|
104
122
|
position: absolute;
|
|
105
123
|
right: -5px;
|
|
106
|
-
top: -
|
|
124
|
+
top: -4px;
|
|
107
125
|
z-index: 1;
|
|
108
126
|
}
|
|
109
127
|
.u-vip {
|