af-mobile-client-vue3 1.6.48 → 1.6.49

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/.env CHANGED
@@ -5,7 +5,7 @@ VITE_APP_OUT_DIR=dist_af-library-mobile-web
5
5
  VITE_APP_VCONSOLE=false
6
6
  # 自定义参数
7
7
  VITE_APP_WEB_CONFIG_KEY=admin.webconfig
8
- VITE_APP_SYSTEM_NAME=af-safecheck
8
+ VITE_APP_SYSTEM_NAME=af-system
9
9
  # 最低兼容性 V4(最新产品)V3(V3产品) OA(公司OA)
10
10
  VITE_APP_COMPATIBLE=V4
11
11
  VITE_RSA_PRIVATE_KEY=MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIRrmftLDHCQqREEJ132Onu+W3vmFbdF7QD751SrcDDGDTfzuz1zBuElvkHhuDBb7KZkXrCIe+MhvX2IvxcLObl3faX+evYlnfj2HRbF0hIpQLuIq22tL06ZcV5w7wqLxUZRpFElIFm8gZTkUvfKXVuHw89e4daDVhU5hK3GHNGTAgMBAAECgYABiINrFaE1E8pkBYx1JJA5yuhL73aUktfd2TeCU00vFg6kyrWCI85Sa2RKu/6CJNZWeOFgdubEUv7a22tRrNIZb3yUMaqtTwSso78mspIOJqjWXTkTH9WPElfTcdpdIse/lgZtPz6egxkuhadSvwrM9Y6NgusiW/5+x95Ct08iOQJBAN5aK+7uISURvGQj2EaRtgGEd8+d4oHl+BYvvTeG3qSgUikHQW3j0sp4gXPw2kxw6sjVgLFOc4FB6LGqwzOTzokCQQCYdYG8ty3Uo/ebUlNzeJFxHXjy/KvBSytAUzAXkRu3nZrkEaPQsi3dgOkZgk+F1fMDzfQ4EbDIU6xvqOoZXHg7AkATCW9XfoXR8anKfRMoP5Nwn9HOMbtR2cmaxK2TknV/bMZ8AsYETYwfj5+tuIJIJybC2RyykX/sIiN1CqS5xr7ZAkArj19rMRdaKyMi8MnBM1Cy9g3Jt2HHj5ejAGG8SgyWUOShh1y70z0BjcSMMkxQXAncK2s83ekZw7aADM4eQupjAkARRgTwwMOnn3IoKmQusKhZk0uxilZ4Zc2LH6Z4GiWnvteM0W8Zw4Z1lJUcjgQq3dGqL2RdmzeQZ+HgPIOXrZVK
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.48",
4
+ "version": "1.6.49",
5
5
  "packageManager": "pnpm@10.13.1",
6
6
  "description": "Vue + Vite component lib",
7
7
  "engines": {
@@ -0,0 +1,79 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * kkFileView 预览子组件
4
+ * 通过 iframe 加载 kkFileView 服务端预览页面
5
+ * 适用于 office 渲染器无法处理的格式(如 .doc / .xls 旧格式)或 office 渲染失败的降级场景
6
+ */
7
+ import { getConfigByNameAsync } from '@af-mobile-client-vue3/services/api/common'
8
+ import { Base64 } from 'js-base64'
9
+ import { Loading as VanLoading } from 'vant'
10
+ import { onMounted, ref } from 'vue'
11
+
12
+ const props = defineProps<{
13
+ /** 文件原始路径(相对路径或完整 URL) */
14
+ filePath: string
15
+ }>()
16
+
17
+ const iframeUrl = ref('')
18
+ const iframeLoading = ref(true)
19
+
20
+ function isHttp(path: string) {
21
+ return /^https?:\/\//i.test(path)
22
+ }
23
+
24
+ /** 读取 kkFileView 服务配置,拼接预览 URL */
25
+ async function buildPreviewUrl() {
26
+ try {
27
+ const res: any = await getConfigByNameAsync('previewDocServiceConfig')
28
+ if (!res?.previewDocService || !res?.fileServer)
29
+ throw new Error('previewDocServiceConfig 配置缺失')
30
+
31
+ const sourceUrl = isHttp(props.filePath)
32
+ ? props.filePath
33
+ : `${res.fileServer}${props.filePath}`
34
+
35
+ // kkFileView 要求 url 参数为 Base64 编码后再 encodeURIComponent
36
+ iframeUrl.value = `${res.previewDocService}${encodeURIComponent(Base64.encode(sourceUrl))}`
37
+ }
38
+ catch (e) {
39
+ console.warn('[KkPreview] 配置加载失败,降级直接加载原始地址', e)
40
+ // 服务未配置时,直接把原始地址塞给 iframe
41
+ iframeUrl.value = isHttp(props.filePath) ? props.filePath : ''
42
+ }
43
+ }
44
+
45
+ onMounted(buildPreviewUrl)
46
+ </script>
47
+
48
+ <template>
49
+ <div class="preview-doc-container">
50
+ <VanLoading v-if="iframeLoading" class="kk-loading" type="spinner" vertical>
51
+ 加载中...
52
+ </VanLoading>
53
+ <iframe
54
+ v-if="iframeUrl"
55
+ v-show="!iframeLoading"
56
+ :src="iframeUrl"
57
+ width="100%"
58
+ height="100%"
59
+ frameborder="0"
60
+ @load="iframeLoading = false"
61
+ />
62
+ </div>
63
+ </template>
64
+
65
+ <style scoped>
66
+ .preview-doc-container {
67
+ height: 100%;
68
+ position: relative;
69
+ }
70
+
71
+ .kk-loading {
72
+ position: absolute;
73
+ inset: 0;
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ z-index: 1;
78
+ }
79
+ </style>
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 媒体预览子组件
4
+ * 处理 image / video / audio 三种类型,直接用原生 HTML 标签渲染
5
+ */
6
+ import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
7
+
8
+ defineProps<{
9
+ kind: PreviewFileKind
10
+ src: string
11
+ }>()
12
+ </script>
13
+
14
+ <template>
15
+ <div v-if="kind === 'image'" class="media-wrapper">
16
+ <img :src="src" alt="预览图片" class="media-image">
17
+ </div>
18
+
19
+ <div v-else-if="kind === 'video'" class="media-wrapper media-dark">
20
+ <video class="media-video" controls :src="src" />
21
+ </div>
22
+
23
+ <div v-else-if="kind === 'audio'" class="media-audio-wrapper">
24
+ <audio class="media-audio" controls :src="src" />
25
+ </div>
26
+ </template>
27
+
28
+ <style scoped>
29
+ .media-wrapper {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ flex: 1;
34
+ min-height: 200px;
35
+ }
36
+
37
+ .media-dark {
38
+ background: #000;
39
+ }
40
+
41
+ .media-image {
42
+ max-width: 100%;
43
+ max-height: 100%;
44
+ object-fit: contain;
45
+ }
46
+
47
+ .media-video {
48
+ width: 100%;
49
+ max-height: 100%;
50
+ border: none;
51
+ }
52
+
53
+ .media-audio-wrapper {
54
+ display: flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ padding: 32px 16px;
58
+ }
59
+
60
+ .media-audio {
61
+ width: 100%;
62
+ max-width: 480px;
63
+ }
64
+ </style>
@@ -0,0 +1,65 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Office 文档预览子组件
4
+ * 使用 @vue-office 系列库渲染 pdf / docx / xlsx / xls / doc
5
+ * 渲染失败时 emit 'error',由父组件决定是否降级到 kk 预览
6
+ */
7
+ import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
8
+ import { defineAsyncComponent } from 'vue'
9
+
10
+ const props = defineProps<{
11
+ kind: PreviewFileKind
12
+ src: string | ArrayBuffer
13
+ }>()
14
+
15
+ const emit = defineEmits<{
16
+ (e: 'error'): void
17
+ }>()
18
+
19
+ // 按需异步加载,避免影响首屏
20
+ const VueOfficePdf = defineAsyncComponent(() =>
21
+ import('@vue-office/pdf/lib/v3/vue-office-pdf.mjs').then(m => m.default),
22
+ )
23
+
24
+ const VueOfficeDocx = defineAsyncComponent(async () => {
25
+ await import('@vue-office/docx/lib/v3/index.css')
26
+ return import('@vue-office/docx/lib/v3/vue-office-docx.mjs').then(m => m.default)
27
+ })
28
+
29
+ const VueOfficeExcel = defineAsyncComponent(async () => {
30
+ await import('@vue-office/excel/lib/v3/index.css')
31
+ return import('@vue-office/excel/lib/v3/vue-office-excel.mjs').then(m => m.default)
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <VueOfficePdf
37
+ v-if="kind === 'pdf'"
38
+ :src="src"
39
+ class="office-viewer"
40
+ @error="emit('error')"
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
+ />
49
+ <!-- xlsx / xls 共用 excel 渲染器 -->
50
+ <VueOfficeExcel
51
+ v-else-if="kind === 'xlsx' || kind === 'xls'"
52
+ :src="src"
53
+ class="office-viewer"
54
+ @error="emit('error')"
55
+ />
56
+ </template>
57
+
58
+ <style scoped>
59
+ .office-viewer {
60
+ width: 100%;
61
+ flex: 1;
62
+ min-height: 200px;
63
+ overflow: auto;
64
+ }
65
+ </style>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * 纯文本预览子组件
4
+ * 直接渲染文本内容,保留空白和换行
5
+ */
6
+ defineProps<{
7
+ content: string
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <pre class="text-preview">{{ content }}</pre>
13
+ </template>
14
+
15
+ <style scoped>
16
+ .text-preview {
17
+ margin: 0;
18
+ padding: 12px;
19
+ white-space: pre-wrap;
20
+ word-break: break-word;
21
+ font-size: 13px;
22
+ line-height: 1.5;
23
+ color: #333;
24
+ overflow: auto;
25
+ flex: 1;
26
+ }
27
+ </style>
@@ -1,4 +1,14 @@
1
1
  <script setup lang="ts">
2
+ /**
3
+ * 文件预览组件(主入口)
4
+ *
5
+ * 渲染策略:
6
+ * 1. image / video / audio → MediaPreview(原生标签)
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
11
+ */
2
12
  import type { PreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
3
13
  import { downloadAttachment } from '@af-mobile-client-vue3/utils/fileDownload'
4
14
  import { getPreviewFileKind } from '@af-mobile-client-vue3/utils/fileType'
@@ -9,96 +19,78 @@ import {
9
19
  resolveResourceUrl,
10
20
  } from '@af-mobile-client-vue3/utils/resourceUrl'
11
21
  import { Button as VanButton, Empty as VanEmpty, Loading as VanLoading } from 'vant'
12
- import { defineAsyncComponent, ref, shallowRef } from 'vue'
22
+ import { ref, shallowRef } from 'vue'
23
+ import KkPreview from './components/KkPreview.vue'
24
+ import MediaPreview from './components/MediaPreview.vue'
25
+ import OfficePreview from './components/OfficePreview.vue'
26
+ import TextPreview from './components/TextPreview.vue'
13
27
 
14
28
  export interface FilePreviewInitOptions {
15
29
  /** 后端 f_downloadpath 或完整 URL */
16
30
  path: string
17
- /** 下载时使用的文件名 */
31
+ /** 下载时使用的文件名(可选,默认从路径提取) */
18
32
  name?: string
19
33
  }
20
34
 
21
- const VueOfficePdf = defineAsyncComponent(async () => {
22
- const mod = await import('@vue-office/pdf/lib/v3/vue-office-pdf.mjs')
23
- return mod.default
24
- })
35
+ /** office 类型集合,这些类型优先走 @vue-office,失败降级 kk */
36
+ const OFFICE_KINDS: PreviewFileKind[] = ['pdf', 'docx', 'xlsx']
25
37
 
26
- const VueOfficeDocx = defineAsyncComponent(async () => {
27
- await import('@vue-office/docx/lib/v3/index.css')
28
- const mod = await import('@vue-office/docx/lib/v3/vue-office-docx.mjs')
29
- return mod.default
30
- })
38
+ /**
39
+ * 直接走 kk 预览的格式,不尝试 @vue-office
40
+ * - doc 旧二进制格式,@vue-office 无法解析
41
+ * - xls 旧二进制格式,@vue-office 解析效果差
42
+ * - unsupported 未知格式,交给 kkFileView 服务端判断
43
+ */
44
+ const KK_ONLY_KINDS: PreviewFileKind[] = ['doc', 'xls', 'unsupported']
31
45
 
32
- const VueOfficeExcel = defineAsyncComponent(async () => {
33
- await import('@vue-office/excel/lib/v3/index.css')
34
- const mod = await import('@vue-office/excel/lib/v3/vue-office-excel.mjs')
35
- return mod.default
36
- })
37
-
38
- const previewKind = ref<PreviewFileKind>('unsupported')
46
+ const kind = ref<PreviewFileKind>('unsupported')
39
47
  const fileSource = shallowRef<string | ArrayBuffer>('')
40
48
  const textContent = ref('')
41
- const imageSrc = ref('')
42
- const videoSrc = ref('')
43
- const audioSrc = ref('')
44
- const openUrl = ref('')
49
+ const mediaSrc = ref('')
50
+ const rawUrl = ref('') // 文件原始 URL(kk 预览和下载共用)
51
+ const rawPath = ref('') // 文件相对路径(kk 预览用)
45
52
  const downloadName = ref('')
46
53
  const loading = ref(false)
47
54
  const errorMsg = ref('')
55
+ /** true = office 渲染失败,已降级到 kk 预览 */
56
+ const officeFailed = ref(false)
48
57
 
49
- function onRenderError() {
50
- errorMsg.value = '文件预览渲染失败,可尝试下载查看'
58
+ /** office 渲染失败回调,切换到 kk 预览 */
59
+ function onOfficeFailed() {
60
+ officeFailed.value = true
51
61
  }
52
62
 
53
63
  async function init(options: FilePreviewInitOptions) {
54
64
  const path = getPreviewPath(options.path)
55
65
  if (!path) {
56
66
  errorMsg.value = '文件路径为空'
57
- previewKind.value = 'unsupported'
58
67
  return
59
68
  }
60
69
 
61
- openUrl.value = resolveResourceUrl(path)
70
+ // 重置所有状态
71
+ kind.value = getPreviewFileKind(path)
72
+ rawUrl.value = resolveResourceUrl(path)
73
+ rawPath.value = path
62
74
  downloadName.value = options.name || path.split('/').pop()?.split('?')[0] || '附件'
63
- previewKind.value = getPreviewFileKind(path)
64
75
  loading.value = true
65
76
  errorMsg.value = ''
66
- textContent.value = ''
77
+ officeFailed.value = false
67
78
  fileSource.value = ''
68
- imageSrc.value = ''
69
- videoSrc.value = ''
70
- audioSrc.value = ''
79
+ textContent.value = ''
80
+ mediaSrc.value = ''
71
81
 
72
82
  try {
73
- switch (previewKind.value) {
74
- case 'image':
75
- imageSrc.value = openUrl.value
76
- break
77
-
78
- case 'video':
79
- videoSrc.value = openUrl.value
80
- break
81
-
82
- case 'audio':
83
- audioSrc.value = openUrl.value
84
- break
85
-
86
- case 'text':
87
- textContent.value = await fetchResourceText(openUrl.value)
88
- break
89
-
90
- case 'pdf':
91
- case 'docx':
92
- case 'doc':
93
- case 'xlsx':
94
- case 'xls':
95
- fileSource.value = await fetchResourceArrayBuffer(openUrl.value)
96
- break
97
-
98
- case 'unsupported':
99
- // 未知格式,直接用 iframe 尝试打开,无需预加载内容
100
- break
83
+ if (kind.value === 'image' || kind.value === 'video' || kind.value === 'audio') {
84
+ mediaSrc.value = rawUrl.value
85
+ }
86
+ else if (kind.value === 'text') {
87
+ textContent.value = await fetchResourceText(rawUrl.value)
88
+ }
89
+ else if (OFFICE_KINDS.includes(kind.value)) {
90
+ // 预加载文件二进制,交给 @vue-office 渲染
91
+ fileSource.value = await fetchResourceArrayBuffer(rawUrl.value)
101
92
  }
93
+ // doc / unsupported 直接走 KkPreview,无需预加载
102
94
  }
103
95
  catch (e: any) {
104
96
  errorMsg.value = e?.message || '文件加载失败'
@@ -109,85 +101,67 @@ async function init(options: FilePreviewInitOptions) {
109
101
  }
110
102
 
111
103
  function handleDownload() {
112
- if (!openUrl.value)
113
- return
114
- downloadAttachment({ url: openUrl.value, name: downloadName.value })
104
+ downloadAttachment({ url: rawUrl.value, name: downloadName.value })
115
105
  }
116
106
 
117
- defineExpose({
118
- init,
119
- download: handleDownload,
120
- })
107
+ defineExpose({ init, download: handleDownload })
121
108
  </script>
122
109
 
123
110
  <template>
124
111
  <div class="file-preview-root">
125
- <div v-if="openUrl && !loading" class="preview-toolbar">
112
+ <!-- 悬浮下载按钮,定位在预览区右上角 -->
113
+ <div v-if="rawUrl && !loading" class="preview-download-btn">
126
114
  <VanButton size="small" type="primary" plain icon="down" @click="handleDownload">
127
115
  下载
128
116
  </VanButton>
129
117
  </div>
130
118
 
119
+ <!-- 加载中 -->
131
120
  <VanLoading v-if="loading" class="preview-loading" type="spinner" vertical>
132
121
  加载中...
133
122
  </VanLoading>
134
123
 
124
+ <!-- 加载/解析出错 -->
135
125
  <VanEmpty v-else-if="errorMsg" :description="errorMsg">
136
- <template v-if="openUrl" #default>
137
- <VanButton size="small" type="primary" @click="handleDownload">
138
- 下载
139
- </VanButton>
140
- </template>
126
+ <VanButton v-if="rawUrl" size="small" type="primary" @click="handleDownload">
127
+ 下载
128
+ </VanButton>
141
129
  </VanEmpty>
142
130
 
143
131
  <template v-else>
144
- <VueOfficePdf
145
- v-if="previewKind === 'pdf' && fileSource"
146
- :src="fileSource"
147
- class="office-preview"
148
- @error="onRenderError"
132
+ <!-- 媒体:图片 / 视频 / 音频 -->
133
+ <MediaPreview
134
+ v-if="kind === 'image' || kind === 'video' || kind === 'audio'"
135
+ :kind="kind"
136
+ :src="mediaSrc"
149
137
  />
150
- <!-- docx / doc 共用同一渲染器 -->
151
- <VueOfficeDocx
152
- v-else-if="(previewKind === 'docx' || previewKind === 'doc') && fileSource"
153
- :src="fileSource"
154
- class="office-preview"
155
- @error="onRenderError"
138
+
139
+ <!-- 纯文本 -->
140
+ <TextPreview
141
+ v-else-if="kind === 'text'"
142
+ :content="textContent"
156
143
  />
157
- <!-- xlsx / xls 共用同一渲染器 -->
158
- <VueOfficeExcel
159
- v-else-if="(previewKind === 'xlsx' || previewKind === 'xls') && fileSource"
144
+
145
+ <!--
146
+ Office 文档:先走 @vue-office
147
+ 失败(officeFailed)或 doc/unsupported 直接走 KkPreview
148
+ -->
149
+ <OfficePreview
150
+ v-else-if="OFFICE_KINDS.includes(kind) && fileSource && !officeFailed"
151
+ :kind="kind"
160
152
  :src="fileSource"
161
- class="office-preview"
162
- @error="onRenderError"
153
+ @error="onOfficeFailed"
163
154
  />
164
- <div v-else-if="previewKind === 'image'" class="preview-image-wrapper">
165
- <img :src="imageSrc" alt="image" class="preview-image">
166
- </div>
167
- <div v-else-if="previewKind === 'video'" class="preview-video-wrapper">
168
- <video class="preview-video" controls :src="videoSrc" />
169
- </div>
170
- <div v-else-if="previewKind === 'audio'" class="preview-audio-wrapper">
171
- <audio class="preview-audio" controls :src="audioSrc" />
172
- </div>
173
- <pre v-else-if="previewKind === 'text'" class="preview-text">{{ textContent }}</pre>
174
- <!-- 未知格式:用 iframe 尝试直接打开,系统/浏览器自行决定如何处理 -->
175
- <div v-else-if="previewKind === 'unsupported'" class="preview-iframe-wrapper">
176
- <iframe
177
- :src="openUrl"
178
- class="preview-iframe"
179
- sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
180
- @error="onRenderError"
181
- />
182
- <div class="preview-iframe-fallback">
183
- <span>若文件无法显示,请</span>
184
- <VanButton size="small" type="primary" plain @click="handleDownload">
185
- 下载查看
186
- </VanButton>
187
- </div>
188
- </div>
155
+
156
+ <!-- kk 预览:office 降级 或 doc/unsupported 直接走 -->
157
+ <KkPreview
158
+ v-else-if="officeFailed || KK_ONLY_KINDS.includes(kind)"
159
+ :file-path="rawPath"
160
+ />
161
+
162
+ <!-- 兜底:路径异常 -->
189
163
  <VanEmpty v-else description="文件路径异常">
190
- <VanButton v-if="openUrl" size="small" type="primary" @click="handleDownload">
164
+ <VanButton v-if="rawUrl" size="small" type="primary" @click="handleDownload">
191
165
  下载
192
166
  </VanButton>
193
167
  </VanEmpty>
@@ -200,103 +174,25 @@ defineExpose({
200
174
  width: 100%;
201
175
  height: 100%;
202
176
  min-height: 200px;
203
- position: relative;
204
- overflow: auto;
205
- background: #fff;
206
177
  display: flex;
207
178
  flex-direction: column;
179
+ background: #fff;
180
+ overflow: hidden;
181
+ /* 作为悬浮按钮的定位基准 */
182
+ position: relative;
208
183
  }
209
184
 
210
- .preview-toolbar {
211
- display: flex;
212
- justify-content: flex-end;
213
- padding: 8px 12px 0;
214
- flex-shrink: 0;
215
- }
216
-
217
- .preview-loading {
185
+ .preview-download-btn {
218
186
  position: absolute;
219
- inset: 0;
220
- display: flex;
221
- align-items: center;
222
- justify-content: center;
223
- z-index: 1;
187
+ top: 10px;
188
+ right: 12px;
189
+ z-index: 10;
224
190
  }
225
191
 
226
- .office-preview {
227
- width: 100%;
228
- min-height: 200px;
229
- height: 100%;
230
- }
231
-
232
- .preview-image-wrapper,
233
- .preview-video-wrapper {
234
- display: flex;
235
- align-items: center;
236
- justify-content: center;
237
- min-height: 200px;
238
- height: 100%;
239
- background: #000;
240
- }
241
-
242
- .preview-image {
243
- max-width: 100%;
244
- max-height: 100%;
245
- }
246
-
247
- .preview-video {
248
- width: 100%;
249
- max-height: 100%;
250
- border: none;
251
- background: #000;
252
- }
253
-
254
- .preview-text {
255
- margin: 0;
256
- padding: 12px;
257
- white-space: pre-wrap;
258
- word-break: break-word;
259
- font-size: 13px;
260
- line-height: 1.5;
261
- color: #333;
262
- }
263
-
264
- .preview-audio-wrapper {
265
- display: flex;
266
- align-items: center;
267
- justify-content: center;
268
- padding: 32px 16px;
269
- min-height: 120px;
270
- }
271
-
272
- .preview-audio {
273
- width: 100%;
274
- max-width: 480px;
275
- }
276
-
277
- .preview-iframe-wrapper {
278
- display: flex;
279
- flex-direction: column;
280
- width: 100%;
281
- height: 100%;
282
- flex: 1;
283
- }
284
-
285
- .preview-iframe {
286
- width: 100%;
192
+ .preview-loading {
287
193
  flex: 1;
288
- min-height: 200px;
289
- border: none;
290
- }
291
-
292
- .preview-iframe-fallback {
293
194
  display: flex;
294
195
  align-items: center;
295
- gap: 8px;
296
- padding: 8px 12px;
297
- font-size: 12px;
298
- color: #999;
299
- flex-shrink: 0;
300
- border-top: 1px solid #f0f0f0;
196
+ justify-content: center;
301
197
  }
302
198
  </style>