adtec-core-package 3.2.9 → 3.3.1

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": "adtec-core-package",
3
- "version": "3.2.9",
3
+ "version": "3.3.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -102,6 +102,7 @@
102
102
  "smooth-signature": "1.0.15",
103
103
  "socket.io-client": "^2.3.1",
104
104
  "tdesign-vue-next": "1.18.2",
105
+ "ts-md5": "^1.3.1",
105
106
  "uuid": "^11.0.3",
106
107
  "vue": "^3.5.13",
107
108
  "vue-demi": "^0.14.10",
@@ -82,6 +82,28 @@ ElSelect.props.fitInputWidth = {
82
82
  type: Boolean,
83
83
  default: false,
84
84
  }
85
+ const isWujieSubApp =
86
+ typeof window !== 'undefined' && !!(window as Window & { __POWERED_BY_WUJIE__?: boolean }).__POWERED_BY_WUJIE__
87
+ /**
88
+ * 无界 iframe 内 Popper 坐标修正。
89
+ * ElSelect / ElDatePicker 等须保持 teleported 默认 true(vxe 单元格挂 body + popperClass 防 clearEdit),
90
+ * 仅通过 strategy/modifiers 修正偏移,避免各子应用逐页 v-bind。
91
+ */
92
+ const wujiePopperOptions = {
93
+ strategy: 'fixed' as const,
94
+ modifiers: [
95
+ {
96
+ name: 'computeStyles',
97
+ options: { adaptive: false, gpuAcceleration: false },
98
+ },
99
+ ],
100
+ }
101
+ if (isWujieSubApp) {
102
+ ElSelect.props.popperOptions = { type: Object, default: () => wujiePopperOptions }
103
+ ElDatePicker.props.popperOptions = { type: Object, default: () => wujiePopperOptions }
104
+ ElTreeSelect.props.popperOptions = { type: Object, default: () => wujiePopperOptions }
105
+ ElCascader.props.popperOptions = { type: Object, default: () => wujiePopperOptions }
106
+ }
85
107
  ElSelect.props.popperClass = {
86
108
  type: String,
87
109
  default: VXE_IGNORE_CLEAR_POPPER,
@@ -7,6 +7,39 @@ import { storeToRefs } from 'pinia'
7
7
  import { userInfoStore } from '../stores/userInfoStore.ts'
8
8
  import frameworkUtils from '../utils/FrameworkUtils.ts'
9
9
 
10
+ /**
11
+ * 后端在带 orgId 且 Redis 未命中时,可能返回带 parentId 的扁平列表(未 buildTree)。
12
+ * 前端按 parentId 重建 children,恢复领域/项目类型等树形字典。
13
+ */
14
+ const ensureDictTree = (list?: ISysDictDataCacheVo[]): ISysDictDataCacheVo[] => {
15
+ if (!list?.length) return list ?? []
16
+ if (list.some((item) => Array.isArray(item.children) && item.children.length > 0)) {
17
+ return list
18
+ }
19
+ const nodeMap = new Map<string, ISysDictDataCacheVo>()
20
+ list.forEach((item) => {
21
+ if (!item?.id) return
22
+ nodeMap.set(item.id, { ...item, children: undefined })
23
+ })
24
+ if (!nodeMap.size) return list
25
+
26
+ const roots: ISysDictDataCacheVo[] = []
27
+ let linked = 0
28
+ nodeMap.forEach((node) => {
29
+ const parentId = node.parentId
30
+ if (parentId && parentId !== '0' && nodeMap.has(parentId)) {
31
+ const parent = nodeMap.get(parentId)!
32
+ if (!parent.children) parent.children = []
33
+ parent.children.push(node)
34
+ linked += 1
35
+ } else {
36
+ roots.push(node)
37
+ }
38
+ })
39
+ if (!linked) return list
40
+ return roots
41
+ }
42
+
10
43
  export default function useDictHooks(dictTypes?: string[], orgId?: string) {
11
44
  const userInfo = userInfoStore()
12
45
  const dictStores = dictStore()
@@ -19,37 +52,133 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
19
52
  return userInfo.user?.orgId
20
53
  }
21
54
 
55
+ const hasDictList = (list?: ISysDictDataCacheVo[]) => Array.isArray(list) && list.length > 0
56
+
57
+ const hasDictCache = (dictType: string, oid?: string): boolean => {
58
+ if (oid && hasDictList(dictMap.value[`${oid}:${dictType}`])) return true
59
+ if (hasDictList(dictMap.value[dictType])) return true
60
+ return Object.keys(dictMap.value).some(
61
+ (key) => key.endsWith(`:${dictType}`) && hasDictList(dictMap.value[key]),
62
+ )
63
+ }
64
+
65
+ const resolveDictMapKey = (dictType: string, orgId?: string): string | undefined => {
66
+ const tenantOrgId = resolveOrgId()
67
+ const scopedOrgId = orgId ?? tenantOrgId
68
+ if (scopedOrgId && hasDictList(dictMap.value[`${scopedOrgId}:${dictType}`])) {
69
+ return `${scopedOrgId}:${dictType}`
70
+ }
71
+ if (hasDictList(dictMap.value[dictType])) return dictType
72
+ const fallback = Object.keys(dictMap.value).find(
73
+ (key) => key.endsWith(`:${dictType}`) && hasDictList(dictMap.value[key]),
74
+ )
75
+ return fallback
76
+ }
77
+
78
+ const resolveDictDataKey = (dictType: string, orgId?: string): string | undefined => {
79
+ const tenantOrgId = resolveOrgId()
80
+ const scopedOrgId = orgId ?? tenantOrgId
81
+ if (scopedOrgId) {
82
+ const scopedKey = `${scopedOrgId}:${dictType}`
83
+ if (dictDataMap.value[scopedKey] && Object.keys(dictDataMap.value[scopedKey]).length) {
84
+ return scopedKey
85
+ }
86
+ }
87
+ if (dictDataMap.value[dictType] && Object.keys(dictDataMap.value[dictType]).length) {
88
+ return dictType
89
+ }
90
+ return Object.keys(dictDataMap.value).find(
91
+ (key) => key.endsWith(`:${dictType}`) && Object.keys(dictDataMap.value[key]).length > 0,
92
+ )
93
+ }
94
+
95
+ const repairCachedDictTrees = (): boolean => {
96
+ let repaired = false
97
+ for (const key of Object.keys(dictMap.value)) {
98
+ const list = dictMap.value[key]
99
+ if (!hasDictList(list)) continue
100
+ const tree = ensureDictTree(list)
101
+ if (tree !== list && tree.some((item) => item.children?.length)) {
102
+ dictMap.value[key] = tree
103
+ repaired = true
104
+ }
105
+ }
106
+ return repaired
107
+ }
108
+
22
109
  const getDict = async (dictTypes?: string[], orgId?: string): Promise<void> => {
23
110
  try {
24
111
  if (!dictTypes || !dictTypes.length) return
112
+ await userInfo.ensureUserHydrated()
25
113
  const tenantOrgId = resolveOrgId(orgId)
26
- const dictKeySet = new Set(Object.keys(dictMap.value))
27
- let uncachedDictTypes: string[] = []
28
- if (orgId || tenantOrgId) {
29
- const oid = orgId ?? tenantOrgId!
30
- uncachedDictTypes = dictTypes.filter((dictType) => !dictKeySet.has(`${oid}:${dictType}`))
31
- } else {
32
- uncachedDictTypes = dictTypes.filter((dictType) => !dictKeySet.has(dictType))
114
+ // orgId 就绪后清理 session 里「有 key 无数据」的脏缓存,避免误判已命中
115
+ let cacheDirty = false
116
+ if (tenantOrgId) {
117
+ const oid = orgId ?? tenantOrgId
118
+ for (const key of Object.keys(dictMap.value)) {
119
+ if (
120
+ (key.startsWith(`${oid}:`) || !key.includes(':')) &&
121
+ Array.isArray(dictMap.value[key]) &&
122
+ dictMap.value[key].length === 0
123
+ ) {
124
+ delete dictMap.value[key]
125
+ delete dictDataMap.value[key]
126
+ delete dictDefaultValueMap.value[key]
127
+ cacheDirty = true
128
+ }
129
+ }
130
+ }
131
+ // 修复「扁平但含 parentId」的脏缓存(带 orgId 后后端未 buildTree 的历史数据)
132
+ if (repairCachedDictTrees()) {
133
+ cacheDirty = true
33
134
  }
135
+ if (cacheDirty) {
136
+ dictStores.publishToSharedCache()
137
+ }
138
+ const uncachedDictTypes = dictTypes.filter(
139
+ (dictType) => !hasDictCache(dictType, orgId ?? tenantOrgId),
140
+ )
34
141
  if (!uncachedDictTypes.length) return
35
142
  const data = await SysDictCacheApi.batchGetSysDictDataCacheVo(uncachedDictTypes, orgId ?? tenantOrgId)
36
- dictMap.value = { ...dictMap.value, ...data }
37
- const promises = Object.keys(data).map(async (item: string) => {
143
+ const normalized: typeof data = {}
144
+ Object.entries(data).forEach(([key, value]) => {
145
+ if (!value?.length) return
146
+ const tree = ensureDictTree(value)
147
+ normalized[key] = tree
148
+ const colon = key.indexOf(':')
149
+ if (colon > 0) {
150
+ const bare = key.slice(colon + 1)
151
+ if (!normalized[bare]?.length) {
152
+ normalized[bare] = tree
153
+ }
154
+ } else if (tenantOrgId) {
155
+ const scoped = `${tenantOrgId}:${key}`
156
+ if (!normalized[scoped]?.length) {
157
+ normalized[scoped] = tree
158
+ }
159
+ }
160
+ })
161
+ dictMap.value = { ...dictMap.value, ...normalized }
162
+ const nextDataMap = { ...dictDataMap.value }
163
+ const nextDefaultMap = { ...dictDefaultValueMap.value }
164
+ const promises = Object.keys(normalized).map(async (item: string) => {
38
165
  const dataMap: dictDataMapVoType = {}
39
166
  const defaultList: ISysDictDataCacheVo[] = []
40
- await packageDictDataMap(dataMap, defaultList, data[item])
167
+ await packageDictDataMap(dataMap, defaultList, normalized[item])
41
168
  return { item, dataMap, defaultList }
42
169
  })
43
170
  const results = await Promise.allSettled(promises)
44
171
  results.forEach((result, index) => {
45
172
  if (result.status === 'fulfilled') {
46
173
  const { item, dataMap, defaultList } = result.value
47
- dictDataMap.value[item] = dataMap
48
- dictDefaultValueMap.value[item] = defaultList
174
+ nextDataMap[item] = dataMap
175
+ nextDefaultMap[item] = defaultList
49
176
  } else {
50
- ElMessage.error(`处理键 ${Object.keys(data)[index]} 时出错:`, result.reason)
177
+ ElMessage.error(`处理键 ${Object.keys(normalized)[index]} 时出错:`, result.reason)
51
178
  }
52
179
  })
180
+ dictDataMap.value = nextDataMap
181
+ dictDefaultValueMap.value = nextDefaultMap
53
182
  dictStores.publishToSharedCache()
54
183
  } catch (error: any) {
55
184
  frameworkUtils.messageError(error)
@@ -75,13 +204,8 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
75
204
  }
76
205
 
77
206
  const getDictName = (value?: string, dictType?: string, orgId?: string): string | undefined => {
78
- let key = orgId ? `${orgId}:${dictType}` : dictType
79
- if (dictDataMap.value[key!]) return dictDataMap.value[key!][value!]?.label || value
80
- const tenantOrgId = resolveOrgId()
81
- if (tenantOrgId) {
82
- key = `${tenantOrgId}:${dictType}`
83
- if (dictDataMap.value[key!]) return dictDataMap.value[key!][value!]?.label || value
84
- }
207
+ const key = resolveDictDataKey(dictType!, orgId)
208
+ if (key && dictDataMap.value[key]) return dictDataMap.value[key][value!]?.label || value
85
209
  return value
86
210
  }
87
211
 
@@ -90,52 +214,59 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
90
214
  dictType: string,
91
215
  orgId?: string,
92
216
  ): ISysDictDataCacheVo | {} => {
93
- let key = orgId ? `${orgId}:${dictType}` : dictType
94
- if (dictDataMap.value[key!] && dictDataMap.value[key!][value!])
95
- return dictDataMap.value[key!][value!]
96
- const tenantOrgId = resolveOrgId()
97
- if (tenantOrgId) {
98
- key = `${tenantOrgId}:${dictType}`
99
- if (dictDataMap.value[key!] && dictDataMap.value[key!][value!])
100
- return dictDataMap.value[key!][value!]
101
- }
217
+ const key = resolveDictDataKey(dictType, orgId)
218
+ if (key && dictDataMap.value[key]?.[value]) return dictDataMap.value[key][value]
102
219
  return {}
103
220
  }
104
221
  const getDictDefaultValue = (
105
222
  dictType: string,
106
223
  orgId?: string
107
224
  ): ISysDictDataCacheVo[] => {
108
- let key = orgId ? `${orgId}:${dictType}` : dictType
109
- if (dictDefaultValueMap.value[key!]) return dictDefaultValueMap.value[key!]
110
225
  const tenantOrgId = resolveOrgId()
111
- if (tenantOrgId) {
112
- key = `${tenantOrgId}:${dictType}`
113
- if (dictDefaultValueMap.value[key!]) return dictDefaultValueMap.value[key!]
226
+ const scopedOrgId = orgId ?? tenantOrgId
227
+ if (scopedOrgId) {
228
+ const scoped = dictDefaultValueMap.value[`${scopedOrgId}:${dictType}`]
229
+ if (scoped?.length) return scoped
114
230
  }
115
- return []
231
+ return dictDefaultValueMap.value[dictType] ?? []
116
232
  }
117
233
  const getDictTypeData = (dictType: string, orgId?: string): ISysDictDataCacheVo[] => {
118
- let key = orgId ? `${orgId}:${dictType}` : dictType
119
- if (dictMap.value[key!]) return dictMap.value[key!]
120
- const tenantOrgId = resolveOrgId()
121
- if (tenantOrgId) {
122
- key = `${tenantOrgId}:${dictType}`
123
- if (dictMap.value[key!]) return dictMap.value[key!]
234
+ const key = resolveDictMapKey(dictType, orgId)
235
+ if (!key) return []
236
+ const list = dictMap.value[key]
237
+ const tree = ensureDictTree(list)
238
+ if (tree !== list && tree.some((item) => item.children?.length)) {
239
+ dictMap.value[key] = tree
240
+ const bare = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key
241
+ const tenantOrgId = resolveOrgId(orgId)
242
+ if (tenantOrgId && !key.includes(':')) {
243
+ dictMap.value[`${tenantOrgId}:${bare}`] = tree
244
+ } else if (key.includes(':') && !dictMap.value[bare]?.some((i) => i.children?.length)) {
245
+ dictMap.value[bare] = tree
246
+ }
124
247
  }
125
- return []
248
+ return tree
126
249
  }
127
250
  const clearDict = (dictType?: string[], orgId?: string, all?: Boolean) => {
128
251
  clearDictCache(dictType, resolveOrgId(), orgId, all)
129
252
  }
253
+ const userReady = ref(!dictTypes?.length)
130
254
  const dictReady = ref(!dictTypes?.length)
131
255
  if (dictTypes?.length) {
132
- getDict(dictTypes, orgId).finally(() => {
256
+ void (async () => {
257
+ await userInfo.ensureUserHydrated()
258
+ userReady.value = true
259
+ await getDict(dictTypes, orgId)
133
260
  dictReady.value = true
134
- })
261
+ })()
135
262
  }
136
263
  return {
137
264
  getDict,
265
+ userReady,
138
266
  dictReady,
267
+ dictMap,
268
+ dictDataMap,
269
+ dictDefaultValueMap,
139
270
  getDictName,
140
271
  getDictData,
141
272
  getDictTypeData,
@@ -47,7 +47,7 @@ function getRootWindow(): Window {
47
47
  }
48
48
 
49
49
  function hydrateCacheFromSessionStorage(cache: SharedDictCache) {
50
- const raw = sessionStorage.getItem(DICT_STORAGE_KEY)
50
+ const raw = getRootWindow().sessionStorage.getItem(DICT_STORAGE_KEY)
51
51
  if (!raw) return
52
52
  try {
53
53
  const parsed = JSON.parse(Base64.parse(raw).toString(Utf8)) as {
@@ -70,14 +70,15 @@ function hydrateCacheFromSessionStorage(cache: SharedDictCache) {
70
70
  }
71
71
 
72
72
  function persistCacheToSessionStorage(cache: SharedDictCache) {
73
- if (typeof sessionStorage === 'undefined') return
73
+ if (typeof window === 'undefined') return
74
+ const storage = getRootWindow().sessionStorage
74
75
  try {
75
76
  const payload = JSON.stringify({
76
77
  dictMap: cache.dictMap,
77
78
  dictDataMap: cache.dictDataMap,
78
79
  dictDefaultValueMap: cache.dictDefaultValueMap,
79
80
  })
80
- sessionStorage.setItem(DICT_STORAGE_KEY, Base64.stringify(Utf8.parse(payload)))
81
+ storage.setItem(DICT_STORAGE_KEY, Base64.stringify(Utf8.parse(payload)))
81
82
  } catch {
82
83
  // ignore quota / privacy errors
83
84
  }
@@ -8,6 +8,12 @@ import { defineStore } from 'pinia'
8
8
  import type { IUserPermissionVo } from '../interface/IUserPermissionVo'
9
9
  import { readUserInfoFromSessionStorage } from '../utils/userSessionStorage'
10
10
 
11
+ let hydratePromise: Promise<IUserPermissionVo | null> | null = null
12
+
13
+ function resetHydratePromise() {
14
+ hydratePromise = null
15
+ }
16
+
11
17
  export const userInfoStore = defineStore({
12
18
  id: 'userInfoStore',
13
19
  state: () => ({
@@ -16,34 +22,52 @@ export const userInfoStore = defineStore({
16
22
  }),
17
23
  getters: {
18
24
  getUserInfo(): IUserPermissionVo {
19
- if (!this.user) {
20
- const hydrated = readUserInfoFromSessionStorage()
21
- if (hydrated) {
22
- //@ts-ignore
23
- this.user = hydrated
24
- }
25
- }
25
+ this.hydrateFromRootSession()
26
26
  return this.user as IUserPermissionVo
27
27
  },
28
28
  },
29
29
  actions: {
30
30
  /** 无界子应用从宿主 sessionStorage 恢复登录态(与 dictStore 同源策略) */
31
31
  hydrateFromRootSession(): IUserPermissionVo | null {
32
- if (this.user) {
32
+ if (this.user?.orgId) {
33
33
  return this.user
34
34
  }
35
35
  const hydrated = readUserInfoFromSessionStorage()
36
- if (hydrated) {
36
+ if (hydrated?.orgId) {
37
37
  //@ts-ignore
38
38
  this.user = hydrated
39
39
  }
40
40
  return this.user
41
41
  },
42
+ /** 等待宿主 sessionStorage 写入 userInfo(无界子应用首次进入菜单页) */
43
+ async ensureUserHydrated(maxWaitMs = 3000): Promise<IUserPermissionVo | null> {
44
+ if (this.user?.orgId) {
45
+ return this.user
46
+ }
47
+ if (!hydratePromise) {
48
+ hydratePromise = (async () => {
49
+ const deadline = Date.now() + maxWaitMs
50
+ while (Date.now() < deadline) {
51
+ const hydrated = this.hydrateFromRootSession()
52
+ if (hydrated?.orgId) {
53
+ return hydrated
54
+ }
55
+ await new Promise((resolve) => setTimeout(resolve, 50))
56
+ }
57
+ return this.user
58
+ })().finally(() => {
59
+ hydratePromise = null
60
+ })
61
+ }
62
+ return hydratePromise
63
+ },
42
64
  setUserInfo(userInfo: IUserPermissionVo) {
65
+ resetHydratePromise()
43
66
  //@ts-ignore
44
67
  this.user = userInfo
45
68
  },
46
69
  clear() {
70
+ resetHydratePromise()
47
71
  //@ts-ignore
48
72
  this.user = null
49
73
  },
@@ -14,8 +14,18 @@ export function getRootWindow(): Window {
14
14
  /** 从 pinia-plugin-store 持久化的 sessionStorage 恢复用户信息 */
15
15
  export function readUserInfoFromSessionStorage(storage?: Storage): IUserPermissionVo | null {
16
16
  if (typeof window === 'undefined') return null
17
- const store = storage ?? getRootWindow().sessionStorage
18
- const raw = store.getItem(USER_INFO_STORAGE_KEY)
17
+ let store: Storage
18
+ try {
19
+ store = storage ?? getRootWindow().sessionStorage
20
+ } catch {
21
+ return null
22
+ }
23
+ let raw: string | null
24
+ try {
25
+ raw = store.getItem(USER_INFO_STORAGE_KEY)
26
+ } catch {
27
+ return null
28
+ }
19
29
  if (!raw) return null
20
30
  try {
21
31
  const parsed = JSON.parse(Base64.parse(raw).toString(Utf8)) as {