adtec-core-package 3.3.1 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adtec-core-package",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -8,6 +8,7 @@
8
8
  :content="props.tooltipContent ? props.tooltipContent : props.content"
9
9
  placement="top"
10
10
  :disabled="isShow"
11
+ :popper-options="{ strategy: 'fixed' }"
11
12
  >
12
13
  <template #content>
13
14
  <!-- 此处的默认值先看tooltipContent有没有,没有就给默认content -->
@@ -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="showSearch"
14
- :show-page-nav="showPageNav && officeType !== 'excel'"
15
- :show-zoom="showZoom"
16
- :show-download="showDownload"
17
- :show-refresh="showRefresh"
18
- :show-fullscreen="showFullscreen"
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
- isPreviewLoading.value = false
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
- isPreviewLoading.value = false
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。
@@ -85,7 +88,7 @@ ElSelect.props.fitInputWidth = {
85
88
  const isWujieSubApp =
86
89
  typeof window !== 'undefined' && !!(window as Window & { __POWERED_BY_WUJIE__?: boolean }).__POWERED_BY_WUJIE__
87
90
  /**
88
- * 无界 iframe 内 Popper 坐标修正。
91
+ * 无界 iframe 内 Popper 坐标修正(与下拉框数据页同类问题)。
89
92
  * ElSelect / ElDatePicker 等须保持 teleported 默认 true(vxe 单元格挂 body + popperClass 防 clearEdit),
90
93
  * 仅通过 strategy/modifiers 修正偏移,避免各子应用逐页 v-bind。
91
94
  */
@@ -99,10 +102,15 @@ const wujiePopperOptions = {
99
102
  ],
100
103
  }
101
104
  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 }
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
106
114
  }
107
115
  ElSelect.props.popperClass = {
108
116
  type: String,
@@ -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
- // 模块加载即注册,确保任何 grid 创建前 xlsx/pdf 类型已可用
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')
@@ -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()