af-mobile-client-vue3 1.6.49 → 1.6.52

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,7 +1,7 @@
1
1
  {
2
2
  "name": "af-mobile-client-vue3",
3
3
  "type": "module",
4
- "version": "1.6.49",
4
+ "version": "1.6.52",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -149,52 +149,54 @@ const resultLabel = computed(() => {
149
149
  :is-link="$attrs.disabled === undefined"
150
150
  @click="showPopu($attrs.readonly)"
151
151
  />
152
- <VanPopup v-model:show="show" position="bottom">
153
- <div class="van-picker__toolbar">
154
- <button type="button" class="van-picker__cancel" @click="cancel">
155
- 取消
156
- </button>
157
- <div class="van-ellipsis van-picker__title">
158
- {{ $attrs.label }}
159
- </div>
160
- <button type="button" class="van-picker__confirm" @click="onConfirm">
161
- 确认
162
- </button>
163
- </div>
164
- <div class="x-multi-select-content">
165
- <VanSearch
166
- v-if="props.isSearch"
167
- v-model="searchVal"
168
- placeholder="搜索"
169
- @update:model-value="search"
170
- @cancel="search"
171
- />
172
- <div v-if="isLoading" class="loading-container">
173
- <div class="loading-text">
174
- 搜索中...
152
+ <VanPopup v-model:show="show" position="bottom" teleport="body" round>
153
+ <div class="x-multi-select-popup">
154
+ <div class="van-picker__toolbar">
155
+ <button type="button" class="van-picker__cancel" @click="cancel">
156
+ 取消
157
+ </button>
158
+ <div class="van-ellipsis van-picker__title">
159
+ {{ $attrs.label }}
175
160
  </div>
161
+ <button type="button" class="van-picker__confirm" @click="onConfirm">
162
+ 确认
163
+ </button>
176
164
  </div>
177
- <div v-else class="x-multi-select-checkbox-group">
178
- <VanCell title="全选">
179
- <template #right-icon>
180
- <VanCheckbox v-model="checkedAll" name="all" @click="toggleAll" />
181
- </template>
182
- </VanCell>
183
- <VanCheckboxGroup ref="checkboxGroup" v-model="checkboxValue" @change="change">
184
- <VanCellGroup>
185
- <VanCell
186
- v-for="(item, index) in columnsData"
187
- :key="item[props.option.value]"
188
- :title="item[props.option.text]"
189
- clickable
190
- @click="toggle(item, index)"
191
- >
192
- <template #right-icon>
193
- <VanCheckbox ref="checkboxes" :name="item[props.option.value]" @click.stop />
194
- </template>
195
- </VanCell>
196
- </VanCellGroup>
197
- </VanCheckboxGroup>
165
+ <div class="x-multi-select-content">
166
+ <VanSearch
167
+ v-if="props.isSearch"
168
+ v-model="searchVal"
169
+ placeholder="搜索"
170
+ @update:model-value="search"
171
+ @cancel="search"
172
+ />
173
+ <div v-if="isLoading" class="loading-container">
174
+ <div class="loading-text">
175
+ 搜索中...
176
+ </div>
177
+ </div>
178
+ <div v-else class="x-multi-select-checkbox-group">
179
+ <VanCell title="全选">
180
+ <template #right-icon>
181
+ <VanCheckbox v-model="checkedAll" name="all" @click="toggleAll" />
182
+ </template>
183
+ </VanCell>
184
+ <VanCheckboxGroup ref="checkboxGroup" v-model="checkboxValue" @change="change">
185
+ <VanCellGroup>
186
+ <VanCell
187
+ v-for="(item, index) in columnsData"
188
+ :key="item[props.option.value]"
189
+ :title="item[props.option.text]"
190
+ clickable
191
+ @click="toggle(item, index)"
192
+ >
193
+ <template #right-icon>
194
+ <VanCheckbox ref="checkboxes" :name="item[props.option.value]" @click.stop />
195
+ </template>
196
+ </VanCell>
197
+ </VanCellGroup>
198
+ </VanCheckboxGroup>
199
+ </div>
198
200
  </div>
199
201
  </div>
200
202
  </VanPopup>
@@ -213,12 +215,19 @@ const resultLabel = computed(() => {
213
215
  left: -8px;
214
216
  }
215
217
  .x-multi-select-content {
216
- max-height: 264px;
218
+ max-height: calc(70vh - 44px);
217
219
  overflow-y: auto;
218
220
  .x-multi-select-checkbox-group {
219
221
  padding: 0 16px;
220
222
  }
221
223
  }
224
+ .x-multi-select-popup {
225
+ max-height: 70vh;
226
+ padding-bottom: env(safe-area-inset-bottom);
227
+ overflow: hidden;
228
+ display: flex;
229
+ flex-direction: column;
230
+ }
222
231
  .loading-container {
223
232
  display: flex;
224
233
  justify-content: center;
@@ -197,7 +197,7 @@ onBeforeUnmount(() => {
197
197
  :border="props.border"
198
198
  @click="showPopu($attrs.readonly)"
199
199
  />
200
- <VanPopup v-model:show="show" position="bottom" teleport="body">
200
+ <VanPopup v-model:show="show" position="bottom" teleport="body" round>
201
201
  <div class="x-select-popup">
202
202
  <!-- 搜索框 -->
203
203
  <VanSearch
@@ -222,6 +222,23 @@ onBeforeUnmount(() => {
222
222
  </template>
223
223
 
224
224
  <style scoped>
225
+ .x-select-popup {
226
+ max-height: 70vh;
227
+ padding-bottom: env(safe-area-inset-bottom);
228
+ overflow: hidden;
229
+ display: flex;
230
+ flex-direction: column;
231
+ }
232
+
233
+ .x-select-popup :deep(.van-picker) {
234
+ flex: 1;
235
+ min-height: 0;
236
+ }
237
+
238
+ .x-select-popup :deep(.van-picker__columns) {
239
+ max-height: 40vh;
240
+ }
241
+
225
242
  .loading-container {
226
243
  display: flex;
227
244
  justify-content: center;
@@ -0,0 +1,257 @@
1
+ <script setup lang="ts">
2
+ import type { FilePreviewInitOptions } from './index.vue'
3
+ /**
4
+ * 文件预览弹窗组件
5
+ * 支持两种展示形式,通过 mode prop 控制:
6
+ * - drawer(默认):从底部滑出的抽屉,占屏幕 92% 高度
7
+ * - dialog:居中弹窗,固定宽高
8
+ *
9
+ * 底部操作栏(仅通过此组件打开时显示):
10
+ * - 下载
11
+ * - 用其他应用打开(仅 Flutter 环境,调用 openFileWithApp 方式二:传 url+name)
12
+ *
13
+ * 用法:
14
+ * <FilePreviewDialog ref="dialogRef" mode="dialog" @close="onClose" />
15
+ * dialogRef.value.open({ path: '/resource/...', name: '文件名.docx' })
16
+ */
17
+ import { detectEnvironment } from '@af-mobile-client-vue3/utils/environment'
18
+ import { downloadAttachment, openFileWithApp } from '@af-mobile-client-vue3/utils/fileDownload'
19
+ import { Popup as VanPopup } from 'vant'
20
+ import { computed, ref } from 'vue'
21
+ import FilePreview from './index.vue'
22
+
23
+ const props = withDefaults(defineProps<{
24
+ /** 展示形式:drawer 底部抽屉(默认)| dialog 居中弹窗 */
25
+ mode?: 'drawer' | 'dialog'
26
+ }>(), {
27
+ mode: 'drawer',
28
+ })
29
+
30
+ const emit = defineEmits<{
31
+ (e: 'close'): void
32
+ }>()
33
+
34
+ const visible = ref(false)
35
+ const fileName = ref('')
36
+ const fileUrl = ref('')
37
+ const previewRef = ref<InstanceType<typeof FilePreview> | null>(null)
38
+ const initOptions = ref<FilePreviewInitOptions | null>(null)
39
+ const loadingKey = ref<string | null>(null) // 当前正在执行的操作 key,null 表示空闲
40
+
41
+ const isApp = computed(() => detectEnvironment().isApp)
42
+
43
+ /** 操作按钮配置,后续新增按钮只需在此追加 */
44
+ interface ActionItem {
45
+ key: string
46
+ label: string
47
+ /** 仅在特定环境显示,不传则始终显示 */
48
+ show?: () => boolean
49
+ handler: () => Promise<void> | void
50
+ }
51
+
52
+ const actions = computed<ActionItem[]>(() => [
53
+ {
54
+ key: 'download',
55
+ label: '下载',
56
+ handler: handleDownload,
57
+ },
58
+ {
59
+ key: 'openWithApp',
60
+ label: '用其他应用打开',
61
+ // 仅 Flutter App 环境显示
62
+ show: () => isApp.value,
63
+ handler: handleOpenWithApp,
64
+ },
65
+ ])
66
+
67
+ /** 过滤出当前环境应显示的按钮 */
68
+ const visibleActions = computed(() =>
69
+ actions.value.filter(a => !a.show || a.show()),
70
+ )
71
+
72
+ function open(options: FilePreviewInitOptions) {
73
+ fileName.value = options.name || options.path.split('/').pop()?.split('?')[0] || '文件预览'
74
+ fileUrl.value = options.path
75
+ initOptions.value = options
76
+ visible.value = true
77
+ }
78
+
79
+ function onOpened() {
80
+ if (initOptions.value)
81
+ previewRef.value?.init(initOptions.value)
82
+ }
83
+
84
+ function handleClose() {
85
+ visible.value = false
86
+ emit('close')
87
+ }
88
+
89
+ /** 下载文件 */
90
+ async function handleDownload() {
91
+ await downloadAttachment({ url: fileUrl.value, name: fileName.value })
92
+ }
93
+
94
+ /**
95
+ * 用其他应用打开(方式二:传 url+name,Flutter 端自动下载后调起系统分享面板)
96
+ */
97
+ function handleOpenWithApp() {
98
+ openFileWithApp(fileUrl.value, fileName.value)
99
+ }
100
+
101
+ /** 执行操作按钮,防重复点击 */
102
+ async function runAction(action: ActionItem) {
103
+ if (loadingKey.value)
104
+ return
105
+ loadingKey.value = action.key
106
+ try {
107
+ await action.handler()
108
+ }
109
+ finally {
110
+ loadingKey.value = null
111
+ }
112
+ }
113
+
114
+ defineExpose({ open })
115
+ </script>
116
+
117
+ <template>
118
+ <VanPopup
119
+ v-model:show="visible"
120
+ :position="mode === 'drawer' ? 'bottom' : 'center'"
121
+ :style="mode === 'drawer'
122
+ ? { height: '92vh' }
123
+ : { width: '90vw', height: '80vh', borderRadius: '12px' }"
124
+ :round="mode === 'drawer'"
125
+ @opened="onOpened"
126
+ >
127
+ <div class="dialog-root">
128
+ <!-- 顶部标题栏 -->
129
+ <div class="dialog-header" :class="{ 'dialog-header--center': mode === 'dialog' }">
130
+ <span class="dialog-title" :title="fileName">{{ fileName }}</span>
131
+ <button class="dialog-close" @click="handleClose">
132
+
133
+ </button>
134
+ </div>
135
+
136
+ <!-- 预览区域 -->
137
+ <div class="dialog-body">
138
+ <FilePreview ref="previewRef" class="dialog-preview" />
139
+ </div>
140
+
141
+ <!-- 底部操作栏 -->
142
+ <div class="dialog-actions">
143
+ <button
144
+ v-for="action in visibleActions"
145
+ :key="action.key"
146
+ class="action-btn"
147
+ :class="`action-btn--${action.key}`"
148
+ :disabled="!!loadingKey"
149
+ @pointerdown.stop="runAction(action)"
150
+ >
151
+ {{ loadingKey === action.key ? '处理中...' : action.label }}
152
+ </button>
153
+ </div>
154
+ </div>
155
+ </VanPopup>
156
+ </template>
157
+
158
+ <style scoped>
159
+ .dialog-root {
160
+ display: flex;
161
+ flex-direction: column;
162
+ height: 100%;
163
+ overflow: hidden;
164
+ }
165
+
166
+ .dialog-header {
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: space-between;
170
+ padding: 0 16px;
171
+ height: 48px;
172
+ background: #1677ff;
173
+ flex-shrink: 0;
174
+ border-radius: 16px 16px 0 0;
175
+ }
176
+
177
+ .dialog-header--center {
178
+ border-radius: 12px 12px 0 0;
179
+ }
180
+
181
+ .dialog-title {
182
+ flex: 1;
183
+ font-size: 16px;
184
+ font-weight: 500;
185
+ color: #fff;
186
+ white-space: nowrap;
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ margin-right: 12px;
190
+ }
191
+
192
+ .dialog-close {
193
+ width: 28px;
194
+ height: 28px;
195
+ display: flex;
196
+ align-items: center;
197
+ justify-content: center;
198
+ background: transparent;
199
+ border: none;
200
+ color: #fff;
201
+ font-size: 16px;
202
+ cursor: pointer;
203
+ padding: 0;
204
+ flex-shrink: 0;
205
+ }
206
+
207
+ .dialog-body {
208
+ flex: 1;
209
+ overflow: hidden;
210
+ display: flex;
211
+ flex-direction: column;
212
+ min-height: 0;
213
+ }
214
+
215
+ .dialog-preview {
216
+ flex: 1;
217
+ height: 100%;
218
+ }
219
+
220
+ /* 底部操作栏:按钮数量自适应 */
221
+ .dialog-actions {
222
+ display: flex;
223
+ gap: 10px;
224
+ padding: 10px 16px;
225
+ padding-bottom: calc(10px + env(safe-area-inset-bottom));
226
+ border-top: 1px solid #f0f0f0;
227
+ flex-shrink: 0;
228
+ background: #fff;
229
+ }
230
+
231
+ .action-btn {
232
+ flex: 1;
233
+ height: 40px;
234
+ border: none;
235
+ border-radius: 8px;
236
+ font-size: 14px;
237
+ cursor: pointer;
238
+ transition: opacity 0.15s;
239
+ }
240
+
241
+ .action-btn:disabled {
242
+ opacity: 0.5;
243
+ }
244
+
245
+ /* 下载:主色蓝 */
246
+ .action-btn--download {
247
+ background: #1677ff;
248
+ color: #fff;
249
+ }
250
+
251
+ /* 用其他应用打开:次级灰白 */
252
+ .action-btn--openWithApp {
253
+ background: #f5f5f5;
254
+ color: #333;
255
+ border: 1px solid #e0e0e0;
256
+ }
257
+ </style>
@@ -68,6 +68,15 @@ onMounted(buildPreviewUrl)
68
68
  position: relative;
69
69
  }
70
70
 
71
+ /* 透明遮罩:阻止 iframe 在点击悬浮按钮时抢夺事件 */
72
+ .preview-doc-container::after {
73
+ content: '';
74
+ position: absolute;
75
+ inset: 0;
76
+ z-index: 99;
77
+ pointer-events: none;
78
+ }
79
+
71
80
  .kk-loading {
72
81
  position: absolute;
73
82
  inset: 0;
@@ -39,13 +39,14 @@ const VueOfficeExcel = defineAsyncComponent(async () => {
39
39
  class="office-viewer"
40
40
  @error="emit('error')"
41
41
  />
42
- <!-- docx / doc 共用 docx 渲染器;.doc 若解析失败会触发 error 降级到 kk -->
43
- <VueOfficeDocx
44
- v-else-if="kind === 'docx' || kind === 'doc'"
45
- :src="src"
46
- class="office-viewer"
47
- @error="emit('error')"
48
- />
42
+ <!-- docx 单独用缩放容器,整体缩小至 85% 以适配移动端屏幕 -->
43
+ <div v-else-if="kind === 'docx' || kind === 'doc'" class="docx-scale-wrapper">
44
+ <VueOfficeDocx
45
+ :src="src"
46
+ class="office-viewer docx-scaled"
47
+ @error="emit('error')"
48
+ />
49
+ </div>
49
50
  <!-- xlsx / xls 共用 excel 渲染器 -->
50
51
  <VueOfficeExcel
51
52
  v-else-if="kind === 'xlsx' || kind === 'xls'"
@@ -62,4 +63,20 @@ const VueOfficeExcel = defineAsyncComponent(async () => {
62
63
  min-height: 200px;
63
64
  overflow: auto;
64
65
  }
66
+
67
+ /* docx 缩放容器:裁掉 scale 产生的右侧和底部留白 */
68
+ .docx-scale-wrapper {
69
+ flex: 1;
70
+ overflow: hidden;
71
+ width: 100%;
72
+ }
73
+
74
+ /* 整体缩放 85%,transform-origin 从左上角开始,保持左对齐 */
75
+ .docx-scaled {
76
+ transform: scale(0.7);
77
+ transform-origin: top left;
78
+ /* scale 不影响布局,需手动补偿宽度和高度 */
79
+ width: calc(100% / 0.7);
80
+ min-height: calc(100% / 0.7);
81
+ }
65
82
  </style>
@@ -5,12 +5,10 @@
5
5
  * 渲染策略:
6
6
  * 1. image / video / audio → MediaPreview(原生标签)
7
7
  * 2. text → TextPreview(<pre>)
8
- * 3. pdf / docx / doc / xlsx / xls → OfficePreview(@vue-office
9
- * └─ @vue-office 渲染失败自动降级到 KkPreview(kkFileView iframe)
10
- * 4. doc 旧格式 / unsupported → 直接走 KkPreview
8
+ * 3. pdf / docx / xlsx → OfficePreview(@vue-office),失败自动降级到 KkPreview
9
+ * 4. doc / xls / unsupported直接走 KkPreview
11
10
  */
12
11
  import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
13
- import { downloadAttachment } from '@af-mobile-client-vue3/utils/fileDownload'
14
12
  import { getPreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
15
13
  import {
16
14
  fetchResourceArrayBuffer,
@@ -18,7 +16,7 @@ import {
18
16
  getPreviewPath,
19
17
  resolveResourceUrl,
20
18
  } from '@af-mobile-client-vue3/utils/resourceUrl'
21
- import { Button as VanButton, Empty as VanEmpty, Loading as VanLoading } from 'vant'
19
+ import { Empty as VanEmpty, Loading as VanLoading } from 'vant'
22
20
  import { ref, shallowRef } from 'vue'
23
21
  import KkPreview from './components/KkPreview.vue'
24
22
  import MediaPreview from './components/MediaPreview.vue'
@@ -28,17 +26,16 @@ import TextPreview from './components/TextPreview.vue'
28
26
  export interface FilePreviewInitOptions {
29
27
  /** 后端 f_downloadpath 或完整 URL */
30
28
  path: string
31
- /** 下载时使用的文件名(可选,默认从路径提取) */
29
+ /** 文件名(可选,默认从路径提取) */
32
30
  name?: string
33
31
  }
34
32
 
35
- /** office 类型集合,这些类型优先走 @vue-office,失败降级 kk */
33
+ /** 优先走 @vue-office,失败降级 kk */
36
34
  const OFFICE_KINDS: PreviewFileKind[] = ['pdf', 'docx', 'xlsx']
37
35
 
38
36
  /**
39
- * 直接走 kk 预览的格式,不尝试 @vue-office:
40
- * - doc 旧二进制格式,@vue-office 无法解析
41
- * - xls 旧二进制格式,@vue-office 解析效果差
37
+ * 直接走 kk 预览的格式:
38
+ * - doc / xls 旧二进制格式,@vue-office 无法解析
42
39
  * - unsupported 未知格式,交给 kkFileView 服务端判断
43
40
  */
44
41
  const KK_ONLY_KINDS: PreviewFileKind[] = ['doc', 'xls', 'unsupported']
@@ -47,15 +44,12 @@ const kind = ref<PreviewFileKind>('unsupported')
47
44
  const fileSource = shallowRef<string | ArrayBuffer>('')
48
45
  const textContent = ref('')
49
46
  const mediaSrc = ref('')
50
- const rawUrl = ref('') // 文件原始 URL(kk 预览和下载共用)
51
47
  const rawPath = ref('') // 文件相对路径(kk 预览用)
52
- const downloadName = ref('')
53
48
  const loading = ref(false)
54
49
  const errorMsg = ref('')
55
50
  /** true = office 渲染失败,已降级到 kk 预览 */
56
51
  const officeFailed = ref(false)
57
52
 
58
- /** office 渲染失败回调,切换到 kk 预览 */
59
53
  function onOfficeFailed() {
60
54
  officeFailed.value = true
61
55
  }
@@ -67,11 +61,9 @@ async function init(options: FilePreviewInitOptions) {
67
61
  return
68
62
  }
69
63
 
70
- // 重置所有状态
64
+ const rawUrl = resolveResourceUrl(path)
71
65
  kind.value = getPreviewFileKind(path)
72
- rawUrl.value = resolveResourceUrl(path)
73
66
  rawPath.value = path
74
- downloadName.value = options.name || path.split('/').pop()?.split('?')[0] || '附件'
75
67
  loading.value = true
76
68
  errorMsg.value = ''
77
69
  officeFailed.value = false
@@ -80,17 +72,16 @@ async function init(options: FilePreviewInitOptions) {
80
72
  mediaSrc.value = ''
81
73
 
82
74
  try {
83
- if (kind.value === 'image' || kind.value === 'video' || kind.value === 'audio') {
84
- mediaSrc.value = rawUrl.value
75
+ if (['image', 'video', 'audio'].includes(kind.value)) {
76
+ mediaSrc.value = rawUrl
85
77
  }
86
78
  else if (kind.value === 'text') {
87
- textContent.value = await fetchResourceText(rawUrl.value)
79
+ textContent.value = await fetchResourceText(rawUrl)
88
80
  }
89
81
  else if (OFFICE_KINDS.includes(kind.value)) {
90
- // 预加载文件二进制,交给 @vue-office 渲染
91
- fileSource.value = await fetchResourceArrayBuffer(rawUrl.value)
82
+ fileSource.value = await fetchResourceArrayBuffer(rawUrl)
92
83
  }
93
- // doc / unsupported 直接走 KkPreview,无需预加载
84
+ // doc / xls / unsupported 直接走 KkPreview,无需预加载
94
85
  }
95
86
  catch (e: any) {
96
87
  errorMsg.value = e?.message || '文件加载失败'
@@ -100,52 +91,32 @@ async function init(options: FilePreviewInitOptions) {
100
91
  }
101
92
  }
102
93
 
103
- function handleDownload() {
104
- downloadAttachment({ url: rawUrl.value, name: downloadName.value })
105
- }
106
-
107
- defineExpose({ init, download: handleDownload })
94
+ defineExpose({ init })
108
95
  </script>
109
96
 
110
97
  <template>
111
98
  <div class="file-preview-root">
112
- <!-- 悬浮下载按钮,定位在预览区右上角 -->
113
- <div v-if="rawUrl && !loading" class="preview-download-btn">
114
- <VanButton size="small" type="primary" plain icon="down" @click="handleDownload">
115
- 下载
116
- </VanButton>
117
- </div>
118
-
119
99
  <!-- 加载中 -->
120
100
  <VanLoading v-if="loading" class="preview-loading" type="spinner" vertical>
121
101
  加载中...
122
102
  </VanLoading>
123
103
 
124
104
  <!-- 加载/解析出错 -->
125
- <VanEmpty v-else-if="errorMsg" :description="errorMsg">
126
- <VanButton v-if="rawUrl" size="small" type="primary" @click="handleDownload">
127
- 下载
128
- </VanButton>
129
- </VanEmpty>
105
+ <VanEmpty v-else-if="errorMsg" :description="errorMsg" />
130
106
 
131
107
  <template v-else>
132
- <!-- 媒体:图片 / 视频 / 音频 -->
133
108
  <MediaPreview
134
109
  v-if="kind === 'image' || kind === 'video' || kind === 'audio'"
135
110
  :kind="kind"
136
111
  :src="mediaSrc"
137
112
  />
138
113
 
139
- <!-- 纯文本 -->
140
114
  <TextPreview
141
115
  v-else-if="kind === 'text'"
142
116
  :content="textContent"
143
117
  />
144
118
 
145
- <!--
146
- Office 文档:先走 @vue-office
147
- 失败(officeFailed)或 doc/unsupported 直接走 KkPreview
148
- -->
119
+ <!-- office:先 @vue-office,失败降级 kk -->
149
120
  <OfficePreview
150
121
  v-else-if="OFFICE_KINDS.includes(kind) && fileSource && !officeFailed"
151
122
  :kind="kind"
@@ -153,18 +124,13 @@ defineExpose({ init, download: handleDownload })
153
124
  @error="onOfficeFailed"
154
125
  />
155
126
 
156
- <!-- kk 预览:office 降级 或 doc/unsupported 直接走 -->
127
+ <!-- kk 预览:office 降级 或 doc/xls/unsupported 直接走 -->
157
128
  <KkPreview
158
129
  v-else-if="officeFailed || KK_ONLY_KINDS.includes(kind)"
159
130
  :file-path="rawPath"
160
131
  />
161
132
 
162
- <!-- 兜底:路径异常 -->
163
- <VanEmpty v-else description="文件路径异常">
164
- <VanButton v-if="rawUrl" size="small" type="primary" @click="handleDownload">
165
- 下载
166
- </VanButton>
167
- </VanEmpty>
133
+ <VanEmpty v-else description="文件路径异常" />
168
134
  </template>
169
135
  </div>
170
136
  </template>
@@ -178,15 +144,6 @@ defineExpose({ init, download: handleDownload })
178
144
  flex-direction: column;
179
145
  background: #fff;
180
146
  overflow: hidden;
181
- /* 作为悬浮按钮的定位基准 */
182
- position: relative;
183
- }
184
-
185
- .preview-download-btn {
186
- position: absolute;
187
- top: 10px;
188
- right: 12px;
189
- z-index: 10;
190
147
  }
191
148
 
192
149
  .preview-loading {