haiwei-module-admin 1.0.81 → 1.0.83

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