adtec-core-package 3.2.1 → 3.2.3

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.1",
3
+ "version": "3.2.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -644,10 +644,41 @@ onUnmounted(() => {
644
644
  min-width: 0;
645
645
  }
646
646
 
647
+ /* classic 模式:切换工具栏固定右上角(与 ribbon 图二一致) */
648
+ :deep(.umo-toolbar-actions-classic) {
649
+ position: absolute;
650
+ right: 5px;
651
+ top: 6px;
652
+ z-index: 10;
653
+ border-radius: 6px;
654
+ background-color: var(--umo-color-white, #fff);
655
+ box-shadow:
656
+ 0 0 0 1px hsla(0, 0%, 5%, 0.04),
657
+ 0 2px 5px hsla(0, 0%, 5%, 0.06);
658
+ }
659
+
660
+ :deep(.toolbar-classic .umo-scrollable-container) {
661
+ padding-right: 108px;
662
+ }
663
+
664
+ :deep(.umo-scrollable-content) {
665
+ overflow-x: hidden;
666
+ overflow-y: hidden;
667
+ scrollbar-width: none;
668
+ -ms-overflow-style: none;
669
+
670
+ &::-webkit-scrollbar {
671
+ display: none;
672
+ width: 0;
673
+ height: 0;
674
+ }
675
+ }
676
+
647
677
  :deep(.umo-classic-menu),
648
678
  :deep(.umo-virtual-group) {
649
- flex-wrap: nowrap;
650
- white-space: nowrap;
679
+ flex-wrap: wrap;
680
+ row-gap: 4px;
681
+ white-space: normal;
651
682
  }
652
683
 
653
684
  :deep(.umo-button__text),
@@ -250,10 +250,12 @@ const setContentFromCache = () => {
250
250
  padding: 6px 10px;
251
251
  display: flex;
252
252
  align-items: center;
253
- &-ribbon {
253
+ &-ribbon,
254
+ &-classic {
254
255
  position: absolute;
255
256
  right: 0;
256
257
  top: 1px;
258
+ z-index: 10;
257
259
  }
258
260
  &-button {
259
261
  &.active {
@@ -337,9 +339,18 @@ const setContentFromCache = () => {
337
339
 
338
340
  <style lang="less">
339
341
  .umo-skin-modern {
342
+ &.toolbar-classic,
343
+ &.toolbar-ribbon {
344
+ .umo-toolbar-actions {
345
+ right: 5px !important;
346
+ top: 6px !important;
347
+ }
348
+ }
340
349
  &.toolbar-classic {
350
+ .umo-scrollable-container {
351
+ padding-right: 108px;
352
+ }
341
353
  .umo-toolbar-actions {
342
- margin: 15px 15px 2px 0;
343
354
  border-radius: 6px;
344
355
  background-color: var(--umo-color-white);
345
356
  box-shadow:
@@ -352,12 +363,6 @@ const setContentFromCache = () => {
352
363
  }
353
364
  }
354
365
  }
355
- &.toolbar-ribbon {
356
- .umo-toolbar-actions {
357
- right: 5px !important;
358
- top: 6px !important;
359
- }
360
- }
361
366
  }
362
367
  [theme-mode='dark'] .umo-skin-modern {
363
368
  &.toolbar-classic {
@@ -1,15 +1,15 @@
1
1
  <template>
2
2
  <div ref="scrollRef" class="docx-js-viewer">
3
- <div v-if="loading" class="docx-loading">正在解析 Word 文档…</div>
4
- <div v-else-if="loadError" class="docx-error-tip">
5
- <el-empty :description="loadError" />
6
- </div>
7
- <div v-else class="docx-body">
3
+ <div class="docx-body">
8
4
  <div ref="styleHostRef" class="docx-style-host" />
9
5
  <div class="docx-scale-host" :style="scaleHostStyle">
10
6
  <div ref="bodyHostRef" class="docx-render-host" :style="renderStyle" />
11
7
  </div>
12
8
  </div>
9
+ <div v-if="loading" class="docx-overlay docx-loading">正在解析 Word 文档…</div>
10
+ <div v-else-if="loadError" class="docx-overlay docx-error-tip">
11
+ <el-empty :description="loadError" />
12
+ </div>
13
13
  </div>
14
14
  </template>
15
15
 
@@ -98,16 +98,23 @@ const collectPageSections = () => {
98
98
  emit('page-change', 1)
99
99
  }
100
100
 
101
+ let loadToken = 0
102
+
101
103
  const loadDocument = async (src: Blob | ArrayBuffer) => {
104
+ const token = ++loadToken
102
105
  loading.value = true
103
- resetViewer()
106
+ loadError.value = ''
104
107
  try {
105
108
  const buffer = src instanceof Blob ? await src.arrayBuffer() : src
109
+ if (token !== loadToken) return
110
+
111
+ await nextTick()
106
112
  const bodyHost = bodyHostRef.value
107
113
  const styleHost = styleHostRef.value
108
114
  if (!bodyHost || !styleHost) {
109
115
  throw new Error('预览容器未就绪')
110
116
  }
117
+ resetViewer()
111
118
  await renderAsync(buffer, bodyHost, styleHost, {
112
119
  className: 'docx',
113
120
  inWrapper: true,
@@ -119,14 +126,18 @@ const loadDocument = async (src: Blob | ArrayBuffer) => {
119
126
  renderFootnotes: true,
120
127
  renderEndnotes: true,
121
128
  })
129
+ if (token !== loadToken) return
122
130
  await nextTick()
123
131
  collectPageSections()
124
132
  emit('loaded')
125
133
  } catch (err) {
134
+ if (token !== loadToken) return
126
135
  loadError.value = 'Word 文档解析失败,请下载后查看'
127
136
  emit('error', err)
128
137
  } finally {
129
- loading.value = false
138
+ if (token === loadToken) {
139
+ loading.value = false
140
+ }
130
141
  }
131
142
  }
132
143
 
@@ -196,7 +207,11 @@ watch(
196
207
  () => props.src,
197
208
  (src) => {
198
209
  if (src) void loadDocument(src)
199
- else resetViewer()
210
+ else {
211
+ ++loadToken
212
+ loading.value = false
213
+ resetViewer()
214
+ }
200
215
  },
201
216
  { immediate: true },
202
217
  )
@@ -213,6 +228,7 @@ defineExpose({
213
228
 
214
229
  <style scoped lang="scss">
215
230
  .docx-js-viewer {
231
+ position: relative;
216
232
  flex: 1;
217
233
  width: 100%;
218
234
  min-height: 0;
@@ -220,6 +236,16 @@ defineExpose({
220
236
  background: #e5e7eb;
221
237
  }
222
238
 
239
+ .docx-overlay {
240
+ position: absolute;
241
+ inset: 0;
242
+ z-index: 2;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ background: #e5e7eb;
247
+ }
248
+
223
249
  .docx-body {
224
250
  min-height: 100%;
225
251
  padding: 12px;
@@ -238,12 +264,7 @@ defineExpose({
238
264
  background: transparent;
239
265
  }
240
266
 
241
- .docx-loading,
242
- .docx-error-tip {
243
- display: flex;
244
- align-items: center;
245
- justify-content: center;
246
- min-height: 240px;
267
+ .docx-loading {
247
268
  color: #6b7280;
248
269
  font-size: 14px;
249
270
  }
@@ -8,6 +8,7 @@
8
8
  v-loading="showOverlayLoading"
9
9
  element-loading-text="加载中,请稍后。。。"
10
10
  class="file-view-overlay"
11
+ :class="{ 'is-preview-fullscreen': isFullscreen }"
11
12
  >
12
13
  <office-preview-toolbar
13
14
  v-if="!isHeaderPreview"
@@ -15,13 +16,23 @@
15
16
  :zoom="previewScale"
16
17
  :show-zoom="false"
17
18
  :show-download="!!url"
19
+ :show-fullscreen="true"
18
20
  :show-close="true"
21
+ :fullscreen="isFullscreen"
19
22
  :download-loading="loading"
20
23
  @download="download"
21
- @close="model = false"
24
+ @toggle-fullscreen="toggleFullscreen"
25
+ @close="closePreview"
22
26
  />
23
27
 
24
- <div class="office-preview" :class="{ 'has-rich-header': isHeaderPreview }">
28
+ <div
29
+ ref="previewContainerRef"
30
+ class="office-preview"
31
+ :class="{
32
+ 'has-rich-header': isHeaderPreview,
33
+ 'fullscreen-mode': isFullscreen,
34
+ }"
35
+ >
25
36
  <office-preview
26
37
  v-if="url"
27
38
  v-model:scale="previewScale"
@@ -30,19 +41,22 @@
30
41
  :file-name="title"
31
42
  :show-header="isHeaderPreview"
32
43
  :show-download="!!url"
44
+ :show-fullscreen="isHeaderPreview"
33
45
  :show-close="isHeaderPreview"
46
+ :fullscreen="isFullscreen"
34
47
  :download-loading="loading"
35
48
  @loaded="loading = false"
36
49
  @error="loading = false"
37
50
  @download="download"
38
- @close="model = false"
51
+ @toggle-fullscreen="toggleFullscreen"
52
+ @close="closePreview"
39
53
  />
40
54
  </div>
41
55
  </div>
42
56
  </Teleport>
43
57
  </template>
44
58
  <script setup lang="ts">
45
- import { computed, ref, watch } from 'vue'
59
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
46
60
  import { useVModel } from '@vueuse/core'
47
61
  import framework from '../../api/framework'
48
62
  import frameworkUtils from '../../utils/FrameworkUtils'
@@ -58,6 +72,8 @@ import {
58
72
  const model = defineModel()
59
73
  const loading = ref(false)
60
74
  const previewScale = ref(1)
75
+ const previewContainerRef = ref<HTMLElement | null>(null)
76
+ const isFullscreen = ref(false)
61
77
 
62
78
  const props = defineProps({
63
79
  title: {
@@ -89,6 +105,46 @@ const isHeaderPreview = computed(() =>
89
105
  /** 富文本预览由 OfficePreview 自带 loading,避免与 v-loading 双层转圈 */
90
106
  const showOverlayLoading = computed(() => loading.value && !isHeaderPreview.value)
91
107
 
108
+ const getContainerEl = () => previewContainerRef.value
109
+
110
+ const syncFullscreenState = () => {
111
+ const el = getContainerEl()
112
+ isFullscreen.value = !!el && document.fullscreenElement === el
113
+ document.body.style.overflow = isFullscreen.value ? 'hidden' : ''
114
+ }
115
+
116
+ const exitFullscreen = async () => {
117
+ try {
118
+ if (document.fullscreenElement === getContainerEl()) {
119
+ await document.exitFullscreen()
120
+ }
121
+ } catch {
122
+ // ignore
123
+ }
124
+ isFullscreen.value = false
125
+ document.body.style.overflow = ''
126
+ }
127
+
128
+ const toggleFullscreen = async () => {
129
+ const el = getContainerEl()
130
+ if (!el) return
131
+ try {
132
+ if (document.fullscreenElement === el) {
133
+ await document.exitFullscreen()
134
+ } else {
135
+ await el.requestFullscreen()
136
+ }
137
+ } catch {
138
+ isFullscreen.value = !isFullscreen.value
139
+ document.body.style.overflow = isFullscreen.value ? 'hidden' : ''
140
+ }
141
+ }
142
+
143
+ const closePreview = async () => {
144
+ await exitFullscreen()
145
+ model.value = false
146
+ }
147
+
92
148
  watch(
93
149
  [model, url],
94
150
  ([visible, fileUrl]) => {
@@ -100,11 +156,21 @@ watch(
100
156
  } else if (!visible) {
101
157
  loading.value = false
102
158
  previewScale.value = 1
159
+ void exitFullscreen()
103
160
  }
104
161
  },
105
162
  { immediate: true },
106
163
  )
107
164
 
165
+ onMounted(() => {
166
+ document.addEventListener('fullscreenchange', syncFullscreenState)
167
+ })
168
+
169
+ onBeforeUnmount(() => {
170
+ document.removeEventListener('fullscreenchange', syncFullscreenState)
171
+ void exitFullscreen()
172
+ })
173
+
108
174
  const getFileType = () => resolveOfficeFileExt(undefined, title.value, url.value)
109
175
 
110
176
  const download = async () => {
@@ -143,6 +209,10 @@ const download = async () => {
143
209
  display: flex;
144
210
  align-items: center;
145
211
  justify-content: center;
212
+
213
+ &.is-preview-fullscreen {
214
+ background: transparent;
215
+ }
146
216
  }
147
217
 
148
218
  .file-view-toolbar {
@@ -163,5 +233,41 @@ const download = async () => {
163
233
  display: flex;
164
234
  flex-direction: column;
165
235
  }
236
+
237
+ &:fullscreen {
238
+ display: flex;
239
+ flex-direction: column;
240
+ width: 100% !important;
241
+ height: 100% !important;
242
+ border-radius: 0;
243
+ background: #f5f5f5 !important;
244
+ }
245
+
246
+ &.fullscreen-mode {
247
+ position: fixed !important;
248
+ inset: 0 !important;
249
+ z-index: 10000;
250
+ display: flex;
251
+ flex-direction: column;
252
+ width: 100vw !important;
253
+ height: 100vh !important;
254
+ border-radius: 0;
255
+ background: #f5f5f5 !important;
256
+ }
257
+
258
+ &:fullscreen,
259
+ &.fullscreen-mode {
260
+ :deep(.office-preview-root) {
261
+ flex: 1;
262
+ min-height: 0;
263
+ height: 100%;
264
+ background: #f5f5f5 !important;
265
+ }
266
+
267
+ :deep(.office-rich-wrap),
268
+ :deep(.pdf-js-viewer) {
269
+ background: #f5f5f5;
270
+ }
271
+ }
166
272
  }
167
273
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="office-preview-root" :class="{ 'is-rich-preview': isRichPreview }">
2
+ <div class="office-preview-root" :class="{ 'is-rich-preview': isRichPreview, 'is-fullscreen': fullscreen }">
3
3
  <template v-if="isRichPreview">
4
4
  <office-preview-header-bar
5
5
  v-if="showHeader"
@@ -19,6 +19,7 @@
19
19
  :show-close="showClose"
20
20
  :fullscreen="fullscreen"
21
21
  :download-loading="downloadLoading"
22
+ :refresh-loading="refreshLoading"
22
23
  :min-zoom="MIN_OFFICE_ZOOM"
23
24
  :max-zoom="MAX_RICH_PREVIEW_ZOOM"
24
25
  @zoom-in="zoomIn"
@@ -71,7 +72,14 @@
71
72
  @error="handleError"
72
73
  @page-change="previewCurrentPage = $event"
73
74
  />
74
- <div v-if="isPreviewLoading && !fileSrc" class="office-rich-loading">
75
+ <div v-if="refreshLoading" class="office-rich-loading">
76
+ <div class="loading-card">
77
+ <span class="loading-spinner" />
78
+ <p class="loading-title">正在重新生成附件,请稍候…</p>
79
+ <p v-if="headerTitle" class="loading-sub">{{ headerTitle }}</p>
80
+ </div>
81
+ </div>
82
+ <div v-else-if="isPreviewLoading && !fileSrc" class="office-rich-loading">
75
83
  <div class="loading-card">
76
84
  <span class="loading-spinner" />
77
85
  <p class="loading-title">{{ loadingMessage }}</p>
@@ -128,6 +136,7 @@ const props = withDefaults(
128
136
  showClose?: boolean
129
137
  fullscreen?: boolean
130
138
  downloadLoading?: boolean
139
+ refreshLoading?: boolean
131
140
  }>(),
132
141
  {
133
142
  showHeader: true,
@@ -140,6 +149,7 @@ const props = withDefaults(
140
149
  showClose: false,
141
150
  fullscreen: false,
142
151
  downloadLoading: false,
152
+ refreshLoading: false,
143
153
  },
144
154
  )
145
155
 
@@ -386,6 +396,19 @@ defineExpose({
386
396
  flex-direction: column;
387
397
  overflow: hidden;
388
398
  }
399
+
400
+ &.is-fullscreen {
401
+ height: 100%;
402
+ background: #f5f5f5 !important;
403
+
404
+ &.is-rich-preview {
405
+ .office-rich-wrap {
406
+ flex: 1;
407
+ min-height: 0;
408
+ background: #f5f5f5;
409
+ }
410
+ }
411
+ }
389
412
  }
390
413
 
391
414
  .office-rich-viewer {
@@ -80,8 +80,16 @@
80
80
  <el-icon v-else><Download /></el-icon>
81
81
  </button>
82
82
 
83
- <button v-if="showRefresh" class="toolbar-btn" type="button" title="刷新附件" @click="emit('refresh')">
84
- <el-icon><Refresh /></el-icon>
83
+ <button
84
+ v-if="showRefresh"
85
+ class="toolbar-btn"
86
+ type="button"
87
+ title="刷新附件"
88
+ :disabled="refreshLoading"
89
+ @click="emit('refresh')"
90
+ >
91
+ <el-icon v-if="refreshLoading" class="is-loading"><Loading /></el-icon>
92
+ <el-icon v-else><Refresh /></el-icon>
85
93
  </button>
86
94
 
87
95
  <button
@@ -145,6 +153,7 @@ const props = withDefaults(
145
153
  showClose?: boolean
146
154
  fullscreen?: boolean
147
155
  downloadLoading?: boolean
156
+ refreshLoading?: boolean
148
157
  minZoom?: number
149
158
  maxZoom?: number
150
159
  }>(),
@@ -165,6 +174,7 @@ const props = withDefaults(
165
174
  showClose: false,
166
175
  fullscreen: false,
167
176
  downloadLoading: false,
177
+ refreshLoading: false,
168
178
  minZoom: MIN_OFFICE_ZOOM,
169
179
  maxZoom: MAX_PDF_ZOOM,
170
180
  },
@@ -104,7 +104,7 @@ const showPlaceholder = computed(
104
104
  const placeholderText = computed(() => {
105
105
  if (isPending.value) return '等待预览区域就绪…'
106
106
  if (totalPages.value > 0) return '正在渲染页面…'
107
- return '正在解析 PDF 文档…'
107
+ return '正在解析文档…'
108
108
  })
109
109
 
110
110
  const setPageRef = (pageNum: number, el: HTMLElement | null) => {