haiwei-module-admin 1.0.83 → 1.0.85
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
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"id": 0,
|
|
3
3
|
"name": "haiwei-module-admin",
|
|
4
4
|
"code": "admin",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.85",
|
|
6
6
|
"description": "haiwei前端Admin模块组件",
|
|
7
7
|
"author": "Eric",
|
|
8
|
-
"license": "ISC",
|
|
8
|
+
"license": "ISC",
|
|
9
9
|
"main": "src/index.js",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"serve": "vue-cli-service serve",
|
|
11
|
+
"serve": "vue-cli-service lint && vue-cli-service serve",
|
|
12
12
|
"build": "vue-cli-service build",
|
|
13
13
|
"lint": "vue-cli-service lint",
|
|
14
14
|
"cm": "rimraf node_modules",
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="file-preview">
|
|
3
|
+
<!-- 当fullPath有效时显示内容 -->
|
|
4
|
+
<template v-if="hasValidFullPath">
|
|
5
|
+
<!-- 图片预览链接 -->
|
|
6
|
+
<a v-if="isImage" class="preview-link" @click="showPreviewDialog">
|
|
7
|
+
查看
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
<!-- 非图片文件下载 -->
|
|
11
|
+
<nm-file-download v-else :url="downloadUrl" :private="isPrivate" :fileName="fileName" :text="downloadText" :icon="downloadIcon" :size="downloadSize" :type="downloadType" @click="onDownload" />
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<!-- 图片预览对话框 -->
|
|
15
|
+
<nm-dialog
|
|
16
|
+
v-if="isImage"
|
|
17
|
+
ref="previewDialog"
|
|
18
|
+
:visible.sync="previewVisible"
|
|
19
|
+
:title="previewTitle"
|
|
20
|
+
width="800px"
|
|
21
|
+
height="800px"
|
|
22
|
+
:fullscreen="false"
|
|
23
|
+
:footer="false"
|
|
24
|
+
custom-class="image-preview-dialog"
|
|
25
|
+
@closed="resetState"
|
|
26
|
+
>
|
|
27
|
+
<div class="preview-wrapper">
|
|
28
|
+
<!-- 使用CSS实现图片居中,而不是JS计算 -->
|
|
29
|
+
<div class="preview-container" ref="previewContainer">
|
|
30
|
+
<img
|
|
31
|
+
:src="previewUrl"
|
|
32
|
+
:alt="fileName"
|
|
33
|
+
class="preview-image"
|
|
34
|
+
ref="previewImage"
|
|
35
|
+
@load="handleImageLoad"
|
|
36
|
+
@error="handleImageError"
|
|
37
|
+
:style="imageStyle"
|
|
38
|
+
@mousedown="startDrag"
|
|
39
|
+
@wheel="handleWheel"
|
|
40
|
+
@dblclick="resetImage"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<!-- 操作按钮栏 -->
|
|
45
|
+
<div class="toolbar">
|
|
46
|
+
<button class="toolbar-btn" @click="zoomIn">
|
|
47
|
+
<span class="icon">+</span>
|
|
48
|
+
<span class="text">放大</span>
|
|
49
|
+
</button>
|
|
50
|
+
<button class="toolbar-btn" @click="zoomOut">
|
|
51
|
+
<span class="icon">-</span>
|
|
52
|
+
<span class="text">缩小</span>
|
|
53
|
+
</button>
|
|
54
|
+
<button class="toolbar-btn" @click="resetImage">
|
|
55
|
+
<span class="icon">⟳</span>
|
|
56
|
+
<span class="text">重置</span>
|
|
57
|
+
</button>
|
|
58
|
+
<!-- 非图片文件下载 -->
|
|
59
|
+
<nm-file-download
|
|
60
|
+
v-if="hasValidFullPath"
|
|
61
|
+
:url="downloadUrl"
|
|
62
|
+
:private="isPrivate"
|
|
63
|
+
:fileName="fileName"
|
|
64
|
+
:text="downloadText"
|
|
65
|
+
:icon="downloadIcon"
|
|
66
|
+
:size="downloadSize"
|
|
67
|
+
:type="downloadType"
|
|
68
|
+
@click="onDownload"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</nm-dialog>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<script>
|
|
77
|
+
export default {
|
|
78
|
+
name: 'FilePreview',
|
|
79
|
+
props: {
|
|
80
|
+
fullPath: { type: String, required: true },
|
|
81
|
+
isPrivate: { type: Boolean, default: false },
|
|
82
|
+
downloadText: { type: String, default: '下载' },
|
|
83
|
+
downloadIcon: { type: String, default: 'download' },
|
|
84
|
+
downloadSize: String,
|
|
85
|
+
downloadType: { type: String, default: 'primary' },
|
|
86
|
+
previewTitle: { type: String, default: '图片预览' },
|
|
87
|
+
imageExtensions: {
|
|
88
|
+
type: Array,
|
|
89
|
+
default: () => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico']
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
data() {
|
|
94
|
+
return {
|
|
95
|
+
previewVisible: false,
|
|
96
|
+
previewUrl: '',
|
|
97
|
+
fileInfo: null,
|
|
98
|
+
|
|
99
|
+
// 图片状态
|
|
100
|
+
imageLoading: false,
|
|
101
|
+
imageError: false,
|
|
102
|
+
loadingTimeout: null,
|
|
103
|
+
|
|
104
|
+
// 图片位置和缩放(使用CSS transform,但让CSS处理居中)
|
|
105
|
+
position: { x: 0, y: 0 },
|
|
106
|
+
scale: 1,
|
|
107
|
+
minScale: 0.1,
|
|
108
|
+
maxScale: 5,
|
|
109
|
+
|
|
110
|
+
// 拖拽状态
|
|
111
|
+
isDragging: false,
|
|
112
|
+
startPosition: { x: 0, y: 0 }
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
computed: {
|
|
117
|
+
hasValidFullPath() {
|
|
118
|
+
// 检查fullPath是否有效:不为空且不是空字符串
|
|
119
|
+
if (!this.fullPath || this.fullPath.trim() === '') {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 检查是否是目录路径(以斜杠结尾)
|
|
124
|
+
if (this.fullPath.endsWith('/') || this.fullPath.endsWith('\\')) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 检查是否有文件名(路径的最后一部分)
|
|
129
|
+
const path = this.fullPath.replace(/\\/g, '/')
|
|
130
|
+
const filename = path.split('/').pop()
|
|
131
|
+
|
|
132
|
+
// 文件名不能为空
|
|
133
|
+
if (!filename || filename.trim() === '') {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return true
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
fileName() {
|
|
141
|
+
// 从fullPath中提取文件名
|
|
142
|
+
if (!this.fullPath) return ''
|
|
143
|
+
const path = this.fullPath.replace(/\\/g, '/')
|
|
144
|
+
const filename = path.split('/').pop()
|
|
145
|
+
return filename || ''
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
fileExt() {
|
|
149
|
+
// 从fullPath中提取文件扩展名
|
|
150
|
+
const filename = this.fileName
|
|
151
|
+
const dotIndex = filename.lastIndexOf('.')
|
|
152
|
+
return dotIndex > -1 ? filename.slice(dotIndex + 1).toLowerCase() : ''
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
isImage() {
|
|
156
|
+
return this.imageExtensions.includes(this.fileExt)
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
downloadUrl() {
|
|
160
|
+
console.log('this.fileInfo?.url ', this.fileInfo?.url)
|
|
161
|
+
return this.fileInfo?.url || this.fullPath
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
imageStyle() {
|
|
165
|
+
return {
|
|
166
|
+
transform: `translate(${this.position.x}px, ${this.position.y}px) scale(${this.scale})`,
|
|
167
|
+
cursor: this.isDragging ? 'grabbing' : 'grab'
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
watch: {
|
|
173
|
+
fullPath(val) {
|
|
174
|
+
if (val && this.isImage && !this.isPrivate) {
|
|
175
|
+
this.previewUrl = val
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
previewVisible(val) {
|
|
180
|
+
if (!val) {
|
|
181
|
+
this.resetState()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
mounted() {
|
|
187
|
+
// 监听全局鼠标事件用于拖拽
|
|
188
|
+
document.addEventListener('mousemove', this.handleDrag)
|
|
189
|
+
document.addEventListener('mouseup', this.stopDrag)
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
beforeDestroy() {
|
|
193
|
+
// 清理事件监听
|
|
194
|
+
document.removeEventListener('mousemove', this.handleDrag)
|
|
195
|
+
document.removeEventListener('mouseup', this.stopDrag)
|
|
196
|
+
|
|
197
|
+
// 清理超时
|
|
198
|
+
if (this.loadingTimeout) {
|
|
199
|
+
clearTimeout(this.loadingTimeout)
|
|
200
|
+
this.loadingTimeout = null
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
methods: {
|
|
205
|
+
async getFileInfo() {
|
|
206
|
+
if (!this.fullPath) return null
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const result = await $api.admin.file.getByFullPath(this.fullPath)
|
|
210
|
+
this.fileInfo = result
|
|
211
|
+
|
|
212
|
+
console.log('result', result)
|
|
213
|
+
|
|
214
|
+
return result
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('获取文件信息失败:', error)
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
async showPreviewDialog() {
|
|
222
|
+
if (!this.isImage) return
|
|
223
|
+
|
|
224
|
+
this.resetState()
|
|
225
|
+
this.imageLoading = true
|
|
226
|
+
this.previewVisible = true
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const fileInfo = await this.getFileInfo()
|
|
230
|
+
if (!fileInfo) {
|
|
231
|
+
this.imageLoading = false
|
|
232
|
+
this.imageError = true
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await this.setPreviewUrl(fileInfo)
|
|
237
|
+
|
|
238
|
+
// 设置一个立即检查,防止缓存图片不触发load事件
|
|
239
|
+
this.$nextTick(() => {
|
|
240
|
+
setTimeout(() => {
|
|
241
|
+
if (this.imageLoading && this.$refs.previewImage && this.$refs.previewImage.complete) {
|
|
242
|
+
this.handleImageLoad()
|
|
243
|
+
}
|
|
244
|
+
}, 300) // 300ms后检查
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// 设置超时,防止图片加载事件未触发
|
|
248
|
+
this.loadingTimeout = setTimeout(() => {
|
|
249
|
+
if (this.imageLoading) {
|
|
250
|
+
console.warn('图片加载超时,强制结束加载状态')
|
|
251
|
+
this.imageLoading = false
|
|
252
|
+
// 检查图片是否已经加载完成
|
|
253
|
+
if (this.$refs.previewImage && this.$refs.previewImage.complete) {
|
|
254
|
+
this.handleImageLoad()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}, 2000) // 2秒超时
|
|
258
|
+
} catch (error) {
|
|
259
|
+
console.error('打开预览对话框失败:', error)
|
|
260
|
+
this.imageLoading = false
|
|
261
|
+
this.imageError = true
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
async setPreviewUrl(fileInfo) {
|
|
266
|
+
let newUrl
|
|
267
|
+
if (this.isPrivate && fileInfo.url) {
|
|
268
|
+
try {
|
|
269
|
+
newUrl = await $api.admin.file.preview(fileInfo.url)
|
|
270
|
+
} catch {
|
|
271
|
+
newUrl = fileInfo.url
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
newUrl = fileInfo.url || this.fullPath
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 先清空URL,强制重新加载(即使URL相同)
|
|
278
|
+
if (this.previewUrl) {
|
|
279
|
+
this.previewUrl = ''
|
|
280
|
+
// 等待一个tick确保DOM更新
|
|
281
|
+
await this.$nextTick()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 设置新的URL
|
|
285
|
+
this.previewUrl = newUrl
|
|
286
|
+
|
|
287
|
+
// 等待DOM更新后检查图片是否已经加载完成
|
|
288
|
+
this.$nextTick(() => {
|
|
289
|
+
// 如果图片元素存在且已经加载完成,直接触发加载完成事件
|
|
290
|
+
if (this.$refs.previewImage && this.$refs.previewImage.complete) {
|
|
291
|
+
// 小延迟确保图片完全渲染
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
if (this.imageLoading) {
|
|
294
|
+
this.handleImageLoad()
|
|
295
|
+
}
|
|
296
|
+
}, 100)
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
},
|
|
300
|
+
|
|
301
|
+
async onDownload() {
|
|
302
|
+
const fileInfo = await this.getFileInfo()
|
|
303
|
+
const url = fileInfo?.url || this.fullPath
|
|
304
|
+
this.$emit('download', url, this.fileName)
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
handleImageLoad() {
|
|
308
|
+
// 清理超时
|
|
309
|
+
if (this.loadingTimeout) {
|
|
310
|
+
clearTimeout(this.loadingTimeout)
|
|
311
|
+
this.loadingTimeout = null
|
|
312
|
+
}
|
|
313
|
+
this.imageLoading = false
|
|
314
|
+
// 图片加载完成后自动居中
|
|
315
|
+
this.resetImage()
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
handleImageError() {
|
|
319
|
+
// 清理超时
|
|
320
|
+
if (this.loadingTimeout) {
|
|
321
|
+
clearTimeout(this.loadingTimeout)
|
|
322
|
+
this.loadingTimeout = null
|
|
323
|
+
}
|
|
324
|
+
this.imageLoading = false
|
|
325
|
+
this.imageError = true
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
// 鼠标滚轮缩放 - 以鼠标为中心点缩放
|
|
329
|
+
handleWheel(event) {
|
|
330
|
+
event.preventDefault()
|
|
331
|
+
|
|
332
|
+
const delta = event.deltaY < 0 ? 0.1 : -0.1
|
|
333
|
+
const newScale = Math.max(this.minScale, Math.min(this.maxScale, this.scale + delta))
|
|
334
|
+
|
|
335
|
+
// 计算缩放中心(相对于图片)
|
|
336
|
+
if (this.$refs.previewContainer) {
|
|
337
|
+
const containerRect = this.$refs.previewContainer.getBoundingClientRect()
|
|
338
|
+
const mouseX = event.clientX - containerRect.left
|
|
339
|
+
const mouseY = event.clientY - containerRect.top
|
|
340
|
+
|
|
341
|
+
// 计算鼠标相对于图片原始坐标系的位置
|
|
342
|
+
// 注意:position.x 和 position.y 是图片相对于容器的平移
|
|
343
|
+
const relativeX = (mouseX - this.position.x) / this.scale
|
|
344
|
+
const relativeY = (mouseY - this.position.y) / this.scale
|
|
345
|
+
|
|
346
|
+
// 更新位置以保持鼠标点不变
|
|
347
|
+
this.position.x = mouseX - relativeX * newScale
|
|
348
|
+
this.position.y = mouseY - relativeY * newScale
|
|
349
|
+
this.scale = newScale
|
|
350
|
+
} else {
|
|
351
|
+
this.scale = newScale
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
// 开始拖拽
|
|
356
|
+
startDrag(event) {
|
|
357
|
+
if (event.button !== 0) return // 只响应左键
|
|
358
|
+
this.isDragging = true
|
|
359
|
+
this.startPosition = {
|
|
360
|
+
x: event.clientX - this.position.x,
|
|
361
|
+
y: event.clientY - this.position.y
|
|
362
|
+
}
|
|
363
|
+
event.preventDefault()
|
|
364
|
+
},
|
|
365
|
+
|
|
366
|
+
// 处理拖拽
|
|
367
|
+
handleDrag(event) {
|
|
368
|
+
if (!this.isDragging) return
|
|
369
|
+
|
|
370
|
+
this.position.x = event.clientX - this.startPosition.x
|
|
371
|
+
this.position.y = event.clientY - this.startPosition.y
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// 停止拖拽
|
|
375
|
+
stopDrag() {
|
|
376
|
+
this.isDragging = false
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
// 放大
|
|
380
|
+
zoomIn() {
|
|
381
|
+
const newScale = Math.min(this.maxScale, this.scale + 0.2)
|
|
382
|
+
if (newScale !== this.scale) {
|
|
383
|
+
this.centerZoom(newScale)
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
// 缩小
|
|
388
|
+
zoomOut() {
|
|
389
|
+
const newScale = Math.max(this.minScale, this.scale - 0.2)
|
|
390
|
+
if (newScale !== this.scale) {
|
|
391
|
+
this.centerZoom(newScale)
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
// 以当前视图中心点缩放(用于工具栏按钮)
|
|
396
|
+
centerZoom(newScale) {
|
|
397
|
+
if (this.$refs.previewContainer) {
|
|
398
|
+
const containerRect = this.$refs.previewContainer.getBoundingClientRect()
|
|
399
|
+
// 使用容器中心作为缩放中心点
|
|
400
|
+
const centerX = containerRect.width / 2
|
|
401
|
+
const centerY = containerRect.height / 2
|
|
402
|
+
|
|
403
|
+
// 计算中心点相对于图片原始坐标系的位置
|
|
404
|
+
const relativeX = (centerX - this.position.x) / this.scale
|
|
405
|
+
const relativeY = (centerY - this.position.y) / this.scale
|
|
406
|
+
|
|
407
|
+
// 更新位置以保持中心点不变
|
|
408
|
+
this.position.x = centerX - relativeX * newScale
|
|
409
|
+
this.position.y = centerY - relativeY * newScale
|
|
410
|
+
}
|
|
411
|
+
this.scale = newScale
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
// 重置图片位置和大小
|
|
415
|
+
resetImage() {
|
|
416
|
+
this.position = { x: 0, y: 0 }
|
|
417
|
+
this.scale = 1
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
// 重置所有状态
|
|
421
|
+
resetState() {
|
|
422
|
+
// 清理超时
|
|
423
|
+
if (this.loadingTimeout) {
|
|
424
|
+
clearTimeout(this.loadingTimeout)
|
|
425
|
+
this.loadingTimeout = null
|
|
426
|
+
}
|
|
427
|
+
this.position = { x: 0, y: 0 }
|
|
428
|
+
this.scale = 1
|
|
429
|
+
this.imageLoading = false
|
|
430
|
+
this.imageError = false
|
|
431
|
+
this.isDragging = false
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
</script>
|
|
436
|
+
|
|
437
|
+
<style lang="scss" scoped>
|
|
438
|
+
.file-preview {
|
|
439
|
+
display: inline-block;
|
|
440
|
+
|
|
441
|
+
.preview-link {
|
|
442
|
+
color: #409eff;
|
|
443
|
+
cursor: pointer;
|
|
444
|
+
text-decoration: none;
|
|
445
|
+
transition: color 0.2s;
|
|
446
|
+
|
|
447
|
+
&:hover {
|
|
448
|
+
color: #66b1ff;
|
|
449
|
+
text-decoration: underline;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.preview-wrapper {
|
|
455
|
+
width: 100%;
|
|
456
|
+
height: 750px;
|
|
457
|
+
background: #000;
|
|
458
|
+
position: relative;
|
|
459
|
+
overflow: hidden;
|
|
460
|
+
display: flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
justify-content: center;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.preview-container {
|
|
466
|
+
width: 100%;
|
|
467
|
+
height: 100%;
|
|
468
|
+
display: flex;
|
|
469
|
+
align-items: center;
|
|
470
|
+
justify-content: center;
|
|
471
|
+
position: relative;
|
|
472
|
+
overflow: hidden;
|
|
473
|
+
touch-action: none;
|
|
474
|
+
/* 防止移动端默认行为 */
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.preview-image {
|
|
478
|
+
max-width: 100%;
|
|
479
|
+
max-height: 100%;
|
|
480
|
+
object-fit: contain;
|
|
481
|
+
/* 关键:保持比例并适应容器 */
|
|
482
|
+
transform-origin: 0 0;
|
|
483
|
+
/* 设置为左上角以匹配我们的数学计算 */
|
|
484
|
+
transition: transform 0.2s ease;
|
|
485
|
+
user-select: none;
|
|
486
|
+
-webkit-user-drag: none;
|
|
487
|
+
|
|
488
|
+
/* 平滑的拖拽和缩放效果 */
|
|
489
|
+
will-change: transform;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/* 操作工具栏 */
|
|
493
|
+
.toolbar {
|
|
494
|
+
position: absolute;
|
|
495
|
+
bottom: 20px;
|
|
496
|
+
left: 50%;
|
|
497
|
+
transform: translateX(-50%);
|
|
498
|
+
display: flex;
|
|
499
|
+
gap: 10px;
|
|
500
|
+
background: rgba(0, 0, 0, 0.7);
|
|
501
|
+
padding: 10px;
|
|
502
|
+
border-radius: 4px;
|
|
503
|
+
z-index: 10;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.toolbar-btn {
|
|
507
|
+
display: flex;
|
|
508
|
+
align-items: center;
|
|
509
|
+
justify-content: center;
|
|
510
|
+
gap: 5px;
|
|
511
|
+
background: rgba(255, 255, 255, 0.1);
|
|
512
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
513
|
+
color: white;
|
|
514
|
+
padding: 8px 12px;
|
|
515
|
+
border-radius: 4px;
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
font-size: 12px;
|
|
518
|
+
transition: all 0.2s;
|
|
519
|
+
|
|
520
|
+
&:hover {
|
|
521
|
+
background: rgba(255, 255, 255, 0.2);
|
|
522
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
&:active {
|
|
526
|
+
transform: scale(0.95);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.icon {
|
|
530
|
+
font-size: 14px;
|
|
531
|
+
font-weight: bold;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.text {
|
|
535
|
+
white-space: nowrap;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
</style>
|
|
539
|
+
|
|
540
|
+
<style lang="scss">
|
|
541
|
+
/* 对话框全局样式 */
|
|
542
|
+
.image-preview-dialog {
|
|
543
|
+
.el-dialog {
|
|
544
|
+
background: #000;
|
|
545
|
+
border: 1px solid #333;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.el-dialog__header {
|
|
549
|
+
background: #000;
|
|
550
|
+
border-bottom: 1px solid #333;
|
|
551
|
+
padding: 10px 20px;
|
|
552
|
+
|
|
553
|
+
.el-dialog__title {
|
|
554
|
+
color: #fff;
|
|
555
|
+
font-size: 14px;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.el-dialog__close {
|
|
559
|
+
color: #fff;
|
|
560
|
+
|
|
561
|
+
&:hover {
|
|
562
|
+
color: #409eff;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.el-dialog__body {
|
|
568
|
+
padding: 0 !important;
|
|
569
|
+
background: #000 !important;
|
|
570
|
+
overflow: hidden !important;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
</style>
|
|
@@ -115,21 +115,25 @@ export default {
|
|
|
115
115
|
|
|
116
116
|
computed: {
|
|
117
117
|
hasValidFullPath() {
|
|
118
|
-
// 检查fullPath
|
|
119
|
-
if (!this.fullPath || this.fullPath.trim() === '')
|
|
118
|
+
// 检查fullPath是否有效:不为空且不是空字符串
|
|
119
|
+
if (!this.fullPath || this.fullPath.trim() === '') {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
120
122
|
|
|
121
123
|
// 检查是否是目录路径(以斜杠结尾)
|
|
122
|
-
if (this.fullPath.endsWith('/') || this.fullPath.endsWith('\\'))
|
|
124
|
+
if (this.fullPath.endsWith('/') || this.fullPath.endsWith('\\')) {
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
123
127
|
|
|
124
128
|
// 检查是否有文件名(路径的最后一部分)
|
|
125
129
|
const path = this.fullPath.replace(/\\/g, '/')
|
|
126
130
|
const filename = path.split('/').pop()
|
|
127
131
|
|
|
128
132
|
// 文件名不能为空
|
|
129
|
-
if (!filename || filename.trim() === '')
|
|
133
|
+
if (!filename || filename.trim() === '') {
|
|
134
|
+
return false
|
|
135
|
+
}
|
|
130
136
|
|
|
131
|
-
// 文件名应该包含点号(表示有扩展名),但为了兼容性,不强制要求
|
|
132
|
-
// 因为有些文件可能没有扩展名,或者扩展名被省略
|
|
133
137
|
return true
|
|
134
138
|
},
|
|
135
139
|
|