adtec-core-package 3.3.0 → 3.3.2
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 +1 -1
- package/src/components/autoToolTip/ElAutoToolTip.vue +1 -0
- package/src/components/upload/OfficePreview.vue +75 -13
- package/src/components/upload/OfficePreviewHeaderBar.vue +8 -1
- package/src/config/ElementPlusConfig.ts +30 -0
- package/src/config/VxeTableConfig.ts +5 -1
- package/src/hooks/useDictHooks.ts +167 -42
- package/src/utils/patchWujiePopperCoordinates.ts +91 -0
package/package.json
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
<office-preview-header-bar
|
|
5
5
|
v-if="showHeader"
|
|
6
6
|
:title="headerTitle"
|
|
7
|
+
:title-only="previewLoadFailed"
|
|
7
8
|
:zoom="scale"
|
|
8
9
|
:current-page="previewCurrentPage"
|
|
9
10
|
:total-pages="previewTotalPages"
|
|
10
11
|
:search-query="searchQuery"
|
|
11
12
|
:search-index="searchMatchIndex"
|
|
12
13
|
:search-total="searchMatchTotal"
|
|
13
|
-
:show-search="
|
|
14
|
-
:show-page-nav="
|
|
15
|
-
:show-zoom="
|
|
16
|
-
:show-download="
|
|
17
|
-
:show-refresh="
|
|
18
|
-
:show-fullscreen="
|
|
14
|
+
:show-search="showHeaderSearch"
|
|
15
|
+
:show-page-nav="showHeaderPageNav"
|
|
16
|
+
:show-zoom="showHeaderZoom"
|
|
17
|
+
:show-download="showHeaderDownload"
|
|
18
|
+
:show-refresh="showHeaderRefresh"
|
|
19
|
+
:show-fullscreen="showHeaderFullscreen"
|
|
19
20
|
:show-close="showClose"
|
|
20
21
|
:fullscreen="fullscreen"
|
|
21
22
|
:download-loading="downloadLoading"
|
|
@@ -79,6 +80,9 @@
|
|
|
79
80
|
<p v-if="headerTitle" class="loading-sub">{{ headerTitle }}</p>
|
|
80
81
|
</div>
|
|
81
82
|
</div>
|
|
83
|
+
<div v-else-if="previewLoadFailed" class="office-rich-lost">
|
|
84
|
+
<el-empty :description="lostDescription" />
|
|
85
|
+
</div>
|
|
82
86
|
<div v-else-if="isPreviewLoading && !fileSrc" class="office-rich-loading">
|
|
83
87
|
<div class="loading-card">
|
|
84
88
|
<span class="loading-spinner" />
|
|
@@ -137,6 +141,10 @@ const props = withDefaults(
|
|
|
137
141
|
fullscreen?: boolean
|
|
138
142
|
downloadLoading?: boolean
|
|
139
143
|
refreshLoading?: boolean
|
|
144
|
+
/** 库里有附件记录但物理文件无法加载时的提示 */
|
|
145
|
+
lostDescription?: string
|
|
146
|
+
/** 父组件已判定附件丢失(如无下载地址)时直接展示丢失态 */
|
|
147
|
+
attachmentLost?: boolean
|
|
140
148
|
}>(),
|
|
141
149
|
{
|
|
142
150
|
showHeader: true,
|
|
@@ -150,6 +158,8 @@ const props = withDefaults(
|
|
|
150
158
|
fullscreen: false,
|
|
151
159
|
downloadLoading: false,
|
|
152
160
|
refreshLoading: false,
|
|
161
|
+
lostDescription: '附件已丢失',
|
|
162
|
+
attachmentLost: false,
|
|
153
163
|
},
|
|
154
164
|
)
|
|
155
165
|
|
|
@@ -176,6 +186,7 @@ const searchQuery = ref('')
|
|
|
176
186
|
const searchMatchIndex = ref(-1)
|
|
177
187
|
const searchMatchTotal = ref(0)
|
|
178
188
|
const isPreviewLoading = ref(false)
|
|
189
|
+
const previewLoadFailed = ref(false)
|
|
179
190
|
const loadingPhase = ref<'fetch' | 'open'>('fetch')
|
|
180
191
|
|
|
181
192
|
type RichViewer = {
|
|
@@ -201,11 +212,21 @@ const isLegacyDoc = computed(() =>
|
|
|
201
212
|
|
|
202
213
|
const isRichPreview = computed(
|
|
203
214
|
() =>
|
|
215
|
+
props.attachmentLost ||
|
|
204
216
|
officeType.value === 'pdf' ||
|
|
205
217
|
officeType.value === 'excel' ||
|
|
206
218
|
officeType.value === 'docx',
|
|
207
219
|
)
|
|
208
220
|
|
|
221
|
+
const showHeaderSearch = computed(() => props.showSearch && !previewLoadFailed.value)
|
|
222
|
+
const showHeaderPageNav = computed(
|
|
223
|
+
() => props.showPageNav && !previewLoadFailed.value && officeType.value !== 'excel',
|
|
224
|
+
)
|
|
225
|
+
const showHeaderZoom = computed(() => props.showZoom && !previewLoadFailed.value)
|
|
226
|
+
const showHeaderDownload = computed(() => props.showDownload && !previewLoadFailed.value)
|
|
227
|
+
const showHeaderRefresh = computed(() => props.showRefresh && !previewLoadFailed.value)
|
|
228
|
+
const showHeaderFullscreen = computed(() => props.showFullscreen && !previewLoadFailed.value)
|
|
229
|
+
|
|
209
230
|
const isUnsupportedType = computed(
|
|
210
231
|
() => !!officeType.value && !isSupportedPreviewType(officeType.value),
|
|
211
232
|
)
|
|
@@ -235,13 +256,25 @@ const resetPreview = () => {
|
|
|
235
256
|
searchMatchIndex.value = -1
|
|
236
257
|
searchMatchTotal.value = 0
|
|
237
258
|
isPreviewLoading.value = false
|
|
259
|
+
previewLoadFailed.value = false
|
|
238
260
|
loadingPhase.value = 'fetch'
|
|
239
261
|
pdfViewerRef.value?.clearSearch()
|
|
240
262
|
excelViewerRef.value?.clearSearch()
|
|
241
263
|
docxViewerRef.value?.clearSearch()
|
|
242
264
|
}
|
|
243
265
|
|
|
266
|
+
const markPreviewLost = (err?: unknown) => {
|
|
267
|
+
isPreviewLoading.value = false
|
|
268
|
+
previewLoadFailed.value = true
|
|
269
|
+
fileSrc.value = undefined
|
|
270
|
+
emit('error', err)
|
|
271
|
+
}
|
|
272
|
+
|
|
244
273
|
const loadPreview = async () => {
|
|
274
|
+
if (props.attachmentLost) {
|
|
275
|
+
markPreviewLost()
|
|
276
|
+
return
|
|
277
|
+
}
|
|
245
278
|
if (!props.url || !officeType.value) {
|
|
246
279
|
return
|
|
247
280
|
}
|
|
@@ -262,6 +295,10 @@ const loadPreview = async () => {
|
|
|
262
295
|
}
|
|
263
296
|
loadingPhase.value = 'open'
|
|
264
297
|
const pdfBlob = await documentApi.previewWordAsPdf(props.fileId)
|
|
298
|
+
if (!pdfBlob || pdfBlob.size === 0) {
|
|
299
|
+
markPreviewLost()
|
|
300
|
+
return
|
|
301
|
+
}
|
|
265
302
|
previewAsPdf.value = true
|
|
266
303
|
fileSrc.value = pdfBlob
|
|
267
304
|
return
|
|
@@ -273,16 +310,22 @@ const loadPreview = async () => {
|
|
|
273
310
|
})
|
|
274
311
|
if (officeType.value === 'txt') {
|
|
275
312
|
txtContent.value = await res.data.text()
|
|
313
|
+
if (!txtContent.value.trim()) {
|
|
314
|
+
markPreviewLost()
|
|
315
|
+
return
|
|
316
|
+
}
|
|
276
317
|
isPreviewLoading.value = false
|
|
277
318
|
emit('loaded')
|
|
278
319
|
} else {
|
|
279
320
|
loadingPhase.value = 'open'
|
|
321
|
+
if (!res.data || res.data.size === 0) {
|
|
322
|
+
markPreviewLost()
|
|
323
|
+
return
|
|
324
|
+
}
|
|
280
325
|
fileSrc.value = res.data
|
|
281
326
|
}
|
|
282
327
|
} catch (err) {
|
|
283
|
-
|
|
284
|
-
frameworkUtils.messageError(err)
|
|
285
|
-
emit('error', err)
|
|
328
|
+
markPreviewLost(err)
|
|
286
329
|
}
|
|
287
330
|
}
|
|
288
331
|
|
|
@@ -294,8 +337,7 @@ const handleRendered = async () => {
|
|
|
294
337
|
}
|
|
295
338
|
|
|
296
339
|
const handleError = (err?: unknown) => {
|
|
297
|
-
|
|
298
|
-
emit('error', err)
|
|
340
|
+
markPreviewLost(err)
|
|
299
341
|
}
|
|
300
342
|
|
|
301
343
|
const zoomIn = () => {
|
|
@@ -357,9 +399,13 @@ const goNextSearchMatch = async () => {
|
|
|
357
399
|
}
|
|
358
400
|
|
|
359
401
|
watch(
|
|
360
|
-
() => [props.url, props.fileName, props.fileType] as const,
|
|
361
|
-
([fileUrl]) => {
|
|
402
|
+
() => [props.url, props.fileName, props.fileType, props.attachmentLost] as const,
|
|
403
|
+
([fileUrl, , , attachmentLost]) => {
|
|
362
404
|
resetPreview()
|
|
405
|
+
if (attachmentLost) {
|
|
406
|
+
markPreviewLost()
|
|
407
|
+
return
|
|
408
|
+
}
|
|
363
409
|
if (fileUrl) {
|
|
364
410
|
loadPreview()
|
|
365
411
|
}
|
|
@@ -395,6 +441,9 @@ defineExpose({
|
|
|
395
441
|
display: flex;
|
|
396
442
|
flex-direction: column;
|
|
397
443
|
overflow: hidden;
|
|
444
|
+
padding-bottom: 8px;
|
|
445
|
+
box-sizing: border-box;
|
|
446
|
+
background: #fff;
|
|
398
447
|
}
|
|
399
448
|
|
|
400
449
|
&.is-fullscreen {
|
|
@@ -402,6 +451,9 @@ defineExpose({
|
|
|
402
451
|
background: #f5f5f5 !important;
|
|
403
452
|
|
|
404
453
|
&.is-rich-preview {
|
|
454
|
+
padding-bottom: 0;
|
|
455
|
+
background: #f5f5f5 !important;
|
|
456
|
+
|
|
405
457
|
.office-rich-wrap {
|
|
406
458
|
flex: 1;
|
|
407
459
|
min-height: 0;
|
|
@@ -434,6 +486,16 @@ defineExpose({
|
|
|
434
486
|
flex-direction: column;
|
|
435
487
|
}
|
|
436
488
|
|
|
489
|
+
.office-rich-lost {
|
|
490
|
+
position: absolute;
|
|
491
|
+
inset: 0;
|
|
492
|
+
z-index: 3;
|
|
493
|
+
display: flex;
|
|
494
|
+
align-items: center;
|
|
495
|
+
justify-content: center;
|
|
496
|
+
background: #fff;
|
|
497
|
+
}
|
|
498
|
+
|
|
437
499
|
.office-rich-loading {
|
|
438
500
|
position: absolute;
|
|
439
501
|
inset: 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="office-preview-header">
|
|
2
|
+
<div class="office-preview-header" :class="{ 'is-title-only': titleOnly }">
|
|
3
3
|
<div v-if="title" class="header-title" :title="title">{{ title }}</div>
|
|
4
4
|
|
|
5
5
|
<div v-if="showSearch" class="header-search">
|
|
@@ -138,6 +138,7 @@ import { MAX_PDF_ZOOM, MIN_OFFICE_ZOOM } from '../../utils/officePreviewUtil.ts'
|
|
|
138
138
|
const props = withDefaults(
|
|
139
139
|
defineProps<{
|
|
140
140
|
title?: string
|
|
141
|
+
titleOnly?: boolean
|
|
141
142
|
zoom?: number
|
|
142
143
|
currentPage?: number
|
|
143
144
|
totalPages?: number
|
|
@@ -159,6 +160,7 @@ const props = withDefaults(
|
|
|
159
160
|
}>(),
|
|
160
161
|
{
|
|
161
162
|
title: '',
|
|
163
|
+
titleOnly: false,
|
|
162
164
|
zoom: 1,
|
|
163
165
|
currentPage: 1,
|
|
164
166
|
totalPages: 0,
|
|
@@ -234,6 +236,11 @@ const emitSearch = () => {
|
|
|
234
236
|
flex-shrink: 0;
|
|
235
237
|
}
|
|
236
238
|
|
|
239
|
+
.is-title-only .header-title {
|
|
240
|
+
max-width: none;
|
|
241
|
+
flex: 1;
|
|
242
|
+
}
|
|
243
|
+
|
|
237
244
|
.header-search {
|
|
238
245
|
display: flex;
|
|
239
246
|
align-items: center;
|
|
@@ -21,7 +21,10 @@ import elementPlus, {
|
|
|
21
21
|
ElCascader,
|
|
22
22
|
ElTable,
|
|
23
23
|
ElTooltip,
|
|
24
|
+
ElPopover,
|
|
25
|
+
ElDropdown,
|
|
24
26
|
} from 'element-plus'
|
|
27
|
+
import { patchWujiePopperCoordinates } from '../utils/patchWujiePopperCoordinates'
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* vxe 单元格编辑时,Element 下拉/日期面板默认 teleport 到 body。
|
|
@@ -82,6 +85,33 @@ ElSelect.props.fitInputWidth = {
|
|
|
82
85
|
type: Boolean,
|
|
83
86
|
default: false,
|
|
84
87
|
}
|
|
88
|
+
const isWujieSubApp =
|
|
89
|
+
typeof window !== 'undefined' && !!(window as Window & { __POWERED_BY_WUJIE__?: boolean }).__POWERED_BY_WUJIE__
|
|
90
|
+
/**
|
|
91
|
+
* 无界 iframe 内 Popper 坐标修正(与下拉框数据页同类问题)。
|
|
92
|
+
* ElSelect / ElDatePicker 等须保持 teleported 默认 true(vxe 单元格挂 body + popperClass 防 clearEdit),
|
|
93
|
+
* 仅通过 strategy/modifiers 修正偏移,避免各子应用逐页 v-bind。
|
|
94
|
+
*/
|
|
95
|
+
const wujiePopperOptions = {
|
|
96
|
+
strategy: 'fixed' as const,
|
|
97
|
+
modifiers: [
|
|
98
|
+
{
|
|
99
|
+
name: 'computeStyles',
|
|
100
|
+
options: { adaptive: false, gpuAcceleration: false },
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}
|
|
104
|
+
if (isWujieSubApp) {
|
|
105
|
+
patchWujiePopperCoordinates()
|
|
106
|
+
const wujiePopperProps = { type: Object, default: () => wujiePopperOptions }
|
|
107
|
+
ElSelect.props.popperOptions = wujiePopperProps
|
|
108
|
+
ElDatePicker.props.popperOptions = wujiePopperProps
|
|
109
|
+
ElTreeSelect.props.popperOptions = wujiePopperProps
|
|
110
|
+
ElCascader.props.popperOptions = wujiePopperProps
|
|
111
|
+
ElTooltip.props.popperOptions = wujiePopperProps
|
|
112
|
+
ElPopover.props.popperOptions = wujiePopperProps
|
|
113
|
+
ElDropdown.props.popperOptions = wujiePopperProps
|
|
114
|
+
}
|
|
85
115
|
ElSelect.props.popperClass = {
|
|
86
116
|
type: String,
|
|
87
117
|
default: VXE_IGNORE_CLEAR_POPPER,
|
|
@@ -17,6 +17,7 @@ import { VxeUIPluginExportPDF } from '@vxe-ui/plugin-export-pdf'
|
|
|
17
17
|
import { jsPDF } from 'jspdf'
|
|
18
18
|
// 导入默认的语言(使用 es 构建,避免 lib CJS 在 Vite 下无 default 导出)
|
|
19
19
|
import zhCN from 'vxe-table/es/locale/lang/zh-CN'
|
|
20
|
+
import { patchWujiePopperCoordinates } from '../utils/patchWujiePopperCoordinates'
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* 导出插件(Excel/PDF)全局注册一次。
|
|
@@ -32,7 +33,9 @@ function registerVxeExportPlugins() {
|
|
|
32
33
|
VxeUI.use(VxeUIPluginExportXLSX, { ExcelJS })
|
|
33
34
|
VxeUI.use(VxeUIPluginExportPDF, { jsPDF })
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
import { patchWujiePopperCoordinates } from '../utils/patchWujiePopperCoordinates'
|
|
37
|
+
|
|
38
|
+
// 模块加载即注册导出插件;无界坐标补丁亦在 patch 模块内执行
|
|
36
39
|
registerVxeExportPlugins()
|
|
37
40
|
|
|
38
41
|
/**
|
|
@@ -43,6 +46,7 @@ export function initVxeTableInPage(
|
|
|
43
46
|
enablePdf: boolean = false,
|
|
44
47
|
enableElementPlus: boolean = true,
|
|
45
48
|
) {
|
|
49
|
+
patchWujiePopperCoordinates(true)
|
|
46
50
|
// 设置语言
|
|
47
51
|
VxeUI.setI18n('zh-CN', zhCN)
|
|
48
52
|
VxeUI.setLanguage('zh-CN')
|
|
@@ -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,38 +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
|
|
25
112
|
await userInfo.ensureUserHydrated()
|
|
26
113
|
const tenantOrgId = resolveOrgId(orgId)
|
|
27
|
-
|
|
28
|
-
let
|
|
29
|
-
if (
|
|
30
|
-
const oid = orgId ?? tenantOrgId
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
34
134
|
}
|
|
135
|
+
if (cacheDirty) {
|
|
136
|
+
dictStores.publishToSharedCache()
|
|
137
|
+
}
|
|
138
|
+
const uncachedDictTypes = dictTypes.filter(
|
|
139
|
+
(dictType) => !hasDictCache(dictType, orgId ?? tenantOrgId),
|
|
140
|
+
)
|
|
35
141
|
if (!uncachedDictTypes.length) return
|
|
36
142
|
const data = await SysDictCacheApi.batchGetSysDictDataCacheVo(uncachedDictTypes, orgId ?? tenantOrgId)
|
|
37
|
-
|
|
38
|
-
|
|
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) => {
|
|
39
165
|
const dataMap: dictDataMapVoType = {}
|
|
40
166
|
const defaultList: ISysDictDataCacheVo[] = []
|
|
41
|
-
await packageDictDataMap(dataMap, defaultList,
|
|
167
|
+
await packageDictDataMap(dataMap, defaultList, normalized[item])
|
|
42
168
|
return { item, dataMap, defaultList }
|
|
43
169
|
})
|
|
44
170
|
const results = await Promise.allSettled(promises)
|
|
45
171
|
results.forEach((result, index) => {
|
|
46
172
|
if (result.status === 'fulfilled') {
|
|
47
173
|
const { item, dataMap, defaultList } = result.value
|
|
48
|
-
|
|
49
|
-
|
|
174
|
+
nextDataMap[item] = dataMap
|
|
175
|
+
nextDefaultMap[item] = defaultList
|
|
50
176
|
} else {
|
|
51
|
-
ElMessage.error(`处理键 ${Object.keys(
|
|
177
|
+
ElMessage.error(`处理键 ${Object.keys(normalized)[index]} 时出错:`, result.reason)
|
|
52
178
|
}
|
|
53
179
|
})
|
|
180
|
+
dictDataMap.value = nextDataMap
|
|
181
|
+
dictDefaultValueMap.value = nextDefaultMap
|
|
54
182
|
dictStores.publishToSharedCache()
|
|
55
183
|
} catch (error: any) {
|
|
56
184
|
frameworkUtils.messageError(error)
|
|
@@ -76,13 +204,8 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
|
|
|
76
204
|
}
|
|
77
205
|
|
|
78
206
|
const getDictName = (value?: string, dictType?: string, orgId?: string): string | undefined => {
|
|
79
|
-
|
|
80
|
-
if (dictDataMap.value[key
|
|
81
|
-
const tenantOrgId = resolveOrgId()
|
|
82
|
-
if (tenantOrgId) {
|
|
83
|
-
key = `${tenantOrgId}:${dictType}`
|
|
84
|
-
if (dictDataMap.value[key!]) return dictDataMap.value[key!][value!]?.label || value
|
|
85
|
-
}
|
|
207
|
+
const key = resolveDictDataKey(dictType!, orgId)
|
|
208
|
+
if (key && dictDataMap.value[key]) return dictDataMap.value[key][value!]?.label || value
|
|
86
209
|
return value
|
|
87
210
|
}
|
|
88
211
|
|
|
@@ -91,39 +214,38 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
|
|
|
91
214
|
dictType: string,
|
|
92
215
|
orgId?: string,
|
|
93
216
|
): ISysDictDataCacheVo | {} => {
|
|
94
|
-
|
|
95
|
-
if (dictDataMap.value[key
|
|
96
|
-
return dictDataMap.value[key!][value!]
|
|
97
|
-
const tenantOrgId = resolveOrgId()
|
|
98
|
-
if (tenantOrgId) {
|
|
99
|
-
key = `${tenantOrgId}:${dictType}`
|
|
100
|
-
if (dictDataMap.value[key!] && dictDataMap.value[key!][value!])
|
|
101
|
-
return dictDataMap.value[key!][value!]
|
|
102
|
-
}
|
|
217
|
+
const key = resolveDictDataKey(dictType, orgId)
|
|
218
|
+
if (key && dictDataMap.value[key]?.[value]) return dictDataMap.value[key][value]
|
|
103
219
|
return {}
|
|
104
220
|
}
|
|
105
221
|
const getDictDefaultValue = (
|
|
106
222
|
dictType: string,
|
|
107
223
|
orgId?: string
|
|
108
224
|
): ISysDictDataCacheVo[] => {
|
|
109
|
-
let key = orgId ? `${orgId}:${dictType}` : dictType
|
|
110
|
-
if (dictDefaultValueMap.value[key!]) return dictDefaultValueMap.value[key!]
|
|
111
225
|
const tenantOrgId = resolveOrgId()
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
226
|
+
const scopedOrgId = orgId ?? tenantOrgId
|
|
227
|
+
if (scopedOrgId) {
|
|
228
|
+
const scoped = dictDefaultValueMap.value[`${scopedOrgId}:${dictType}`]
|
|
229
|
+
if (scoped?.length) return scoped
|
|
115
230
|
}
|
|
116
|
-
return []
|
|
231
|
+
return dictDefaultValueMap.value[dictType] ?? []
|
|
117
232
|
}
|
|
118
233
|
const getDictTypeData = (dictType: string, orgId?: string): ISysDictDataCacheVo[] => {
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
}
|
|
125
247
|
}
|
|
126
|
-
return
|
|
248
|
+
return tree
|
|
127
249
|
}
|
|
128
250
|
const clearDict = (dictType?: string[], orgId?: string, all?: Boolean) => {
|
|
129
251
|
clearDictCache(dictType, resolveOrgId(), orgId, all)
|
|
@@ -142,6 +264,9 @@ export default function useDictHooks(dictTypes?: string[], orgId?: string) {
|
|
|
142
264
|
getDict,
|
|
143
265
|
userReady,
|
|
144
266
|
dictReady,
|
|
267
|
+
dictMap,
|
|
268
|
+
dictDataMap,
|
|
269
|
+
dictDefaultValueMap,
|
|
145
270
|
getDictName,
|
|
146
271
|
getDictData,
|
|
147
272
|
getDictTypeData,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 无界 DocElementRectPlugin 将子应用 document.documentElement 的
|
|
3
|
+
* scrollTop/scrollLeft/clientWidth/clientHeight 代理到主应用窗口。
|
|
4
|
+
*
|
|
5
|
+
* vxe-table overflow tooltip 用 getAbsolutePos:top = document.scrollTop + cell.getBoundingClientRect().top
|
|
6
|
+
* 混用主应用 scroll 与 iframe 内坐标 → 浮层整体下移(常见 1~3 行高度)。
|
|
7
|
+
*
|
|
8
|
+
* Element Plus Popper → ElementPlusConfig 使用 strategy:fixed。
|
|
9
|
+
* vxe tooltip → 本补丁:document 级滚动归零 + tooltip 改为 position:fixed。
|
|
10
|
+
*/
|
|
11
|
+
const STYLE_ID = 'adtec-wujie-popper-fix-style'
|
|
12
|
+
const HTML_CLASS = 'adtec-wujie-subapp'
|
|
13
|
+
|
|
14
|
+
let patched = false
|
|
15
|
+
|
|
16
|
+
const IFRAME_DOC_PROPS = [
|
|
17
|
+
'scrollTop',
|
|
18
|
+
'scrollLeft',
|
|
19
|
+
'clientHeight',
|
|
20
|
+
'clientWidth',
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
function isWujieSubApp() {
|
|
24
|
+
return (
|
|
25
|
+
typeof window !== 'undefined' &&
|
|
26
|
+
!!(window as Window & { __POWERED_BY_WUJIE__?: boolean }).__POWERED_BY_WUJIE__
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function injectWujiePopperStyles() {
|
|
31
|
+
if (typeof document === 'undefined' || document.getElementById(STYLE_ID)) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const style = document.createElement('style')
|
|
35
|
+
style.id = STYLE_ID
|
|
36
|
+
style.textContent = `
|
|
37
|
+
html.${HTML_CLASS} .vxe-tooltip--wrapper,
|
|
38
|
+
html.${HTML_CLASS} .vxe-table--tooltip-wrapper {
|
|
39
|
+
position: fixed !important;
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
}
|
|
42
|
+
`
|
|
43
|
+
document.head.appendChild(style)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function restoreIframeDocumentMetrics(docEl: HTMLElement, body: HTMLElement) {
|
|
47
|
+
IFRAME_DOC_PROPS.forEach((key) => {
|
|
48
|
+
Object.defineProperty(docEl, key, {
|
|
49
|
+
configurable: true,
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get() {
|
|
52
|
+
if (key === 'clientWidth') {
|
|
53
|
+
return window.innerWidth
|
|
54
|
+
}
|
|
55
|
+
if (key === 'clientHeight') {
|
|
56
|
+
return window.innerHeight
|
|
57
|
+
}
|
|
58
|
+
// 子应用页面滚动在表格/容器内,document 级滚动恒为 0
|
|
59
|
+
return 0
|
|
60
|
+
},
|
|
61
|
+
set(val: number) {
|
|
62
|
+
try {
|
|
63
|
+
;(body as HTMLElement & Record<string, number>)[key] = val
|
|
64
|
+
} catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** 无界子应用内统一修正 Popper / vxe tooltip 坐标系 */
|
|
73
|
+
export function patchWujiePopperCoordinates(force = false) {
|
|
74
|
+
if (typeof window === 'undefined') {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
if (!isWujieSubApp()) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
if (patched && !force) {
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
document.documentElement.classList.add(HTML_CLASS)
|
|
85
|
+
injectWujiePopperStyles()
|
|
86
|
+
restoreIframeDocumentMetrics(document.documentElement, document.body)
|
|
87
|
+
patched = true
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 核心包被 import 时即尝试打补丁(覆盖未调用 initVxeTableInPage 的 vxe-grid 页面)
|
|
91
|
+
patchWujiePopperCoordinates()
|