@epxing/core 5.3.0-beta.2 → 5.3.0-beta.4
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/API.md +715 -714
- package/README.md +3 -1
- package/dist/.cache/route-chart-release/frontend/frame/public/js/ecStat.min.js +4 -0
- package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts-wordcloud.min.js +6 -0
- package/dist/.cache/route-chart-release/frontend/frame/public/js/echarts.min.js +49 -0
- package/dist/_virtual/_plugin-vue_export-helper.js +10 -0
- package/dist/_virtual/jsencrypt.min.js +1 -1
- package/dist/assets/role_select-DTqRlFNz.webp +0 -0
- package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/jsencrypt@3.5.4/_vendor/jsencrypt/bin/jsencrypt.min.js +1 -1
- package/dist/src/RootApp.js +54 -0
- package/dist/src/api/createHttp.js +6 -0
- package/dist/src/app-ui-settings.js +49 -0
- package/dist/src/axios.js +10 -15
- package/dist/src/chart-runtime-assets.js +19 -0
- package/dist/src/chart-runtime-sources.js +8 -0
- package/dist/src/create-app.js +3 -6
- package/dist/src/index.js +2 -0
- package/dist/src/loadComponent.js +2 -2
- package/dist/src/menu-route.js +11 -0
- package/dist/src/mixins/component.js +3 -0
- package/dist/src/navigation-context.js +47 -0
- package/dist/src/navigation.js +43 -12
- package/dist/src/package-install.js +3 -2
- package/dist/src/plugins/element-plus.js +8 -8
- package/dist/src/public-api.js +4 -2
- package/dist/src/route-components.js +9 -0
- package/dist/src/router/config.js +7 -8
- package/dist/src/runtime-services.js +1 -1
- package/dist/src/runtime-utils/meta.js +3 -0
- package/dist/src/runtime-utils/pdf.viewer.js +31 -15
- package/dist/src/runtime-utils/util.js +28 -5
- package/dist/src/shared-packages.js +1 -3
- package/dist/src/store/modules/list.js +25 -3
- package/dist/src/store/modules/meta.js +49 -7
- package/dist/src/style-presets.js +4 -4
- package/dist/src/ui-settings.js +90 -18
- package/dist/src/views/App.js +48 -0
- package/dist/src/views/AppRoleSelect.js +233 -0
- package/dist/src/views/Error.js +210 -0
- package/dist/src/views/Module.js +173 -0
- package/dist/style.css +251 -0
- package/package.json +4 -4
- package/src/RootApp.vue +75 -0
- package/src/api/createHttp.js +7 -0
- package/src/api/index.js +47 -47
- package/src/app-ui-settings.js +31 -0
- package/src/axios.js +311 -318
- package/src/chart-runtime-assets.js +13 -0
- package/src/chart-runtime-sources.js +5 -0
- package/src/create-app.js +3 -7
- package/src/index.js +28 -28
- package/src/install.js +157 -157
- package/src/loadComponent.js +187 -187
- package/src/loadLibrary.js +99 -99
- package/src/menu-route.js +12 -1
- package/src/mixins/component.js +1464 -1461
- package/src/navigation-context.js +49 -0
- package/src/navigation.js +208 -176
- package/src/package-install.js +2 -1
- package/src/plugins/element-plus.js +302 -302
- package/src/plugins/frame.js +178 -178
- package/src/public-api.js +1 -0
- package/src/route-components.js +7 -0
- package/src/router/config.js +861 -864
- package/src/router/public.js +1 -1
- package/src/runtime-services.js +1 -1
- package/src/runtime-utils/fieldFactory.js +179 -179
- package/src/runtime-utils/meta.js +399 -378
- package/src/runtime-utils/pdf.viewer.js +672 -644
- package/src/runtime-utils/util.js +1359 -1330
- package/src/shared-packages.js +1 -3
- package/src/store/modules/config.js +351 -351
- package/src/store/modules/list.js +30 -3
- package/src/store/modules/locale.js +236 -236
- package/src/store/modules/meta.js +314 -265
- package/src/style-presets.js +4 -4
- package/src/ui-settings.js +84 -23
- package/src/views/App.vue +53 -0
- package/src/views/AppRoleSelect.vue +334 -0
- package/src/views/Error.vue +63 -0
- package/src/views/Module.vue +210 -0
- package/src/xss.js +26 -26
- package/types/app.d.ts +10 -10
- package/types/axios.d.ts +1 -1
- package/types/component-mixin.d.ts +1 -1
- package/types/field-factory.d.ts +1 -1
- package/types/global-mixin.d.ts +1 -1
- package/types/http.d.ts +1 -1
- package/types/index.d.ts +3 -0
- package/types/meta.d.ts +1 -1
- package/types/navigation.d.ts +21 -21
- package/types/pdf-viewer.d.ts +1 -1
- package/types/runtime.d.ts +13 -13
- package/types/store.d.ts +1 -1
- package/types/util.d.ts +2 -2
- /package/dist/{build → .cache/route-chart-release/frontend/frame/build}/stubs/empty-components.js +0 -0
- /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-icons.names.js +0 -0
- /package/dist/{src → .cache/route-chart-release/frontend/frame/src}/element-plus.lazy.js +0 -0
- /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/en.js +0 -0
- /package/dist/{_vendor → frontend/frame/_vendor}/.pnpm/element-plus@2.14.3_vue@3.5.39_typescript@5.9.3_/_vendor/element-plus/dist/locale/zh-cn.js +0 -0
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 获取页面配置
|
|
3
3
|
*/
|
|
4
|
-
import { markRaw } from 'vue'
|
|
4
|
+
import { markRaw, reactive } from 'vue'
|
|
5
5
|
import http from '@epxing/core/src/api'
|
|
6
6
|
import { ADD_LIST, ADD_OBJECT, ADD_OBJECT_COUNT, ADD_TAG } from '../actions'
|
|
7
7
|
import { prune, touch, touchKeys } from '@epxing/core/src/store/lru'
|
|
8
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
|
|
9
|
+
// 缓存逐 key 更新,getter 从当前 store 的 state 读取,兼容独立 store 和状态替换。
|
|
10
|
+
const lists = reactive({})
|
|
11
|
+
// 每个 key 一个响应式信号,读取方只订阅自己的 key
|
|
12
|
+
const listSignals = new Map()
|
|
13
|
+
const signalOf = (key) => {
|
|
14
|
+
let signal = listSignals.get(key)
|
|
15
|
+
if (!signal) {
|
|
16
|
+
signal = reactive({ version: 0 })
|
|
17
|
+
listSignals.set(key, signal)
|
|
18
|
+
}
|
|
19
|
+
return signal
|
|
20
|
+
}
|
|
8
21
|
|
|
9
22
|
const formatData = (array, values = [], list) => {
|
|
10
23
|
return array.map(item => {
|
|
@@ -67,7 +80,7 @@ const markPayloadRaw = data => {
|
|
|
67
80
|
|
|
68
81
|
export default {
|
|
69
82
|
state: {
|
|
70
|
-
lists
|
|
83
|
+
lists,
|
|
71
84
|
treeList: {},
|
|
72
85
|
objects: {},
|
|
73
86
|
object_counts: {},
|
|
@@ -76,6 +89,9 @@ export default {
|
|
|
76
89
|
},
|
|
77
90
|
getters: {
|
|
78
91
|
getCodeById: (state) => (id) => {
|
|
92
|
+
// 只订阅这一个 key 的信号,其它代码表的到达不会触发本组件重渲染
|
|
93
|
+
// eslint-disable-next-line no-unused-expressions
|
|
94
|
+
signalOf(id).version
|
|
79
95
|
const list = state.lists[id]
|
|
80
96
|
if (list) {
|
|
81
97
|
touch('lists', id)
|
|
@@ -101,12 +117,23 @@ export default {
|
|
|
101
117
|
getTagListByMetaId: (state) => (id) => state.tags[id]
|
|
102
118
|
},
|
|
103
119
|
mutations: {
|
|
120
|
+
// 逐 key 写入并只通知该 key 的信号,不整体替换 lists
|
|
104
121
|
[ADD_LIST] (state, data) {
|
|
105
122
|
touchKeys('lists', Object.keys(data))
|
|
106
|
-
|
|
123
|
+
const rawData = markPayloadRaw(data)
|
|
124
|
+
for (const key in rawData) {
|
|
125
|
+
state.lists[key] = rawData[key]
|
|
126
|
+
signalOf(key).version++
|
|
127
|
+
}
|
|
128
|
+
// 容量淘汰:先在副本上算出保留集合,再原地删除被淘汰的 key
|
|
129
|
+
const keep = prune('lists', { ...state.lists }, LIMITS.lists)
|
|
130
|
+
for (const key in state.lists) {
|
|
131
|
+
if (!hasOwn(keep, key)) delete state.lists[key]
|
|
132
|
+
}
|
|
107
133
|
},
|
|
108
134
|
'DELETE_LIST' (state, payload) {
|
|
109
135
|
delete state.lists[payload.key]
|
|
136
|
+
signalOf(payload.key).version++
|
|
110
137
|
},
|
|
111
138
|
'ADD_TREE_LIST' (state, data) {
|
|
112
139
|
touchKeys('treeList', Object.keys(data))
|
|
@@ -1,236 +1,236 @@
|
|
|
1
|
-
import http from '@epxing/core/src/api'
|
|
2
|
-
import util from '@epxing/core/src/runtime-utils/util'
|
|
3
|
-
import storage from '@epxing/utils/src/storage'
|
|
4
|
-
import { applyLibraryLocale } from '@epxing/core/src/plugins/library-locale'
|
|
5
|
-
import {
|
|
6
|
-
configuredLocales,
|
|
7
|
-
createRuntimeMessages,
|
|
8
|
-
createUiMessageCatalog,
|
|
9
|
-
languagePreferenceKey,
|
|
10
|
-
resolveCurrentLocale
|
|
11
|
-
} from '@epxing/utils/src/locale'
|
|
12
|
-
|
|
13
|
-
const CACHE_PREFIX = 'epx-i18n-bundle'
|
|
14
|
-
const UI_SCOPE = 'UI'
|
|
15
|
-
|
|
16
|
-
function bundleCacheKey ({ clientCode, systemId, locale }) {
|
|
17
|
-
return [CACHE_PREFIX, clientCode, systemId, locale, UI_SCOPE].join(':')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function readBundleCache (context) {
|
|
21
|
-
try {
|
|
22
|
-
const value = storage.get(bundleCacheKey(context))
|
|
23
|
-
if (!value) return null
|
|
24
|
-
const bundle = JSON.parse(value)
|
|
25
|
-
if (!bundle || typeof bundle.content !== 'object' || Array.isArray(bundle.content)) return null
|
|
26
|
-
return bundle
|
|
27
|
-
} catch (error) {
|
|
28
|
-
return null
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function writeBundleCache (context, bundle) {
|
|
33
|
-
try {
|
|
34
|
-
storage.set(bundleCacheKey(context), JSON.stringify({
|
|
35
|
-
etag: bundle.etag || '',
|
|
36
|
-
content: bundle.content || {}
|
|
37
|
-
}))
|
|
38
|
-
} catch (error) {
|
|
39
|
-
// 浏览器禁用或空间不足时退化为当前页面内存缓存。
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function removeBundleCache (context) {
|
|
44
|
-
try {
|
|
45
|
-
storage.remove(bundleCacheKey(context))
|
|
46
|
-
} catch (error) {
|
|
47
|
-
// localStorage 不可用不影响语言回退。
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function decodeMessages (data) {
|
|
52
|
-
return (data.entitys || []).map(item => {
|
|
53
|
-
if (data.encrypt !== '1') return item
|
|
54
|
-
return {
|
|
55
|
-
...item,
|
|
56
|
-
NAME: util.utf8to16(window.atob(item.NAME)),
|
|
57
|
-
CONTENT: util.utf8to16(window.atob(item.CONTENT))
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function loadSourceCatalog (systemId, defaultLocale) {
|
|
63
|
-
return http.list({
|
|
64
|
-
entityId: 'FM_MESSAGE',
|
|
65
|
-
scopeParams: JSON.stringify([
|
|
66
|
-
{
|
|
67
|
-
field: 'TYPE',
|
|
68
|
-
values: 'page'
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
lb: '(',
|
|
72
|
-
field: 'SCOPE',
|
|
73
|
-
values: 'frame',
|
|
74
|
-
link: 'or'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
field: 'SYSTEM',
|
|
78
|
-
values: systemId,
|
|
79
|
-
rb: ')'
|
|
80
|
-
}
|
|
81
|
-
])
|
|
82
|
-
}, {
|
|
83
|
-
headers: {
|
|
84
|
-
Language: defaultLocale
|
|
85
|
-
}
|
|
86
|
-
}).then(data => createUiMessageCatalog(decodeMessages(data), systemId))
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function requestPublishedBundle (context, etag) {
|
|
90
|
-
return http.$request({
|
|
91
|
-
action: 'publishedTranslations',
|
|
92
|
-
entityId: 'FM_I18N_RESOURCE',
|
|
93
|
-
localeCode: context.locale,
|
|
94
|
-
scope: UI_SCOPE
|
|
95
|
-
}, {
|
|
96
|
-
method: 'get',
|
|
97
|
-
headers: etag ? { 'If-None-Match': etag } : {}
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function acceptPublishedBundle (context, bundle) {
|
|
102
|
-
if (!bundle || !bundle.published) {
|
|
103
|
-
removeBundleCache(context)
|
|
104
|
-
return { published: false, etag: '', content: {} }
|
|
105
|
-
}
|
|
106
|
-
writeBundleCache(context, bundle)
|
|
107
|
-
return bundle
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function loadPublishedBundle (context) {
|
|
111
|
-
const cached = readBundleCache(context)
|
|
112
|
-
return requestPublishedBundle(context, cached && cached.etag).then(bundle => {
|
|
113
|
-
if (!bundle || !bundle.notModified) return acceptPublishedBundle(context, bundle)
|
|
114
|
-
const content = cached && cached.content
|
|
115
|
-
if (content && Object.keys(content).length > 0) {
|
|
116
|
-
return { published: true, etag: cached.etag, content }
|
|
117
|
-
}
|
|
118
|
-
// 服务端确认未变更,但本地缓存已丢失或为空:丢掉 ETag 重新拉完整包,
|
|
119
|
-
// 否则会把空内容当成已发布语言包,整站文案回退默认语言。
|
|
120
|
-
removeBundleCache(context)
|
|
121
|
-
return requestPublishedBundle(context, null)
|
|
122
|
-
.then(full => acceptPublishedBundle(context, full))
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function applyMessages (locale, messages, fallbackMessages) {
|
|
127
|
-
const libraryLocale = applyLibraryLocale(locale)
|
|
128
|
-
if (globalThis.i18n) {
|
|
129
|
-
globalThis.i18n.setLocale(locale)
|
|
130
|
-
globalThis.i18n.setMessage(messages, fallbackMessages)
|
|
131
|
-
}
|
|
132
|
-
return libraryLocale
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export default {
|
|
136
|
-
state: {
|
|
137
|
-
enabled: false,
|
|
138
|
-
clientCode: '',
|
|
139
|
-
systemId: '',
|
|
140
|
-
currentLocale: 'zh-CN',
|
|
141
|
-
defaultLocale: 'zh-CN',
|
|
142
|
-
supportedLocales: ['zh-CN'],
|
|
143
|
-
libraryLocale: 'zh-CN',
|
|
144
|
-
messages: {},
|
|
145
|
-
fallbackMessages: {},
|
|
146
|
-
etag: '',
|
|
147
|
-
published: false,
|
|
148
|
-
loaded: false,
|
|
149
|
-
loading: false
|
|
150
|
-
},
|
|
151
|
-
mutations: {
|
|
152
|
-
SET_LOCALE_CONTEXT (state, context) {
|
|
153
|
-
Object.assign(state, context)
|
|
154
|
-
},
|
|
155
|
-
SET_LOCALE_LOADING (state, loading) {
|
|
156
|
-
state.loading = loading
|
|
157
|
-
},
|
|
158
|
-
SET_LOCALE_MESSAGES (state, payload) {
|
|
159
|
-
state.messages = payload.messages
|
|
160
|
-
state.fallbackMessages = payload.fallbackMessages
|
|
161
|
-
state.etag = payload.etag || ''
|
|
162
|
-
state.published = payload.published
|
|
163
|
-
state.libraryLocale = payload.libraryLocale
|
|
164
|
-
state.loaded = true
|
|
165
|
-
state.loading = false
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
actions: {
|
|
169
|
-
async INITIALIZE_LOCALE ({ state, commit, rootState }, payload = {}) {
|
|
170
|
-
const clientConfig = rootState.config.clientConfig || {}
|
|
171
|
-
const userInfo = rootState.user.userInfo || {}
|
|
172
|
-
const appConfig = rootState.config.appConfig || {}
|
|
173
|
-
const configuration = configuredLocales(clientConfig)
|
|
174
|
-
const clientCode = clientConfig.CODE || ''
|
|
175
|
-
const systemId = payload.systemId || appConfig['SYSTEM|ID'] || appConfig.SYSTEM || ''
|
|
176
|
-
const currentLocale = resolveCurrentLocale(clientConfig, userInfo)
|
|
177
|
-
const context = {
|
|
178
|
-
enabled: configuration.enabled,
|
|
179
|
-
clientCode,
|
|
180
|
-
systemId,
|
|
181
|
-
currentLocale,
|
|
182
|
-
defaultLocale: configuration.defaultLocale,
|
|
183
|
-
supportedLocales: configuration.supportedLocales
|
|
184
|
-
}
|
|
185
|
-
if (!payload.force && state.loaded && state.clientCode === clientCode && state.systemId === systemId && state.currentLocale === currentLocale) {
|
|
186
|
-
return state
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
commit('SET_LOCALE_CONTEXT', context)
|
|
190
|
-
commit('SET_LOCALE_LOADING', true)
|
|
191
|
-
let catalog = { fallbackMessages: {}, aliases: {} }
|
|
192
|
-
try {
|
|
193
|
-
catalog = await loadSourceCatalog(systemId, configuration.defaultLocale)
|
|
194
|
-
} catch (error) {
|
|
195
|
-
// 源消息失败时仍允许页面启动,$t 最终回退到调用方传入的 Key。
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
let publishedBundle = { published: false, etag: '', content: {} }
|
|
199
|
-
// 登录、注册页也要加载界面文案包:这些页面整页文案都走 $t,拿不到包就整页回退默认语言。
|
|
200
|
-
// 服务端对匿名请求只放行 UI 范围,配置资源包仍要求登录。
|
|
201
|
-
const usePublishedBundle = configuration.enabled && currentLocale !== configuration.defaultLocale
|
|
202
|
-
if (usePublishedBundle) {
|
|
203
|
-
try {
|
|
204
|
-
publishedBundle = await loadPublishedBundle({ clientCode, systemId, locale: currentLocale })
|
|
205
|
-
} catch (error) {
|
|
206
|
-
// 已发布包暂时不可用时回退默认语言,不让应用启动失败。
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const messages = publishedBundle.published
|
|
211
|
-
? createRuntimeMessages(publishedBundle.content, catalog)
|
|
212
|
-
: catalog.fallbackMessages
|
|
213
|
-
const libraryLocale = applyMessages(currentLocale, messages, catalog.fallbackMessages)
|
|
214
|
-
commit('SET_LOCALE_MESSAGES', {
|
|
215
|
-
messages,
|
|
216
|
-
fallbackMessages: catalog.fallbackMessages,
|
|
217
|
-
etag: publishedBundle.etag,
|
|
218
|
-
published: publishedBundle.published,
|
|
219
|
-
libraryLocale
|
|
220
|
-
})
|
|
221
|
-
return state
|
|
222
|
-
},
|
|
223
|
-
CHANGE_LOCALE ({ commit, rootState }, value) {
|
|
224
|
-
const locale = typeof value === 'string' ? value : value.locale
|
|
225
|
-
const clientConfig = rootState.config.clientConfig || {}
|
|
226
|
-
const configuration = configuredLocales(clientConfig)
|
|
227
|
-
const selected = configuration.supportedLocales.find(item => item.toLowerCase() === String(locale || '').toLowerCase())
|
|
228
|
-
if (!selected) throw new Error(`当前客户未启用语言:${locale}`)
|
|
229
|
-
const clientCode = clientConfig.CODE || ''
|
|
230
|
-
storage.set(languagePreferenceKey(clientCode), selected)
|
|
231
|
-
const libraryLocale = applyLibraryLocale(selected)
|
|
232
|
-
commit('SET_LOCALE_CONTEXT', { clientCode, currentLocale: selected, libraryLocale })
|
|
233
|
-
return selected
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
1
|
+
import http from '@epxing/core/src/api'
|
|
2
|
+
import util from '@epxing/core/src/runtime-utils/util'
|
|
3
|
+
import storage from '@epxing/utils/src/storage'
|
|
4
|
+
import { applyLibraryLocale } from '@epxing/core/src/plugins/library-locale'
|
|
5
|
+
import {
|
|
6
|
+
configuredLocales,
|
|
7
|
+
createRuntimeMessages,
|
|
8
|
+
createUiMessageCatalog,
|
|
9
|
+
languagePreferenceKey,
|
|
10
|
+
resolveCurrentLocale
|
|
11
|
+
} from '@epxing/utils/src/locale'
|
|
12
|
+
|
|
13
|
+
const CACHE_PREFIX = 'epx-i18n-bundle'
|
|
14
|
+
const UI_SCOPE = 'UI'
|
|
15
|
+
|
|
16
|
+
function bundleCacheKey ({ clientCode, systemId, locale }) {
|
|
17
|
+
return [CACHE_PREFIX, clientCode, systemId, locale, UI_SCOPE].join(':')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readBundleCache (context) {
|
|
21
|
+
try {
|
|
22
|
+
const value = storage.get(bundleCacheKey(context))
|
|
23
|
+
if (!value) return null
|
|
24
|
+
const bundle = JSON.parse(value)
|
|
25
|
+
if (!bundle || typeof bundle.content !== 'object' || Array.isArray(bundle.content)) return null
|
|
26
|
+
return bundle
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function writeBundleCache (context, bundle) {
|
|
33
|
+
try {
|
|
34
|
+
storage.set(bundleCacheKey(context), JSON.stringify({
|
|
35
|
+
etag: bundle.etag || '',
|
|
36
|
+
content: bundle.content || {}
|
|
37
|
+
}))
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// 浏览器禁用或空间不足时退化为当前页面内存缓存。
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function removeBundleCache (context) {
|
|
44
|
+
try {
|
|
45
|
+
storage.remove(bundleCacheKey(context))
|
|
46
|
+
} catch (error) {
|
|
47
|
+
// localStorage 不可用不影响语言回退。
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function decodeMessages (data) {
|
|
52
|
+
return (data.entitys || []).map(item => {
|
|
53
|
+
if (data.encrypt !== '1') return item
|
|
54
|
+
return {
|
|
55
|
+
...item,
|
|
56
|
+
NAME: util.utf8to16(window.atob(item.NAME)),
|
|
57
|
+
CONTENT: util.utf8to16(window.atob(item.CONTENT))
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadSourceCatalog (systemId, defaultLocale) {
|
|
63
|
+
return http.list({
|
|
64
|
+
entityId: 'FM_MESSAGE',
|
|
65
|
+
scopeParams: JSON.stringify([
|
|
66
|
+
{
|
|
67
|
+
field: 'TYPE',
|
|
68
|
+
values: 'page'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
lb: '(',
|
|
72
|
+
field: 'SCOPE',
|
|
73
|
+
values: 'frame',
|
|
74
|
+
link: 'or'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
field: 'SYSTEM',
|
|
78
|
+
values: systemId,
|
|
79
|
+
rb: ')'
|
|
80
|
+
}
|
|
81
|
+
])
|
|
82
|
+
}, {
|
|
83
|
+
headers: {
|
|
84
|
+
Language: defaultLocale
|
|
85
|
+
}
|
|
86
|
+
}).then(data => createUiMessageCatalog(decodeMessages(data), systemId))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function requestPublishedBundle (context, etag) {
|
|
90
|
+
return http.$request({
|
|
91
|
+
action: 'publishedTranslations',
|
|
92
|
+
entityId: 'FM_I18N_RESOURCE',
|
|
93
|
+
localeCode: context.locale,
|
|
94
|
+
scope: UI_SCOPE
|
|
95
|
+
}, {
|
|
96
|
+
method: 'get',
|
|
97
|
+
headers: etag ? { 'If-None-Match': etag } : {}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function acceptPublishedBundle (context, bundle) {
|
|
102
|
+
if (!bundle || !bundle.published) {
|
|
103
|
+
removeBundleCache(context)
|
|
104
|
+
return { published: false, etag: '', content: {} }
|
|
105
|
+
}
|
|
106
|
+
writeBundleCache(context, bundle)
|
|
107
|
+
return bundle
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function loadPublishedBundle (context) {
|
|
111
|
+
const cached = readBundleCache(context)
|
|
112
|
+
return requestPublishedBundle(context, cached && cached.etag).then(bundle => {
|
|
113
|
+
if (!bundle || !bundle.notModified) return acceptPublishedBundle(context, bundle)
|
|
114
|
+
const content = cached && cached.content
|
|
115
|
+
if (content && Object.keys(content).length > 0) {
|
|
116
|
+
return { published: true, etag: cached.etag, content }
|
|
117
|
+
}
|
|
118
|
+
// 服务端确认未变更,但本地缓存已丢失或为空:丢掉 ETag 重新拉完整包,
|
|
119
|
+
// 否则会把空内容当成已发布语言包,整站文案回退默认语言。
|
|
120
|
+
removeBundleCache(context)
|
|
121
|
+
return requestPublishedBundle(context, null)
|
|
122
|
+
.then(full => acceptPublishedBundle(context, full))
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function applyMessages (locale, messages, fallbackMessages) {
|
|
127
|
+
const libraryLocale = applyLibraryLocale(locale)
|
|
128
|
+
if (globalThis.i18n) {
|
|
129
|
+
globalThis.i18n.setLocale(locale)
|
|
130
|
+
globalThis.i18n.setMessage(messages, fallbackMessages)
|
|
131
|
+
}
|
|
132
|
+
return libraryLocale
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default {
|
|
136
|
+
state: {
|
|
137
|
+
enabled: false,
|
|
138
|
+
clientCode: '',
|
|
139
|
+
systemId: '',
|
|
140
|
+
currentLocale: 'zh-CN',
|
|
141
|
+
defaultLocale: 'zh-CN',
|
|
142
|
+
supportedLocales: ['zh-CN'],
|
|
143
|
+
libraryLocale: 'zh-CN',
|
|
144
|
+
messages: {},
|
|
145
|
+
fallbackMessages: {},
|
|
146
|
+
etag: '',
|
|
147
|
+
published: false,
|
|
148
|
+
loaded: false,
|
|
149
|
+
loading: false
|
|
150
|
+
},
|
|
151
|
+
mutations: {
|
|
152
|
+
SET_LOCALE_CONTEXT (state, context) {
|
|
153
|
+
Object.assign(state, context)
|
|
154
|
+
},
|
|
155
|
+
SET_LOCALE_LOADING (state, loading) {
|
|
156
|
+
state.loading = loading
|
|
157
|
+
},
|
|
158
|
+
SET_LOCALE_MESSAGES (state, payload) {
|
|
159
|
+
state.messages = payload.messages
|
|
160
|
+
state.fallbackMessages = payload.fallbackMessages
|
|
161
|
+
state.etag = payload.etag || ''
|
|
162
|
+
state.published = payload.published
|
|
163
|
+
state.libraryLocale = payload.libraryLocale
|
|
164
|
+
state.loaded = true
|
|
165
|
+
state.loading = false
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
actions: {
|
|
169
|
+
async INITIALIZE_LOCALE ({ state, commit, rootState }, payload = {}) {
|
|
170
|
+
const clientConfig = rootState.config.clientConfig || {}
|
|
171
|
+
const userInfo = rootState.user.userInfo || {}
|
|
172
|
+
const appConfig = rootState.config.appConfig || {}
|
|
173
|
+
const configuration = configuredLocales(clientConfig)
|
|
174
|
+
const clientCode = clientConfig.CODE || ''
|
|
175
|
+
const systemId = payload.systemId || appConfig['SYSTEM|ID'] || appConfig.SYSTEM || ''
|
|
176
|
+
const currentLocale = resolveCurrentLocale(clientConfig, userInfo)
|
|
177
|
+
const context = {
|
|
178
|
+
enabled: configuration.enabled,
|
|
179
|
+
clientCode,
|
|
180
|
+
systemId,
|
|
181
|
+
currentLocale,
|
|
182
|
+
defaultLocale: configuration.defaultLocale,
|
|
183
|
+
supportedLocales: configuration.supportedLocales
|
|
184
|
+
}
|
|
185
|
+
if (!payload.force && state.loaded && state.clientCode === clientCode && state.systemId === systemId && state.currentLocale === currentLocale) {
|
|
186
|
+
return state
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
commit('SET_LOCALE_CONTEXT', context)
|
|
190
|
+
commit('SET_LOCALE_LOADING', true)
|
|
191
|
+
let catalog = { fallbackMessages: {}, aliases: {} }
|
|
192
|
+
try {
|
|
193
|
+
catalog = await loadSourceCatalog(systemId, configuration.defaultLocale)
|
|
194
|
+
} catch (error) {
|
|
195
|
+
// 源消息失败时仍允许页面启动,$t 最终回退到调用方传入的 Key。
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let publishedBundle = { published: false, etag: '', content: {} }
|
|
199
|
+
// 登录、注册页也要加载界面文案包:这些页面整页文案都走 $t,拿不到包就整页回退默认语言。
|
|
200
|
+
// 服务端对匿名请求只放行 UI 范围,配置资源包仍要求登录。
|
|
201
|
+
const usePublishedBundle = configuration.enabled && currentLocale !== configuration.defaultLocale
|
|
202
|
+
if (usePublishedBundle) {
|
|
203
|
+
try {
|
|
204
|
+
publishedBundle = await loadPublishedBundle({ clientCode, systemId, locale: currentLocale })
|
|
205
|
+
} catch (error) {
|
|
206
|
+
// 已发布包暂时不可用时回退默认语言,不让应用启动失败。
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const messages = publishedBundle.published
|
|
211
|
+
? createRuntimeMessages(publishedBundle.content, catalog)
|
|
212
|
+
: catalog.fallbackMessages
|
|
213
|
+
const libraryLocale = applyMessages(currentLocale, messages, catalog.fallbackMessages)
|
|
214
|
+
commit('SET_LOCALE_MESSAGES', {
|
|
215
|
+
messages,
|
|
216
|
+
fallbackMessages: catalog.fallbackMessages,
|
|
217
|
+
etag: publishedBundle.etag,
|
|
218
|
+
published: publishedBundle.published,
|
|
219
|
+
libraryLocale
|
|
220
|
+
})
|
|
221
|
+
return state
|
|
222
|
+
},
|
|
223
|
+
CHANGE_LOCALE ({ commit, rootState }, value) {
|
|
224
|
+
const locale = typeof value === 'string' ? value : value.locale
|
|
225
|
+
const clientConfig = rootState.config.clientConfig || {}
|
|
226
|
+
const configuration = configuredLocales(clientConfig)
|
|
227
|
+
const selected = configuration.supportedLocales.find(item => item.toLowerCase() === String(locale || '').toLowerCase())
|
|
228
|
+
if (!selected) throw new Error(`当前客户未启用语言:${locale}`)
|
|
229
|
+
const clientCode = clientConfig.CODE || ''
|
|
230
|
+
storage.set(languagePreferenceKey(clientCode), selected)
|
|
231
|
+
const libraryLocale = applyLibraryLocale(selected)
|
|
232
|
+
commit('SET_LOCALE_CONTEXT', { clientCode, currentLocale: selected, libraryLocale })
|
|
233
|
+
return selected
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|