jvs-ppt-ui 0.0.9 → 0.0.10
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/README.md +3 -0
- package/dist/lib/jvs-ppt-ui.es.js +22611 -21764
- package/dist/lib/jvs-ppt-ui.umd.js +102 -101
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +53 -53
- package/src/components/AIPptDialog.vue +2 -0
- package/src/components/CanvasArea.vue +124 -62
- package/src/components/Header.vue +364 -142
- package/src/components/PptEditor.vue +26 -15
- package/src/components/PresentationMode.vue +24 -4
- package/src/components/SidebarRight.vue +8 -1
- package/src/components/SvgChart.vue +5 -2
- package/src/components/TemplateGallery.vue +1321 -866
- package/src/components/Toolbar.vue +243 -22
- package/src/components/canvas/CanvasTransformHandles.vue +5 -1
- package/src/components/canvas/elements/CanvasShapeElement.vue +98 -19
- package/src/components/canvas/elements/CanvasTextElement.vue +39 -9
- package/src/components/canvas/textSelectionOverlay.ts +305 -0
- package/src/composables/canvas/useCanvasAlignment.ts +286 -238
- package/src/composables/canvas/useCanvasContextMenu.ts +53 -22
- package/src/composables/canvas/useCanvasFormatting.ts +35 -12
- package/src/composables/canvas/useCanvasTable.ts +52 -24
- package/src/composables/canvas/useCanvasText.ts +12 -0
- package/src/composables/canvas/useCanvasTransform.ts +2 -2
- package/src/composables/sidebar/useSidebarElementTransform.ts +5 -5
- package/src/composables/sidebar/useSidebarShapeFormat.ts +2 -2
- package/src/composables/useSlides.ts +482 -127
- package/src/style.css +88 -0
- package/src/types/index.ts +4 -0
- package/src/utils/pptxShapes.ts +1 -1
- package/src/utils/pptxShapesLazy.ts +39 -0
- package/src/utils/svgPathParser.ts +12 -1
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, computed, onMounted, watch } from 'vue'
|
|
3
|
-
import {
|
|
2
|
+
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
|
|
3
|
+
import {
|
|
4
|
+
Search,
|
|
5
|
+
Plus,
|
|
6
|
+
Close,
|
|
7
|
+
CircleCloseFilled,
|
|
8
|
+
ArrowDown,
|
|
9
|
+
ArrowLeft,
|
|
10
|
+
ArrowRight,
|
|
11
|
+
MoreFilled,
|
|
12
|
+
View
|
|
13
|
+
} from '@element-plus/icons-vue'
|
|
4
14
|
import { useSlides, DEFAULT_MATERIAL_API_URL, DEFAULT_MATERIAL_PAGE_API_URL } from '../composables/useSlides'
|
|
5
15
|
|
|
6
16
|
const {
|
|
@@ -12,10 +22,16 @@ const {
|
|
|
12
22
|
materialPageApiUrl,
|
|
13
23
|
materialHeaders,
|
|
14
24
|
saveCurrentSlideToMaterial,
|
|
15
|
-
activeSlide
|
|
25
|
+
activeSlide,
|
|
26
|
+
materialPopoverVisible
|
|
16
27
|
} = useSlides()
|
|
17
28
|
|
|
18
|
-
//
|
|
29
|
+
// 容器响应式宽度监听(区分浮窗大屏与侧边栏窄屏模式)
|
|
30
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
31
|
+
const isNarrow = ref(false)
|
|
32
|
+
let resizeObserver: ResizeObserver | null = null
|
|
33
|
+
|
|
34
|
+
// 默认单页模板封面地址
|
|
19
35
|
const DEFAULT_SLIDE_COVER = 'https://frame.bctools.cn/jvs-oss/material/templates/gold-cover.png'
|
|
20
36
|
|
|
21
37
|
function getMaterialThumb(item: MaterialItem): string {
|
|
@@ -29,65 +45,141 @@ function getMaterialThumb(item: MaterialItem): string {
|
|
|
29
45
|
return item.previewUrl || item.url || item.downloadUrl || ''
|
|
30
46
|
}
|
|
31
47
|
|
|
32
|
-
//
|
|
48
|
+
// 提取格式标签
|
|
49
|
+
function getItemFormat(item: MaterialItem): string {
|
|
50
|
+
if (item.format) return item.format.toUpperCase()
|
|
51
|
+
const u = (item.url || item.previewUrl || item.fileName || '').toLowerCase()
|
|
52
|
+
if (u.includes('.png')) return 'PNG'
|
|
53
|
+
if (u.includes('.gif')) return 'GIF'
|
|
54
|
+
if (u.includes('.svg')) return 'SVG'
|
|
55
|
+
if (u.includes('.psd')) return 'PSD'
|
|
56
|
+
if (u.includes('.ai')) return 'AI'
|
|
57
|
+
if (u.includes('.mov') || u.includes('.mp4')) return 'MOV'
|
|
58
|
+
if (item.type === 'gif') return 'GIF'
|
|
59
|
+
if (item.type === 'svg') return 'SVG'
|
|
60
|
+
if (item.type === 'sticker') return 'PNG'
|
|
61
|
+
return 'JPG'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 100% 自研可靠的全屏大图画廊预览状态
|
|
65
|
+
interface PreviewItem {
|
|
66
|
+
url: string
|
|
67
|
+
title: string
|
|
68
|
+
format: string
|
|
69
|
+
meta: MaterialItem
|
|
70
|
+
}
|
|
71
|
+
|
|
33
72
|
const previewVisible = ref(false)
|
|
34
|
-
const previewList = ref<
|
|
73
|
+
const previewList = ref<PreviewItem[]>([])
|
|
35
74
|
const previewInitialIndex = ref(0)
|
|
75
|
+
const wasPopoverOpenBeforePreview = ref(false)
|
|
76
|
+
|
|
77
|
+
const currentPreviewItem = computed(() => {
|
|
78
|
+
if (previewList.value.length === 0) return null
|
|
79
|
+
return previewList.value[previewInitialIndex.value] || null
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
function openPreview(item: MaterialItem, e?: Event) {
|
|
83
|
+
if (e) {
|
|
84
|
+
e.stopPropagation()
|
|
85
|
+
e.preventDefault()
|
|
86
|
+
}
|
|
36
87
|
|
|
37
|
-
function openPreview(item: MaterialItem, contextList?: MaterialItem[]) {
|
|
38
88
|
const currentUrl = getMaterialThumb(item)
|
|
39
89
|
if (!currentUrl) {
|
|
40
90
|
notify('该素材暂无可用预览地址')
|
|
41
91
|
return
|
|
42
92
|
}
|
|
43
93
|
|
|
44
|
-
const
|
|
94
|
+
const validItems: PreviewItem[] = []
|
|
95
|
+
displayedMaterials.value.forEach(m => {
|
|
96
|
+
const u = getMaterialThumb(m)
|
|
97
|
+
if (u) {
|
|
98
|
+
validItems.push({
|
|
99
|
+
url: u,
|
|
100
|
+
title: m.title || '素材大图',
|
|
101
|
+
format: getItemFormat(m),
|
|
102
|
+
meta: m
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
})
|
|
45
106
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
candidateItems = contextList.filter(m => (m.type === targetType || !m.type))
|
|
50
|
-
} else if (viewMode.value === 'category' && detailMaterials.value.length > 0) {
|
|
51
|
-
// 2. 如果在分类模式中,从 detailMaterials 中筛选
|
|
52
|
-
candidateItems = detailMaterials.value.filter(m => (m.type === targetType || !m.type))
|
|
107
|
+
const targetIdx = validItems.findIndex(i => i.url === currentUrl)
|
|
108
|
+
if (targetIdx >= 0) {
|
|
109
|
+
previewInitialIndex.value = targetIdx
|
|
53
110
|
} else {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.filter(m => (m.type === targetType))
|
|
62
|
-
}
|
|
111
|
+
validItems.unshift({
|
|
112
|
+
url: currentUrl,
|
|
113
|
+
title: item.title || '素材大图',
|
|
114
|
+
format: getItemFormat(item),
|
|
115
|
+
meta: item
|
|
116
|
+
})
|
|
117
|
+
previewInitialIndex.value = 0
|
|
63
118
|
}
|
|
64
119
|
|
|
65
|
-
|
|
66
|
-
const validUrls: string[] = []
|
|
67
|
-
candidateItems.forEach(m => {
|
|
68
|
-
const u = getMaterialThumb(m)
|
|
69
|
-
if (u && !validUrls.includes(u)) {
|
|
70
|
-
validUrls.push(u)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
120
|
+
previewList.value = validItems
|
|
73
121
|
|
|
74
|
-
//
|
|
75
|
-
if (
|
|
76
|
-
|
|
122
|
+
// 预览大图时,隐藏素材弹窗,并记录状态以便关闭预览后无缝恢复
|
|
123
|
+
if (materialPopoverVisible && materialPopoverVisible.value) {
|
|
124
|
+
wasPopoverOpenBeforePreview.value = true
|
|
125
|
+
materialPopoverVisible.value = false
|
|
126
|
+
} else {
|
|
127
|
+
wasPopoverOpenBeforePreview.value = false
|
|
77
128
|
}
|
|
78
129
|
|
|
79
|
-
previewList.value = validUrls
|
|
80
|
-
const idx = validUrls.indexOf(currentUrl)
|
|
81
|
-
previewInitialIndex.value = idx >= 0 ? idx : 0
|
|
82
130
|
previewVisible.value = true
|
|
83
131
|
}
|
|
84
132
|
|
|
85
|
-
|
|
86
|
-
|
|
133
|
+
function closePreview() {
|
|
134
|
+
previewVisible.value = false
|
|
135
|
+
if (wasPopoverOpenBeforePreview.value && materialPopoverVisible) {
|
|
136
|
+
wasPopoverOpenBeforePreview.value = false
|
|
137
|
+
// 延时微任务以防预览弹窗的点击事件穿透触发 Popover 的外部关闭逻辑
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
materialPopoverVisible.value = true
|
|
140
|
+
}, 60)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function prevPreview() {
|
|
145
|
+
if (previewList.value.length <= 1) return
|
|
146
|
+
previewInitialIndex.value = (previewInitialIndex.value - 1 + previewList.value.length) % previewList.value.length
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function nextPreview() {
|
|
150
|
+
if (previewList.value.length <= 1) return
|
|
151
|
+
previewInitialIndex.value = (previewInitialIndex.value + 1) % previewList.value.length
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleInsertFromPreview() {
|
|
155
|
+
if (currentPreviewItem.value) {
|
|
156
|
+
handleSelectMaterial(currentPreviewItem.value.meta)
|
|
157
|
+
wasPopoverOpenBeforePreview.value = false
|
|
158
|
+
previewVisible.value = false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
163
|
+
if (!previewVisible.value) return
|
|
164
|
+
if (e.key === 'Escape' || e.key === 'Esc') {
|
|
165
|
+
e.preventDefault()
|
|
166
|
+
e.stopPropagation()
|
|
167
|
+
closePreview()
|
|
168
|
+
} else if (e.key === 'ArrowLeft') {
|
|
169
|
+
prevPreview()
|
|
170
|
+
} else if (e.key === 'ArrowRight') {
|
|
171
|
+
nextPreview()
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface MaterialItem {
|
|
176
|
+
id: string | number
|
|
87
177
|
title: string
|
|
88
178
|
pptSource?: string
|
|
89
179
|
category?: string
|
|
90
180
|
type?: string
|
|
181
|
+
format?: string
|
|
182
|
+
color?: string
|
|
91
183
|
slideIndex?: number
|
|
92
184
|
fileName?: string
|
|
93
185
|
url?: string
|
|
@@ -99,10 +191,11 @@ interface MaterialItem {
|
|
|
99
191
|
originalHeight?: number
|
|
100
192
|
originalSize?: { width: number; height: number }
|
|
101
193
|
tags?: string[]
|
|
194
|
+
aspectRatio?: string
|
|
102
195
|
[key: string]: any
|
|
103
196
|
}
|
|
104
197
|
|
|
105
|
-
interface MaterialCategory {
|
|
198
|
+
export interface MaterialCategory {
|
|
106
199
|
id: string
|
|
107
200
|
type: string
|
|
108
201
|
name: string
|
|
@@ -110,21 +203,56 @@ interface MaterialCategory {
|
|
|
110
203
|
items?: MaterialItem[]
|
|
111
204
|
}
|
|
112
205
|
|
|
113
|
-
//
|
|
114
|
-
const
|
|
115
|
-
|
|
206
|
+
// 统一筛选与搜索模型(红框1 + 红框2 + 红框3)
|
|
207
|
+
const filterParams = reactive({
|
|
208
|
+
query: '', // 红框1:搜索关键词
|
|
209
|
+
activeType: 'all', // 红框2:分类 Tabs
|
|
210
|
+
sort: 'recommend', // 红框3:综合排序
|
|
211
|
+
format: '', // 红框3:格式筛选
|
|
212
|
+
color: '' // 红框3:颜色筛选
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
// 分类 Tabs(红框2)
|
|
216
|
+
const categoryTabs = [
|
|
217
|
+
{ label: '全部', value: 'all' },
|
|
218
|
+
{ label: '图片', value: 'image' },
|
|
219
|
+
{ label: '动图', value: 'gif' },
|
|
220
|
+
{ label: '免抠', value: 'sticker' },
|
|
221
|
+
{ label: '矢量', value: 'svg' },
|
|
222
|
+
{ label: '模板', value: 'slide' }
|
|
223
|
+
]
|
|
224
|
+
|
|
225
|
+
// 排序选项(红框3)
|
|
226
|
+
const sortOptions = [
|
|
227
|
+
{ label: '综合排序', value: 'recommend' },
|
|
228
|
+
{ label: '最新上传', value: 'latest' },
|
|
229
|
+
{ label: '热门下载', value: 'hot' }
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
// 格式选项(红框3下拉)
|
|
233
|
+
const formatOptions = ['JPG', 'PNG', 'GIF', 'SVG', 'EPS', 'PSD', 'MOV', 'AI']
|
|
234
|
+
|
|
235
|
+
// 常用色系
|
|
236
|
+
const colorOptions = [
|
|
237
|
+
{ name: '蓝色系', hex: '#2196F3' },
|
|
238
|
+
{ name: '青色系', hex: '#00BCD4' },
|
|
239
|
+
{ name: '绿色系', hex: '#4CAF50' },
|
|
240
|
+
{ name: '黄色系', hex: '#FFC107' },
|
|
241
|
+
{ name: '橙色系', hex: '#FF9800' },
|
|
242
|
+
{ name: '红色系', hex: '#F44336' },
|
|
243
|
+
{ name: '紫色系', hex: '#9C27B0' },
|
|
244
|
+
{ name: '白色系', hex: '#FFFFFF' },
|
|
245
|
+
{ name: '黑色系', hex: '#212121' }
|
|
246
|
+
]
|
|
116
247
|
|
|
117
248
|
const loading = ref(false)
|
|
118
|
-
const searchQuery = ref('')
|
|
119
249
|
const categoryTree = ref<MaterialCategory[]>([])
|
|
120
|
-
|
|
121
|
-
// 分页列表(用于进入分类详情模式和搜索模式)
|
|
122
|
-
const detailMaterials = ref<MaterialItem[]>([])
|
|
250
|
+
const pagedMaterials = ref<MaterialItem[]>([])
|
|
123
251
|
const currentPage = ref(1)
|
|
124
|
-
const pageSize =
|
|
252
|
+
const pageSize = 24
|
|
125
253
|
const totalRecords = ref(0)
|
|
126
254
|
|
|
127
|
-
//
|
|
255
|
+
// 预设类型中文名映射
|
|
128
256
|
const typeNameMap: Record<string, string> = {
|
|
129
257
|
image: '图库图片',
|
|
130
258
|
gif: '动图GIF',
|
|
@@ -133,14 +261,6 @@ const typeNameMap: Record<string, string> = {
|
|
|
133
261
|
slide: '单页模板'
|
|
134
262
|
}
|
|
135
263
|
|
|
136
|
-
// 保存素材弹窗状态
|
|
137
|
-
const saveDialogVisible = ref(false)
|
|
138
|
-
const saveLoading = ref(false)
|
|
139
|
-
const saveForm = ref({
|
|
140
|
-
title: '',
|
|
141
|
-
category: '正文'
|
|
142
|
-
})
|
|
143
|
-
|
|
144
264
|
// 安全的 JSON 请求辅助函数
|
|
145
265
|
async function safeFetchJson(url: string, options: RequestInit = {}): Promise<any> {
|
|
146
266
|
const response = await fetch(url, options)
|
|
@@ -150,7 +270,7 @@ async function safeFetchJson(url: string, options: RequestInit = {}): Promise<an
|
|
|
150
270
|
}
|
|
151
271
|
const trimmed = rawText.trim()
|
|
152
272
|
if (trimmed.startsWith('<') || trimmed.toLowerCase().startsWith('<!doctype')) {
|
|
153
|
-
throw new Error(
|
|
273
|
+
throw new Error(`接口地址返回了 HTML 网页而非有效 JSON 数据`)
|
|
154
274
|
}
|
|
155
275
|
try {
|
|
156
276
|
return JSON.parse(rawText)
|
|
@@ -159,9 +279,9 @@ async function safeFetchJson(url: string, options: RequestInit = {}): Promise<an
|
|
|
159
279
|
}
|
|
160
280
|
}
|
|
161
281
|
|
|
162
|
-
//
|
|
282
|
+
// 规范化素材数据项
|
|
163
283
|
function normalizeMaterialItem(item: any, defaultCatName = '', defaultType = ''): MaterialItem {
|
|
164
|
-
const type = item.type || defaultType
|
|
284
|
+
const type = item.type || defaultType || 'image'
|
|
165
285
|
let previewUrl = item.previewUrl || item.url || item.downloadUrl || ''
|
|
166
286
|
let url = item.url || item.previewUrl || item.downloadUrl || ''
|
|
167
287
|
|
|
@@ -174,9 +294,12 @@ function normalizeMaterialItem(item: any, defaultCatName = '', defaultType = '')
|
|
|
174
294
|
}
|
|
175
295
|
}
|
|
176
296
|
|
|
297
|
+
const fmt = item.format || getItemFormat({ ...item, url, previewUrl, type })
|
|
298
|
+
|
|
177
299
|
return {
|
|
178
300
|
...item,
|
|
179
301
|
type,
|
|
302
|
+
format: fmt,
|
|
180
303
|
category: item.category || defaultCatName || (type && typeNameMap[type]) || type || '素材',
|
|
181
304
|
previewUrl,
|
|
182
305
|
url
|
|
@@ -206,7 +329,6 @@ function parseMaterialList(data: any): MaterialItem[] {
|
|
|
206
329
|
}
|
|
207
330
|
|
|
208
331
|
// 1. 获取首页分类树的 API 请求 (/jvs-dam/api/dam/material/tree)
|
|
209
|
-
// 未进入具体分类时,默认或搜索时调用此接口展示分块列表
|
|
210
332
|
async function loadTreeMaterials(query = '') {
|
|
211
333
|
const baseTreeUrl = (materialTreeApiUrl?.value && materialTreeApiUrl.value.trim() && !materialTreeApiUrl.value.includes('/templates/index.json'))
|
|
212
334
|
? materialTreeApiUrl.value.trim()
|
|
@@ -230,14 +352,12 @@ async function loadTreeMaterials(query = '') {
|
|
|
230
352
|
})
|
|
231
353
|
|
|
232
354
|
if (Array.isArray(data?.data) && data.data.length > 0 && Array.isArray(data.data[0]?.items)) {
|
|
233
|
-
// 树状分类分组
|
|
234
355
|
categoryTree.value = data.data.map((cat: any) => ({
|
|
235
356
|
...cat,
|
|
236
357
|
name: cat.name || (cat.type ? typeNameMap[cat.type] : '') || cat.type,
|
|
237
358
|
items: (cat.items || []).map((item: any) => normalizeMaterialItem(item, cat.name, cat.type))
|
|
238
359
|
}))
|
|
239
360
|
} else {
|
|
240
|
-
// 兼容非分组结构,按默认类型聚合
|
|
241
361
|
const flatList = parseMaterialList(data)
|
|
242
362
|
categoryTree.value = flatList.length > 0 ? [
|
|
243
363
|
{ id: 'all-items', type: 'image', name: '素材', hasMore: true, items: flatList }
|
|
@@ -245,38 +365,36 @@ async function loadTreeMaterials(query = '') {
|
|
|
245
365
|
}
|
|
246
366
|
} catch (err: any) {
|
|
247
367
|
console.error('加载素材树出错:', err)
|
|
248
|
-
notify(err.message || '无法加载素材库,请确认素材接口地址或配置文件是否正确。')
|
|
249
368
|
} finally {
|
|
250
369
|
loading.value = false
|
|
251
370
|
}
|
|
252
371
|
}
|
|
253
372
|
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
await fetchCategoryPage(cat.type, 1, false, searchQuery.value.trim())
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// 返回首页分类树
|
|
264
|
-
function backToTree() {
|
|
265
|
-
viewMode.value = 'tree'
|
|
266
|
-
activeCategory.value = null
|
|
267
|
-
currentPage.value = 1
|
|
268
|
-
detailMaterials.value = []
|
|
269
|
-
loadTreeMaterials(searchQuery.value.trim())
|
|
270
|
-
}
|
|
373
|
+
// 分页与异常熔断状态(彻底杜绝滚动到底部时的无限死循环请求)
|
|
374
|
+
const loadingMore = ref(false)
|
|
375
|
+
const loadFailed = ref(false)
|
|
376
|
+
const loadErrorMsg = ref('')
|
|
377
|
+
const reachEnd = ref(false)
|
|
378
|
+
let scrollThrottleTimer: number | null = null
|
|
271
379
|
|
|
272
|
-
//
|
|
380
|
+
// 2. 请求某一分类的分页接口 (/jvs-dam/api/dam/material/page)
|
|
273
381
|
async function fetchCategoryPage(type: string, page = 1, append = false, query = '') {
|
|
274
382
|
const pageUrl = (materialPageApiUrl?.value && materialPageApiUrl.value.trim()) || DEFAULT_MATERIAL_PAGE_API_URL
|
|
275
|
-
if (!pageUrl)
|
|
383
|
+
if (!pageUrl) {
|
|
384
|
+
reachEnd.value = true
|
|
385
|
+
return
|
|
386
|
+
}
|
|
276
387
|
|
|
277
388
|
try {
|
|
278
389
|
loading.value = !append
|
|
279
|
-
|
|
390
|
+
loadingMore.value = append
|
|
391
|
+
loadFailed.value = false
|
|
392
|
+
loadErrorMsg.value = ''
|
|
393
|
+
|
|
394
|
+
let url = `${pageUrl}${pageUrl.includes('?') ? '&' : '?'}current=${page}&size=${pageSize}`
|
|
395
|
+
if (type && type !== 'all') {
|
|
396
|
+
url += `&type=${encodeURIComponent(type)}`
|
|
397
|
+
}
|
|
280
398
|
const q = query.trim()
|
|
281
399
|
if (q) {
|
|
282
400
|
url += `&title=${encodeURIComponent(q)}`
|
|
@@ -288,110 +406,187 @@ async function fetchCategoryPage(type: string, page = 1, append = false, query =
|
|
|
288
406
|
})
|
|
289
407
|
|
|
290
408
|
const newRecords = parseMaterialList(data)
|
|
409
|
+
|
|
410
|
+
// 若返回空数组,说明后续已无更多数据,彻底关闭加载开关
|
|
411
|
+
if (newRecords.length === 0) {
|
|
412
|
+
reachEnd.value = true
|
|
413
|
+
} else {
|
|
414
|
+
reachEnd.value = false
|
|
415
|
+
}
|
|
416
|
+
|
|
291
417
|
if (append) {
|
|
292
|
-
|
|
418
|
+
pagedMaterials.value = [...pagedMaterials.value, ...newRecords]
|
|
293
419
|
} else {
|
|
294
|
-
|
|
420
|
+
pagedMaterials.value = newRecords
|
|
295
421
|
}
|
|
296
422
|
currentPage.value = page
|
|
297
|
-
totalRecords.value = data.data?.total ?? newRecords.length
|
|
423
|
+
totalRecords.value = data.data?.total ?? (newRecords.length < pageSize ? (page - 1) * pageSize + newRecords.length : 9999)
|
|
298
424
|
} catch (err: any) {
|
|
299
|
-
console.error('
|
|
300
|
-
|
|
425
|
+
console.error('加载分页素材出错:', err)
|
|
426
|
+
// 熔断保护:记录失败并终止滚动自动重试
|
|
427
|
+
loadFailed.value = true
|
|
428
|
+
loadErrorMsg.value = err.message || '网络连接或接口异常'
|
|
301
429
|
} finally {
|
|
302
430
|
loading.value = false
|
|
431
|
+
loadingMore.value = false
|
|
303
432
|
}
|
|
304
433
|
}
|
|
305
434
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
435
|
+
// 聚合当前素材列表
|
|
436
|
+
const rawSourceMaterials = computed<MaterialItem[]>(() => {
|
|
437
|
+
if (pagedMaterials.value.length > 0) {
|
|
438
|
+
if (filterParams.activeType === 'all') {
|
|
439
|
+
const treeItems: MaterialItem[] = []
|
|
440
|
+
categoryTree.value.forEach(cat => {
|
|
441
|
+
if (cat.items && cat.items.length > 0) {
|
|
442
|
+
treeItems.push(...cat.items)
|
|
443
|
+
}
|
|
444
|
+
})
|
|
445
|
+
const map = new Map<string | number, MaterialItem>()
|
|
446
|
+
treeItems.forEach(i => map.set(i.id, i))
|
|
447
|
+
pagedMaterials.value.forEach(i => map.set(i.id, i))
|
|
448
|
+
return Array.from(map.values())
|
|
449
|
+
}
|
|
450
|
+
return pagedMaterials.value
|
|
451
|
+
}
|
|
452
|
+
const items: MaterialItem[] = []
|
|
453
|
+
categoryTree.value.forEach(cat => {
|
|
454
|
+
if (cat.items && cat.items.length > 0) {
|
|
455
|
+
items.push(...cat.items)
|
|
456
|
+
}
|
|
457
|
+
})
|
|
458
|
+
return items
|
|
459
|
+
})
|
|
311
460
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
} else {
|
|
318
|
-
// 在首页树搜索
|
|
319
|
-
viewMode.value = 'tree'
|
|
320
|
-
activeCategory.value = null
|
|
321
|
-
await loadTreeMaterials(query)
|
|
461
|
+
// 是否还有更多数据可供加载(若已失败或已到底,立即返回 false,防止触底循环)
|
|
462
|
+
const hasMore = computed(() => {
|
|
463
|
+
if (reachEnd.value || loadFailed.value) return false
|
|
464
|
+
if (totalRecords.value > 0) {
|
|
465
|
+
return displayedMaterials.value.length < totalRecords.value
|
|
322
466
|
}
|
|
467
|
+
return false
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
// 加载更多分页数据
|
|
471
|
+
async function loadMore() {
|
|
472
|
+
if (loading.value || loadingMore.value || !hasMore.value || loadFailed.value || reachEnd.value) return
|
|
473
|
+
const nextPage = currentPage.value + 1
|
|
474
|
+
await fetchCategoryPage(filterParams.activeType, nextPage, true, filterParams.query)
|
|
323
475
|
}
|
|
324
476
|
|
|
325
|
-
//
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
477
|
+
// 手动点击重试(重置失败状态并重新触发加载)
|
|
478
|
+
async function retryLoadMore() {
|
|
479
|
+
loadFailed.value = false
|
|
480
|
+
loadErrorMsg.value = ''
|
|
481
|
+
const targetPage = currentPage.value + 1
|
|
482
|
+
await fetchCategoryPage(filterParams.activeType, targetPage, true, filterParams.query)
|
|
330
483
|
}
|
|
331
484
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
485
|
+
// 瀑布流滚动触底自动加载监听(带 250ms 节流与熔断保护)
|
|
486
|
+
function handleScroll(e: Event) {
|
|
487
|
+
if (loading.value || loadingMore.value || loadFailed.value || reachEnd.value || !hasMore.value) {
|
|
488
|
+
return
|
|
489
|
+
}
|
|
335
490
|
|
|
336
|
-
|
|
491
|
+
if (scrollThrottleTimer) return
|
|
492
|
+
scrollThrottleTimer = window.setTimeout(() => {
|
|
493
|
+
scrollThrottleTimer = null
|
|
494
|
+
}, 250)
|
|
337
495
|
|
|
338
|
-
function onCategoryScroll(catId: string, e: Event) {
|
|
339
496
|
const el = e.target as HTMLElement
|
|
340
497
|
if (!el) return
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
scrollStates.value[catId] = { canLeft, canRight }
|
|
498
|
+
if (el.scrollHeight - (el.scrollTop + el.clientHeight) < 140) {
|
|
499
|
+
loadMore()
|
|
500
|
+
}
|
|
345
501
|
}
|
|
346
502
|
|
|
347
|
-
//
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
503
|
+
// 根据红框筛选条件过滤后的瀑布流素材列表(红框4)
|
|
504
|
+
const displayedMaterials = computed<MaterialItem[]>(() => {
|
|
505
|
+
return rawSourceMaterials.value.filter(item => {
|
|
506
|
+
// 1. 类型过滤 (红框2)
|
|
507
|
+
if (filterParams.activeType !== 'all') {
|
|
508
|
+
if (item.type !== filterParams.activeType) return false
|
|
509
|
+
}
|
|
510
|
+
// 2. 格式过滤 (红框3)
|
|
511
|
+
if (filterParams.format) {
|
|
512
|
+
const fmt = getItemFormat(item)
|
|
513
|
+
if (fmt.toUpperCase() !== filterParams.format.toUpperCase()) return false
|
|
356
514
|
}
|
|
515
|
+
// 3. 颜色过滤 (红框3)
|
|
516
|
+
if (filterParams.color && item.color) {
|
|
517
|
+
if (item.color.toLowerCase() !== filterParams.color.toLowerCase()) return false
|
|
518
|
+
}
|
|
519
|
+
return true
|
|
520
|
+
})
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
// 筛选标签计算
|
|
524
|
+
const currentSortLabel = computed(() => {
|
|
525
|
+
const match = sortOptions.find(o => o.value === filterParams.sort)
|
|
526
|
+
return match ? match.label : '综合排序'
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
const currentColorLabel = computed(() => {
|
|
530
|
+
if (!filterParams.color) return '颜色'
|
|
531
|
+
const match = colorOptions.find(c => c.hex.toLowerCase() === filterParams.color.toLowerCase())
|
|
532
|
+
return match ? match.name : '已选色'
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
const hasActiveFilters = computed(() => {
|
|
536
|
+
return !!filterParams.format || !!filterParams.color || filterParams.sort !== 'recommend'
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
// 切换分类 Tab(红框2)
|
|
540
|
+
async function switchCategoryTab(typeValue: string) {
|
|
541
|
+
filterParams.activeType = typeValue
|
|
542
|
+
currentPage.value = 1
|
|
543
|
+
pagedMaterials.value = []
|
|
544
|
+
if (typeValue !== 'all') {
|
|
545
|
+
await fetchCategoryPage(typeValue, 1, false, filterParams.query)
|
|
546
|
+
} else {
|
|
547
|
+
await loadTreeMaterials(filterParams.query)
|
|
357
548
|
}
|
|
358
549
|
}
|
|
359
550
|
|
|
360
|
-
//
|
|
361
|
-
function
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
el = document.getElementById(`cat-scroll-${catId}`)
|
|
369
|
-
}
|
|
370
|
-
if (el) {
|
|
371
|
-
// 平滑滚动步长:取容器当前可视宽度的 75%(至少 160px,至多 320px)
|
|
372
|
-
const step = Math.max(160, Math.min(320, Math.floor(el.clientWidth * 0.75)))
|
|
373
|
-
const offset = direction === 'right' ? step : -step
|
|
374
|
-
el.scrollBy({ left: offset, behavior: 'smooth' })
|
|
375
|
-
setTimeout(() => {
|
|
376
|
-
if (el) {
|
|
377
|
-
const canLeft = el.scrollLeft > 10
|
|
378
|
-
const hasOverflow = el.scrollWidth > el.clientWidth + 10
|
|
379
|
-
const canRight = hasOverflow ? (el.scrollLeft < el.scrollWidth - el.clientWidth - 10) : false
|
|
380
|
-
scrollStates.value[catId] = { canLeft, canRight }
|
|
381
|
-
}
|
|
382
|
-
}, 320)
|
|
551
|
+
// 搜索处理(红框1)
|
|
552
|
+
async function handleSearch() {
|
|
553
|
+
currentPage.value = 1
|
|
554
|
+
pagedMaterials.value = []
|
|
555
|
+
if (filterParams.activeType !== 'all') {
|
|
556
|
+
await fetchCategoryPage(filterParams.activeType, 1, false, filterParams.query)
|
|
557
|
+
} else {
|
|
558
|
+
await loadTreeMaterials(filterParams.query)
|
|
383
559
|
}
|
|
384
560
|
}
|
|
385
561
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
562
|
+
function clearQuery() {
|
|
563
|
+
filterParams.query = ''
|
|
564
|
+
handleSearch()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function handleSortChange(val: string) {
|
|
568
|
+
filterParams.sort = val
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function handleFormatChange(val: string) {
|
|
572
|
+
filterParams.format = val
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function handleColorChange(val: string) {
|
|
576
|
+
filterParams.color = val
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resetFilters() {
|
|
580
|
+
filterParams.sort = 'recommend'
|
|
581
|
+
filterParams.format = ''
|
|
582
|
+
filterParams.color = ''
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function openItemMenu(item: MaterialItem) {
|
|
586
|
+
notify(`素材: ${item.title || '无标题'} (${getItemFormat(item)})`)
|
|
392
587
|
}
|
|
393
588
|
|
|
394
|
-
//
|
|
589
|
+
// 点击使用/插入素材至画布
|
|
395
590
|
function handleSelectMaterial(meta: MaterialItem) {
|
|
396
591
|
if (meta.type === 'slide' || meta.slide) {
|
|
397
592
|
handleInsertSlide(meta)
|
|
@@ -399,9 +594,9 @@ function handleSelectMaterial(meta: MaterialItem) {
|
|
|
399
594
|
const imgUrl = meta.url || meta.previewUrl || meta.downloadUrl
|
|
400
595
|
if (imgUrl) {
|
|
401
596
|
addElement('image', imgUrl)
|
|
402
|
-
notify(
|
|
597
|
+
notify(`已成功添加素材 "${meta.title || '图片'}" 到画布`)
|
|
403
598
|
} else {
|
|
404
|
-
notify('
|
|
599
|
+
notify('该素材暂无可用的图片地址')
|
|
405
600
|
}
|
|
406
601
|
}
|
|
407
602
|
}
|
|
@@ -410,7 +605,6 @@ function handleSelectMaterial(meta: MaterialItem) {
|
|
|
410
605
|
async function handleInsertSlide(meta: MaterialItem) {
|
|
411
606
|
try {
|
|
412
607
|
notify(`正在读取页面素材 [${meta.title || '单页模板'}]...`)
|
|
413
|
-
|
|
414
608
|
const fallbackSize = (typeof meta.originalWidth === 'number' && typeof meta.originalHeight === 'number')
|
|
415
609
|
? { width: meta.originalWidth, height: meta.originalHeight }
|
|
416
610
|
: undefined
|
|
@@ -446,181 +640,219 @@ async function handleInsertSlide(meta: MaterialItem) {
|
|
|
446
640
|
if (!slideObj) {
|
|
447
641
|
throw new Error('单页数据格式异常')
|
|
448
642
|
}
|
|
449
|
-
|
|
643
|
+
|
|
450
644
|
insertExternalSlide(slideObj, originalSize)
|
|
645
|
+
notify(`已成功插入单页模板 [${meta.title || '素材'}]`)
|
|
451
646
|
} catch (err: any) {
|
|
452
|
-
console.error('
|
|
453
|
-
notify(
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function truncateText(text: string, len = 20): string {
|
|
458
|
-
if (!text || text === '无文字页面') return '(排版占位/纯图形页)'
|
|
459
|
-
return text.length > len ? text.substring(0, len) + '...' : text
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// 打开保存当前页对话框
|
|
463
|
-
function openSaveDialog() {
|
|
464
|
-
const currentTitle = activeSlide.value?.title || `幻灯片素材_${Date.now().toString().slice(-4)}`
|
|
465
|
-
saveForm.value = {
|
|
466
|
-
title: currentTitle,
|
|
467
|
-
category: '正文'
|
|
647
|
+
console.error('加载单页模板失败:', err)
|
|
648
|
+
notify(err.message || '加载单页模板失败')
|
|
468
649
|
}
|
|
469
|
-
saveDialogVisible.value = true
|
|
470
650
|
}
|
|
471
651
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (!saveForm.value.title.trim()) {
|
|
475
|
-
notify('请输入素材名称')
|
|
476
|
-
return
|
|
477
|
-
}
|
|
652
|
+
onMounted(() => {
|
|
653
|
+
loadTreeMaterials()
|
|
478
654
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
655
|
+
// 监听容器大小以实现极致自适应
|
|
656
|
+
if (containerRef.value) {
|
|
657
|
+
resizeObserver = new ResizeObserver((entries) => {
|
|
658
|
+
for (const entry of entries) {
|
|
659
|
+
isNarrow.value = entry.contentRect.width < 520
|
|
660
|
+
}
|
|
484
661
|
})
|
|
485
|
-
|
|
486
|
-
saveDialogVisible.value = false
|
|
487
|
-
await loadTreeMaterials()
|
|
488
|
-
}
|
|
489
|
-
} finally {
|
|
490
|
-
saveLoading.value = false
|
|
662
|
+
resizeObserver.observe(containerRef.value)
|
|
491
663
|
}
|
|
492
|
-
}
|
|
493
664
|
|
|
494
|
-
|
|
495
|
-
|
|
665
|
+
// 键盘快捷键监听
|
|
666
|
+
window.addEventListener('keydown', handleKeydown)
|
|
496
667
|
})
|
|
497
668
|
|
|
498
|
-
|
|
499
|
-
|
|
669
|
+
onUnmounted(() => {
|
|
670
|
+
if (resizeObserver) {
|
|
671
|
+
resizeObserver.disconnect()
|
|
672
|
+
resizeObserver = null
|
|
673
|
+
}
|
|
674
|
+
window.removeEventListener('keydown', handleKeydown)
|
|
500
675
|
})
|
|
676
|
+
|
|
501
677
|
watch(() => materialTreeApiUrl?.value, () => {
|
|
502
678
|
loadTreeMaterials()
|
|
503
679
|
})
|
|
504
|
-
|
|
505
|
-
// 搜索词清空时自动还原对应模式的数据
|
|
506
|
-
watch(searchQuery, (newVal) => {
|
|
507
|
-
if (!newVal.trim()) {
|
|
508
|
-
if (viewMode.value === 'category' && activeCategory.value) {
|
|
509
|
-
currentPage.value = 1
|
|
510
|
-
fetchCategoryPage(activeCategory.value.type, 1, false, '')
|
|
511
|
-
} else if (viewMode.value === 'tree') {
|
|
512
|
-
loadTreeMaterials('')
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
})
|
|
516
680
|
</script>
|
|
517
681
|
|
|
518
682
|
<template>
|
|
519
|
-
<div
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
683
|
+
<div
|
|
684
|
+
ref="containerRef"
|
|
685
|
+
class="template-gallery-container"
|
|
686
|
+
:class="{ 'is-narrow-view': isNarrow }"
|
|
687
|
+
>
|
|
688
|
+
<!-- 1. 顶部现代化搜索栏(红框1) -->
|
|
689
|
+
<div class="gallery-top-search">
|
|
690
|
+
<div class="search-input-capsule">
|
|
691
|
+
<!-- 搜索输入框 -->
|
|
523
692
|
<input
|
|
524
|
-
v-model="
|
|
693
|
+
v-model="filterParams.query"
|
|
525
694
|
type="text"
|
|
526
|
-
:placeholder="
|
|
527
|
-
class="
|
|
695
|
+
:placeholder="isNarrow ? '搜索素材、关键词...' : '搜索素材标题、关键词,按回车检索...'"
|
|
696
|
+
class="native-search-input"
|
|
528
697
|
@keyup.enter="handleSearch"
|
|
529
698
|
/>
|
|
530
|
-
|
|
699
|
+
|
|
700
|
+
<!-- 清空图标 -->
|
|
701
|
+
<el-icon
|
|
702
|
+
v-if="filterParams.query"
|
|
703
|
+
class="search-clear-icon"
|
|
704
|
+
@click="clearQuery"
|
|
705
|
+
>
|
|
706
|
+
<CircleCloseFilled />
|
|
707
|
+
</el-icon>
|
|
708
|
+
|
|
709
|
+
<!-- 搜索胶囊按钮(窄屏下仅显示放大镜图标,腾出输入空间) -->
|
|
531
710
|
<button
|
|
532
711
|
type="button"
|
|
533
|
-
class="
|
|
712
|
+
class="capsule-search-btn"
|
|
534
713
|
@click="handleSearch"
|
|
535
|
-
|
|
714
|
+
:disabled="loading"
|
|
715
|
+
:title="isNarrow ? '搜索素材' : ''"
|
|
536
716
|
>
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
</svg>
|
|
541
|
-
<span>搜索</span>
|
|
717
|
+
<el-icon v-if="!loading"><Search /></el-icon>
|
|
718
|
+
<span v-else class="btn-spinner-ring"></span>
|
|
719
|
+
<span class="btn-text">搜索</span>
|
|
542
720
|
</button>
|
|
543
721
|
</div>
|
|
722
|
+
</div>
|
|
544
723
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
724
|
+
<!-- 2. 分类 Tabs 与多维筛选工具栏(红框2 + 红框3) -->
|
|
725
|
+
<div class="gallery-toolbar-row">
|
|
726
|
+
<!-- 红框2:分类类型胶囊组(支持横向触摸/滚轮滑动,不换行截断) -->
|
|
727
|
+
<div class="category-pill-group">
|
|
728
|
+
<button
|
|
729
|
+
v-for="tab in categoryTabs"
|
|
730
|
+
:key="tab.value"
|
|
731
|
+
class="pill-tab-btn"
|
|
732
|
+
:class="{ active: filterParams.activeType === tab.value }"
|
|
733
|
+
@click="switchCategoryTab(tab.value)"
|
|
734
|
+
>
|
|
735
|
+
{{ tab.label }}
|
|
552
736
|
</button>
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
737
|
+
</div>
|
|
738
|
+
|
|
739
|
+
<!-- 红框3:右侧筛选下拉项 -->
|
|
740
|
+
<div class="filter-dropdown-group">
|
|
741
|
+
<!-- 综合排序 -->
|
|
742
|
+
<el-dropdown trigger="click" @command="handleSortChange">
|
|
743
|
+
<span class="toolbar-dropdown-btn" :class="{ 'is-active': filterParams.sort !== 'recommend' }">
|
|
744
|
+
{{ currentSortLabel }}
|
|
745
|
+
<el-icon class="dropdown-arrow"><ArrowDown /></el-icon>
|
|
746
|
+
</span>
|
|
747
|
+
<template #dropdown>
|
|
748
|
+
<el-dropdown-menu>
|
|
749
|
+
<el-dropdown-item
|
|
750
|
+
v-for="item in sortOptions"
|
|
751
|
+
:key="item.value"
|
|
752
|
+
:command="item.value"
|
|
753
|
+
:class="{ 'is-selected': filterParams.sort === item.value }"
|
|
754
|
+
>
|
|
755
|
+
{{ item.label }}
|
|
756
|
+
</el-dropdown-item>
|
|
757
|
+
</el-dropdown-menu>
|
|
758
|
+
</template>
|
|
759
|
+
</el-dropdown>
|
|
760
|
+
|
|
761
|
+
<!-- 格式筛选 -->
|
|
762
|
+
<el-dropdown trigger="click" @command="handleFormatChange">
|
|
763
|
+
<span class="toolbar-dropdown-btn" :class="{ 'is-active': !!filterParams.format }">
|
|
764
|
+
{{ filterParams.format ? `格式: ${filterParams.format}` : '格式' }}
|
|
765
|
+
<el-icon class="dropdown-arrow"><ArrowDown /></el-icon>
|
|
557
766
|
</span>
|
|
558
|
-
|
|
767
|
+
<template #dropdown>
|
|
768
|
+
<el-dropdown-menu class="custom-dropdown-menu">
|
|
769
|
+
<el-dropdown-item command="" :class="{ 'is-selected': !filterParams.format }">
|
|
770
|
+
全部格式
|
|
771
|
+
</el-dropdown-item>
|
|
772
|
+
<el-dropdown-item
|
|
773
|
+
v-for="fmt in formatOptions"
|
|
774
|
+
:key="fmt"
|
|
775
|
+
:command="fmt"
|
|
776
|
+
:class="{ 'is-selected': filterParams.format === fmt }"
|
|
777
|
+
>
|
|
778
|
+
{{ fmt }}
|
|
779
|
+
</el-dropdown-item>
|
|
780
|
+
</el-dropdown-menu>
|
|
781
|
+
</template>
|
|
782
|
+
</el-dropdown>
|
|
783
|
+
|
|
784
|
+
<!-- 颜色筛选 -->
|
|
785
|
+
<el-dropdown trigger="click" @command="handleColorChange">
|
|
786
|
+
<span class="toolbar-dropdown-btn" :class="{ 'is-active': !!filterParams.color }">
|
|
787
|
+
<span v-if="filterParams.color" class="color-badge-dot" :style="{ backgroundColor: filterParams.color }"></span>
|
|
788
|
+
{{ currentColorLabel }}
|
|
789
|
+
<el-icon class="dropdown-arrow"><ArrowDown /></el-icon>
|
|
790
|
+
</span>
|
|
791
|
+
<template #dropdown>
|
|
792
|
+
<div class="color-picker-palette">
|
|
793
|
+
<div class="palette-header-label">按主色调筛选</div>
|
|
794
|
+
<div class="palette-swatch-grid">
|
|
795
|
+
<div
|
|
796
|
+
class="swatch-item all-swatch"
|
|
797
|
+
:class="{ active: !filterParams.color }"
|
|
798
|
+
@click="handleColorChange('')"
|
|
799
|
+
title="全部颜色"
|
|
800
|
+
>
|
|
801
|
+
全部
|
|
802
|
+
</div>
|
|
803
|
+
<div
|
|
804
|
+
v-for="color in colorOptions"
|
|
805
|
+
:key="color.hex"
|
|
806
|
+
class="swatch-item"
|
|
807
|
+
:class="{ active: filterParams.color === color.hex }"
|
|
808
|
+
:style="{ backgroundColor: color.hex }"
|
|
809
|
+
:title="color.name"
|
|
810
|
+
@click="handleColorChange(color.hex)"
|
|
811
|
+
></div>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
</template>
|
|
815
|
+
</el-dropdown>
|
|
816
|
+
|
|
817
|
+
<!-- 一键重置 -->
|
|
818
|
+
<button
|
|
819
|
+
v-if="hasActiveFilters"
|
|
820
|
+
class="reset-filters-link"
|
|
821
|
+
@click="resetFilters"
|
|
822
|
+
title="清除所有筛选"
|
|
823
|
+
>
|
|
824
|
+
重置
|
|
825
|
+
</button>
|
|
559
826
|
</div>
|
|
560
827
|
</div>
|
|
561
828
|
|
|
562
|
-
<!--
|
|
563
|
-
<div v-if="loading &&
|
|
564
|
-
<div class="loading-spinner"></div>
|
|
565
|
-
<span
|
|
829
|
+
<!-- 加载中动画 -->
|
|
830
|
+
<div v-if="loading && displayedMaterials.length === 0" class="gallery-loading-box">
|
|
831
|
+
<div class="loading-ring-spinner"></div>
|
|
832
|
+
<span class="loading-tip-text">正在检索素材媒体库...</span>
|
|
566
833
|
</div>
|
|
567
834
|
|
|
568
|
-
<!--
|
|
569
|
-
<div v-else
|
|
570
|
-
<div
|
|
571
|
-
|
|
572
|
-
:key="cat.id"
|
|
573
|
-
class="category-block"
|
|
574
|
-
>
|
|
575
|
-
<!-- 分类标题栏(点击进入该分类) -->
|
|
576
|
-
<div class="category-block-header" @click="enterCategory(cat)" title="点击查看全部">
|
|
577
|
-
<div class="category-title-wrap">
|
|
578
|
-
<span class="category-title-text">{{ cat.name }}</span>
|
|
579
|
-
<svg class="category-arrow-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
580
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
581
|
-
<polyline points="12 5 19 12 12 19" />
|
|
582
|
-
</svg>
|
|
583
|
-
</div>
|
|
584
|
-
<span class="category-more-text">查看全部</span>
|
|
585
|
-
</div>
|
|
586
|
-
|
|
587
|
-
<!-- 分类素材横向流容器 -->
|
|
588
|
-
<div class="category-row-wrapper">
|
|
589
|
-
<!-- 左翻页平滑滚动按钮 -->
|
|
590
|
-
<button
|
|
591
|
-
v-if="cat.items && cat.items.length > 2 && (scrollStates[cat.id]?.canLeft ?? false)"
|
|
592
|
-
type="button"
|
|
593
|
-
class="scroll-nav-btn scroll-left-btn"
|
|
594
|
-
@click.stop="scrollCategory(cat.id, 'left', $event)"
|
|
595
|
-
title="向左滚动"
|
|
596
|
-
>
|
|
597
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
598
|
-
<polyline points="15 18 9 12 15 6" />
|
|
599
|
-
</svg>
|
|
600
|
-
</button>
|
|
601
|
-
|
|
835
|
+
<!-- 3. 主体内容区:纯瀑布流排版(红框4),支持滚动触底自动加载 -->
|
|
836
|
+
<div v-else class="waterfall-scroll-container" @scroll="handleScroll">
|
|
837
|
+
<div v-if="displayedMaterials.length > 0" class="waterfall-masonry-wrapper">
|
|
838
|
+
<div class="waterfall-masonry-layout">
|
|
602
839
|
<div
|
|
603
|
-
|
|
604
|
-
:
|
|
605
|
-
class="
|
|
606
|
-
@
|
|
840
|
+
v-for="(item, idx) in displayedMaterials"
|
|
841
|
+
:key="item.id || idx"
|
|
842
|
+
class="waterfall-card-item"
|
|
843
|
+
@click="handleSelectMaterial(item)"
|
|
607
844
|
>
|
|
608
|
-
<div
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
class="tree-item-card"
|
|
612
|
-
@click="handleSelectMaterial(item)"
|
|
613
|
-
>
|
|
614
|
-
<div class="tree-item-img-box">
|
|
845
|
+
<div class="card-box">
|
|
846
|
+
<!-- 图片视口 -->
|
|
847
|
+
<div class="card-media-viewport">
|
|
615
848
|
<img
|
|
616
849
|
v-if="getMaterialThumb(item)"
|
|
617
850
|
:src="getMaterialThumb(item)"
|
|
618
|
-
:alt="item.title"
|
|
851
|
+
:alt="item.title || '素材'"
|
|
619
852
|
loading="lazy"
|
|
620
|
-
class="
|
|
853
|
+
class="card-thumb-image"
|
|
621
854
|
/>
|
|
622
|
-
|
|
623
|
-
<div v-else class="thumb-placeholder">
|
|
855
|
+
<div v-else class="card-empty-placeholder">
|
|
624
856
|
<svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="#94a3b8" stroke-width="1.5">
|
|
625
857
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
626
858
|
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
@@ -628,832 +860,1055 @@ watch(searchQuery, (newVal) => {
|
|
|
628
860
|
</svg>
|
|
629
861
|
</div>
|
|
630
862
|
|
|
631
|
-
<!-- hover
|
|
632
|
-
<div class="
|
|
863
|
+
<!-- hover 悬浮层:中央大加号插入 + 右上角大图预览 -->
|
|
864
|
+
<div class="card-hover-mask">
|
|
633
865
|
<button
|
|
634
866
|
v-if="getMaterialThumb(item)"
|
|
635
867
|
type="button"
|
|
636
|
-
class="
|
|
637
|
-
title="
|
|
638
|
-
@click.stop="openPreview(item,
|
|
639
|
-
>
|
|
640
|
-
<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.2">
|
|
641
|
-
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
642
|
-
<circle cx="12" cy="12" r="3" />
|
|
643
|
-
</svg>
|
|
644
|
-
</button>
|
|
645
|
-
<button
|
|
646
|
-
type="button"
|
|
647
|
-
class="card-use-btn"
|
|
648
|
-
title="使用素材"
|
|
649
|
-
@click.stop="handleSelectMaterial(item)"
|
|
868
|
+
class="preview-float-btn"
|
|
869
|
+
title="大图预览"
|
|
870
|
+
@click.stop.prevent="openPreview(item, $event)"
|
|
650
871
|
>
|
|
651
|
-
|
|
872
|
+
<el-icon><View /></el-icon>
|
|
652
873
|
</button>
|
|
874
|
+
<div class="insert-float-action">
|
|
875
|
+
<div class="insert-circle-btn">
|
|
876
|
+
<el-icon><Plus /></el-icon>
|
|
877
|
+
</div>
|
|
878
|
+
<span class="insert-tip">点击插入画布</span>
|
|
879
|
+
</div>
|
|
653
880
|
</div>
|
|
654
881
|
</div>
|
|
655
|
-
<div class="tree-item-title" :title="item.title">{{ item.title || '素材' }}</div>
|
|
656
|
-
</div>
|
|
657
882
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
883
|
+
<!-- 卡片底部信息条 -->
|
|
884
|
+
<div class="card-bottom-info">
|
|
885
|
+
<div class="format-badge-box">
|
|
886
|
+
<span class="format-tag">{{ getItemFormat(item) }}</span>
|
|
887
|
+
<span v-if="item.title" class="format-title-hint" :title="item.title">{{ item.title }}</span>
|
|
888
|
+
</div>
|
|
889
|
+
<button type="button" class="more-options-dot-btn" @click.stop="openItemMenu(item)">
|
|
890
|
+
<el-icon><MoreFilled /></el-icon>
|
|
891
|
+
</button>
|
|
892
|
+
</div>
|
|
661
893
|
</div>
|
|
662
894
|
</div>
|
|
895
|
+
</div>
|
|
663
896
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
class="
|
|
669
|
-
|
|
670
|
-
|
|
897
|
+
<!-- 瀑布流底部加载更多与完成状态 -->
|
|
898
|
+
<div class="waterfall-footer-status">
|
|
899
|
+
<!-- 1. 正在加载中 -->
|
|
900
|
+
<div v-if="loadingMore" class="loading-more-row">
|
|
901
|
+
<span class="btn-spinner-ring dark"></span>
|
|
902
|
+
<span>正在加载更多素材...</span>
|
|
903
|
+
</div>
|
|
904
|
+
|
|
905
|
+
<!-- 2. 加载失败:展示重试按钮,彻底阻断滚动死循环 -->
|
|
906
|
+
<div v-else-if="loadFailed" class="load-failed-row">
|
|
907
|
+
<span class="load-error-text">加载失败: {{ loadErrorMsg || '网络连接或接口异常' }}</span>
|
|
908
|
+
<button type="button" class="retry-load-btn" @click="retryLoadMore">
|
|
909
|
+
点击重试
|
|
910
|
+
</button>
|
|
911
|
+
</div>
|
|
912
|
+
|
|
913
|
+
<!-- 3. 还有更多数据可供加载 -->
|
|
914
|
+
<button
|
|
915
|
+
v-else-if="hasMore"
|
|
916
|
+
type="button"
|
|
917
|
+
class="load-more-action-btn"
|
|
918
|
+
@click="loadMore"
|
|
671
919
|
>
|
|
672
|
-
<
|
|
673
|
-
|
|
674
|
-
</svg>
|
|
920
|
+
<span>加载更多素材</span>
|
|
921
|
+
<span v-if="totalRecords > 0" class="load-more-count">({{ displayedMaterials.length }} / {{ totalRecords }})</span>
|
|
675
922
|
</button>
|
|
923
|
+
|
|
924
|
+
<!-- 4. 已全部加载完毕 -->
|
|
925
|
+
<div v-else class="all-loaded-divider">
|
|
926
|
+
<span>— 已展示全部素材 (共 {{ displayedMaterials.length }} 项) —</span>
|
|
927
|
+
</div>
|
|
676
928
|
</div>
|
|
677
929
|
</div>
|
|
678
930
|
|
|
679
|
-
<!--
|
|
680
|
-
<div v-
|
|
681
|
-
|
|
931
|
+
<!-- 空状态 -->
|
|
932
|
+
<div v-else class="gallery-empty-state">
|
|
933
|
+
<svg viewBox="0 0 24 24" width="44" height="44" fill="none" stroke="#cbd5e1" stroke-width="1.2">
|
|
934
|
+
<circle cx="11" cy="11" r="8" />
|
|
935
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
936
|
+
</svg>
|
|
937
|
+
<p class="empty-title">{{ filterParams.query ? `未检索到与 "${filterParams.query}" 相关的素材` : '当前分类暂无素材推荐' }}</p>
|
|
938
|
+
<span class="empty-subtitle">尝试切换上方「全部/图片」分类或调整筛选条件</span>
|
|
682
939
|
</div>
|
|
683
940
|
</div>
|
|
684
941
|
|
|
685
|
-
<!--
|
|
686
|
-
<
|
|
687
|
-
<
|
|
942
|
+
<!-- 100% 自研可靠的高清全屏大图预览 Modal(Teleport 挂载到 body 顶层) -->
|
|
943
|
+
<teleport to="body">
|
|
944
|
+
<transition name="preview-backdrop-fade">
|
|
688
945
|
<div
|
|
689
|
-
v-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
@click="handleSelectMaterial(item)"
|
|
946
|
+
v-if="previewVisible"
|
|
947
|
+
class="custom-fullscreen-preview-modal"
|
|
948
|
+
@click.self="closePreview"
|
|
693
949
|
>
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
:alt="item.title"
|
|
700
|
-
loading="lazy"
|
|
701
|
-
class="grid-thumb-img"
|
|
702
|
-
/>
|
|
703
|
-
<!-- 无图时的占位相框 -->
|
|
704
|
-
<div v-else class="thumb-placeholder">
|
|
705
|
-
<svg viewBox="0 0 24 24" width="32" height="32" fill="none" stroke="#94a3b8" stroke-width="1.5">
|
|
706
|
-
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
|
707
|
-
<circle cx="8.5" cy="8.5" r="1.5" />
|
|
708
|
-
<polyline points="21 15 16 10 5 21" />
|
|
709
|
-
</svg>
|
|
710
|
-
</div>
|
|
711
|
-
|
|
712
|
-
<!-- hover 悬浮层:右上角预览图标,右下角使用按钮 -->
|
|
713
|
-
<div class="grid-hover-overlay img-hover-mask">
|
|
714
|
-
<button
|
|
715
|
-
v-if="getMaterialThumb(item)"
|
|
716
|
-
type="button"
|
|
717
|
-
class="card-preview-btn"
|
|
718
|
-
title="预览图片"
|
|
719
|
-
@click.stop="openPreview(item, detailMaterials)"
|
|
720
|
-
>
|
|
721
|
-
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.2">
|
|
722
|
-
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
|
723
|
-
<circle cx="12" cy="12" r="3" />
|
|
724
|
-
</svg>
|
|
725
|
-
</button>
|
|
726
|
-
<button
|
|
727
|
-
type="button"
|
|
728
|
-
class="card-use-btn"
|
|
729
|
-
title="使用素材"
|
|
730
|
-
@click.stop="handleSelectMaterial(item)"
|
|
731
|
-
>
|
|
732
|
-
使用
|
|
733
|
-
</button>
|
|
734
|
-
</div>
|
|
735
|
-
</div>
|
|
736
|
-
<div class="grid-item-info">
|
|
737
|
-
<div class="grid-item-title" :title="item.title">{{ item.title || '素材' }}</div>
|
|
738
|
-
<span class="grid-tag-badge">{{ item.category || (item.type ? typeNameMap[item.type] : '') || item.type || '素材' }}</span>
|
|
950
|
+
<!-- 顶部浮动信息条 -->
|
|
951
|
+
<div class="preview-header-bar">
|
|
952
|
+
<div class="preview-title-wrap">
|
|
953
|
+
<span class="preview-format-pill">{{ currentPreviewItem?.format || 'IMG' }}</span>
|
|
954
|
+
<span class="preview-title-text">{{ currentPreviewItem?.title || '素材大图预览' }}</span>
|
|
739
955
|
</div>
|
|
956
|
+
<button
|
|
957
|
+
type="button"
|
|
958
|
+
class="preview-close-cross-btn"
|
|
959
|
+
title="关闭预览 (Esc)"
|
|
960
|
+
@click="closePreview"
|
|
961
|
+
>
|
|
962
|
+
<el-icon><Close /></el-icon>
|
|
963
|
+
</button>
|
|
740
964
|
</div>
|
|
741
|
-
</div>
|
|
742
|
-
</div>
|
|
743
965
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
966
|
+
<!-- 左侧上一张 -->
|
|
967
|
+
<button
|
|
968
|
+
v-if="previewList.length > 1"
|
|
969
|
+
type="button"
|
|
970
|
+
class="preview-arrow-btn prev-arrow"
|
|
971
|
+
title="上一张 (←)"
|
|
972
|
+
@click.stop="prevPreview"
|
|
973
|
+
>
|
|
974
|
+
<el-icon><ArrowLeft /></el-icon>
|
|
975
|
+
</button>
|
|
748
976
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
977
|
+
<!-- 右侧下一张 -->
|
|
978
|
+
<button
|
|
979
|
+
v-if="previewList.length > 1"
|
|
980
|
+
type="button"
|
|
981
|
+
class="preview-arrow-btn next-arrow"
|
|
982
|
+
title="下一张 (→)"
|
|
983
|
+
@click.stop="nextPreview"
|
|
984
|
+
>
|
|
985
|
+
<el-icon><ArrowRight /></el-icon>
|
|
986
|
+
</button>
|
|
756
987
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
<
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
988
|
+
<!-- 中央超清大图视口 -->
|
|
989
|
+
<div class="preview-stage-viewport" @click.self="closePreview">
|
|
990
|
+
<img
|
|
991
|
+
v-if="currentPreviewItem?.url"
|
|
992
|
+
:src="currentPreviewItem.url"
|
|
993
|
+
:alt="currentPreviewItem.title"
|
|
994
|
+
class="preview-stage-img"
|
|
995
|
+
/>
|
|
996
|
+
</div>
|
|
997
|
+
|
|
998
|
+
<!-- 底部工具动作条 -->
|
|
999
|
+
<div class="preview-footer-bar">
|
|
1000
|
+
<span v-if="previewList.length > 1" class="preview-page-indicator">
|
|
1001
|
+
{{ previewInitialIndex + 1 }} / {{ previewList.length }}
|
|
1002
|
+
</span>
|
|
1003
|
+
<button
|
|
1004
|
+
type="button"
|
|
1005
|
+
class="preview-quick-insert-btn"
|
|
1006
|
+
@click="handleInsertFromPreview"
|
|
1007
|
+
>
|
|
1008
|
+
<el-icon><Plus /></el-icon>
|
|
1009
|
+
<span>插入此素材到画布</span>
|
|
1010
|
+
</button>
|
|
1011
|
+
</div>
|
|
780
1012
|
</div>
|
|
781
|
-
</
|
|
782
|
-
</
|
|
783
|
-
|
|
784
|
-
<!-- 图片/GIF/SVG 素材大图预览(支持同类型上下/左右切换) -->
|
|
785
|
-
<ElImageViewer
|
|
786
|
-
v-if="previewVisible"
|
|
787
|
-
:url-list="previewList"
|
|
788
|
-
:initial-index="previewInitialIndex"
|
|
789
|
-
:hide-on-click-modal="true"
|
|
790
|
-
:infinite="true"
|
|
791
|
-
@close="previewVisible = false"
|
|
792
|
-
/>
|
|
1013
|
+
</transition>
|
|
1014
|
+
</teleport>
|
|
793
1015
|
</div>
|
|
794
1016
|
</template>
|
|
795
1017
|
|
|
796
|
-
<style scoped>
|
|
1018
|
+
<style scoped lang="scss">
|
|
797
1019
|
.template-gallery-container {
|
|
798
1020
|
display: flex;
|
|
799
1021
|
flex-direction: column;
|
|
800
1022
|
height: 100%;
|
|
801
|
-
|
|
802
|
-
color: var(--theme-sidebar-text, #333333);
|
|
1023
|
+
padding: 14px 16px 16px;
|
|
803
1024
|
box-sizing: border-box;
|
|
1025
|
+
background: var(--bg-surface, #ffffff);
|
|
1026
|
+
gap: 12px;
|
|
1027
|
+
overflow: hidden;
|
|
804
1028
|
}
|
|
805
1029
|
|
|
806
|
-
.
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
display: flex;
|
|
810
|
-
flex-direction: column;
|
|
811
|
-
gap: 10px;
|
|
1030
|
+
/* 1. 顶部现代搜索栏(红框1) */
|
|
1031
|
+
.gallery-top-search {
|
|
1032
|
+
width: 100%;
|
|
812
1033
|
}
|
|
813
1034
|
|
|
814
|
-
.
|
|
1035
|
+
.search-input-capsule {
|
|
815
1036
|
display: flex;
|
|
816
1037
|
align-items: center;
|
|
817
|
-
gap: 8px;
|
|
818
1038
|
width: 100%;
|
|
1039
|
+
height: 42px;
|
|
1040
|
+
background: #f4f6f8;
|
|
1041
|
+
border: 1.5px solid transparent;
|
|
1042
|
+
border-radius: 21px;
|
|
1043
|
+
padding: 3px 4px 3px 14px;
|
|
1044
|
+
box-sizing: border-box;
|
|
1045
|
+
transition: all 0.25s ease;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.search-input-capsule:focus-within {
|
|
1049
|
+
background: #ffffff;
|
|
1050
|
+
border-color: var(--el-color-primary, #ff5000);
|
|
1051
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
819
1052
|
}
|
|
820
1053
|
|
|
821
|
-
|
|
1054
|
+
|
|
1055
|
+
.native-search-input {
|
|
822
1056
|
flex: 1;
|
|
823
|
-
|
|
824
|
-
padding: 8px 12px;
|
|
825
|
-
border-radius: 6px;
|
|
826
|
-
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
827
|
-
font-size: 13px;
|
|
1057
|
+
border: none;
|
|
828
1058
|
outline: none;
|
|
829
|
-
|
|
830
|
-
|
|
1059
|
+
background: transparent;
|
|
1060
|
+
font-size: 13.5px;
|
|
1061
|
+
color: #1e293b;
|
|
1062
|
+
min-width: 0;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
.native-search-input::placeholder {
|
|
1066
|
+
color: #94a3b8;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.search-clear-icon {
|
|
1070
|
+
font-size: 16px;
|
|
1071
|
+
color: #94a3b8;
|
|
1072
|
+
cursor: pointer;
|
|
1073
|
+
margin-right: 8px;
|
|
1074
|
+
transition: color 0.2s;
|
|
1075
|
+
flex-shrink: 0;
|
|
831
1076
|
}
|
|
832
1077
|
|
|
833
|
-
.
|
|
834
|
-
|
|
1078
|
+
.search-clear-icon:hover {
|
|
1079
|
+
color: #64748b;
|
|
835
1080
|
}
|
|
836
1081
|
|
|
837
|
-
.
|
|
1082
|
+
.capsule-search-btn {
|
|
838
1083
|
display: inline-flex;
|
|
839
1084
|
align-items: center;
|
|
840
|
-
gap:
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
background: var(--theme-color-1, #3b82f6);
|
|
1085
|
+
gap: 5px;
|
|
1086
|
+
height: 34px;
|
|
1087
|
+
padding: 0 16px;
|
|
1088
|
+
background: #1e293b;
|
|
845
1089
|
color: #ffffff;
|
|
1090
|
+
border: none;
|
|
1091
|
+
border-radius: 17px;
|
|
846
1092
|
font-size: 13px;
|
|
847
1093
|
font-weight: 500;
|
|
848
1094
|
cursor: pointer;
|
|
849
|
-
|
|
850
|
-
transition: all 0.2s;
|
|
1095
|
+
transition: all 0.2s ease;
|
|
851
1096
|
flex-shrink: 0;
|
|
852
|
-
box-sizing: border-box;
|
|
853
1097
|
}
|
|
854
1098
|
|
|
855
|
-
.
|
|
856
|
-
|
|
857
|
-
|
|
1099
|
+
.capsule-search-btn:hover:not(:disabled) {
|
|
1100
|
+
background: #0f172a;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.capsule-search-btn:disabled {
|
|
1104
|
+
opacity: 0.6;
|
|
1105
|
+
cursor: not-allowed;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.btn-spinner-ring {
|
|
1109
|
+
display: inline-block;
|
|
1110
|
+
width: 12px;
|
|
1111
|
+
height: 12px;
|
|
1112
|
+
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
1113
|
+
border-top-color: #ffffff;
|
|
1114
|
+
border-radius: 50%;
|
|
1115
|
+
animation: spin 0.8s linear infinite;
|
|
858
1116
|
}
|
|
859
1117
|
|
|
860
|
-
/*
|
|
861
|
-
.
|
|
1118
|
+
/* 2. 工具栏行:分类 Tabs + 多维筛选(红框2 + 红框3) */
|
|
1119
|
+
.gallery-toolbar-row {
|
|
862
1120
|
display: flex;
|
|
863
1121
|
align-items: center;
|
|
864
1122
|
justify-content: space-between;
|
|
865
|
-
|
|
866
|
-
|
|
1123
|
+
gap: 10px;
|
|
1124
|
+
padding-bottom: 4px;
|
|
1125
|
+
border-bottom: 1px solid #f1f5f9;
|
|
1126
|
+
flex-shrink: 0;
|
|
867
1127
|
}
|
|
868
1128
|
|
|
869
|
-
|
|
870
|
-
|
|
1129
|
+
/* 红框2:分类类型胶囊组 */
|
|
1130
|
+
.category-pill-group {
|
|
1131
|
+
display: flex;
|
|
871
1132
|
align-items: center;
|
|
872
1133
|
gap: 4px;
|
|
873
|
-
background:
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
font-size: 12px;
|
|
878
|
-
font-weight: 500;
|
|
879
|
-
padding: 2px 4px;
|
|
880
|
-
border-radius: 4px;
|
|
881
|
-
transition: background-color 0.2s;
|
|
1134
|
+
background: #f1f5f9;
|
|
1135
|
+
padding: 3px;
|
|
1136
|
+
border-radius: 18px;
|
|
1137
|
+
flex-shrink: 0;
|
|
882
1138
|
}
|
|
883
1139
|
|
|
884
|
-
.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1140
|
+
.pill-tab-btn {
|
|
1141
|
+
border: none;
|
|
1142
|
+
outline: none;
|
|
1143
|
+
background: transparent;
|
|
1144
|
+
padding: 5px 12px;
|
|
1145
|
+
font-size: 13px;
|
|
889
1146
|
color: #64748b;
|
|
890
|
-
font-size: 12px;
|
|
891
1147
|
font-weight: 500;
|
|
1148
|
+
border-radius: 15px;
|
|
1149
|
+
cursor: pointer;
|
|
1150
|
+
transition: all 0.2s ease;
|
|
1151
|
+
white-space: nowrap;
|
|
892
1152
|
}
|
|
893
1153
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
flex: 1;
|
|
897
|
-
overflow-y: auto;
|
|
898
|
-
padding: 14px 10px 14px 14px;
|
|
899
|
-
display: flex;
|
|
900
|
-
flex-direction: column;
|
|
901
|
-
gap: 20px;
|
|
902
|
-
scrollbar-width: thin;
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
.gallery-tree-scroll::-webkit-scrollbar,
|
|
906
|
-
.gallery-grid-scroll::-webkit-scrollbar {
|
|
907
|
-
width: 6px;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
.gallery-tree-scroll::-webkit-scrollbar-track,
|
|
911
|
-
.gallery-grid-scroll::-webkit-scrollbar-track {
|
|
912
|
-
background: transparent;
|
|
913
|
-
margin-top: 4px;
|
|
914
|
-
margin-bottom: 8px;
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
.gallery-tree-scroll::-webkit-scrollbar-thumb,
|
|
918
|
-
.gallery-grid-scroll::-webkit-scrollbar-thumb {
|
|
919
|
-
background: rgba(0, 0, 0, 0.18);
|
|
920
|
-
border-radius: 4px;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
.gallery-tree-scroll::-webkit-scrollbar-thumb:hover,
|
|
924
|
-
.gallery-grid-scroll::-webkit-scrollbar-thumb:hover {
|
|
925
|
-
background: rgba(0, 0, 0, 0.32);
|
|
1154
|
+
.pill-tab-btn:hover {
|
|
1155
|
+
color: #0f172a;
|
|
926
1156
|
}
|
|
927
1157
|
|
|
928
|
-
.
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1158
|
+
.pill-tab-btn.active {
|
|
1159
|
+
background: #0f172a;
|
|
1160
|
+
color: #ffffff;
|
|
1161
|
+
font-weight: 600;
|
|
1162
|
+
box-shadow: 0 2px 6px rgba(15, 23, 42, 0.15);
|
|
932
1163
|
}
|
|
933
1164
|
|
|
934
|
-
/*
|
|
935
|
-
.
|
|
1165
|
+
/* 红框3:筛选下拉项 */
|
|
1166
|
+
.filter-dropdown-group {
|
|
936
1167
|
display: flex;
|
|
937
1168
|
align-items: center;
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
user-select: none;
|
|
941
|
-
padding-right: 4px;
|
|
1169
|
+
gap: 8px;
|
|
1170
|
+
flex-shrink: 0;
|
|
942
1171
|
}
|
|
943
1172
|
|
|
944
|
-
.
|
|
1173
|
+
.toolbar-dropdown-btn {
|
|
945
1174
|
display: inline-flex;
|
|
946
1175
|
align-items: center;
|
|
947
|
-
gap:
|
|
948
|
-
|
|
949
|
-
|
|
1176
|
+
gap: 4px;
|
|
1177
|
+
padding: 4px 8px;
|
|
1178
|
+
font-size: 12.5px;
|
|
1179
|
+
color: #475569;
|
|
1180
|
+
border-radius: 6px;
|
|
1181
|
+
cursor: pointer;
|
|
1182
|
+
transition: all 0.2s;
|
|
1183
|
+
user-select: none;
|
|
1184
|
+
white-space: nowrap;
|
|
950
1185
|
}
|
|
951
1186
|
|
|
952
|
-
.
|
|
953
|
-
|
|
954
|
-
|
|
1187
|
+
.toolbar-dropdown-btn:hover {
|
|
1188
|
+
background: #f8fafc;
|
|
1189
|
+
color: #0f172a;
|
|
955
1190
|
}
|
|
956
1191
|
|
|
957
|
-
.
|
|
958
|
-
|
|
1192
|
+
.toolbar-dropdown-btn.is-active {
|
|
1193
|
+
color: #ff5000;
|
|
959
1194
|
font-weight: 600;
|
|
960
|
-
|
|
1195
|
+
background: rgba(255, 80, 0, 0.08);
|
|
961
1196
|
}
|
|
962
1197
|
|
|
963
|
-
.
|
|
964
|
-
|
|
965
|
-
transition: transform 0.2s;
|
|
1198
|
+
.dropdown-arrow {
|
|
1199
|
+
font-size: 11px;
|
|
966
1200
|
}
|
|
967
1201
|
|
|
968
|
-
.
|
|
969
|
-
|
|
1202
|
+
.color-badge-dot {
|
|
1203
|
+
display: inline-block;
|
|
1204
|
+
width: 9px;
|
|
1205
|
+
height: 9px;
|
|
1206
|
+
border-radius: 50%;
|
|
1207
|
+
border: 1px solid rgba(0, 0, 0, 0.15);
|
|
970
1208
|
}
|
|
971
1209
|
|
|
972
|
-
.
|
|
1210
|
+
.reset-filters-link {
|
|
1211
|
+
border: none;
|
|
1212
|
+
background: none;
|
|
973
1213
|
font-size: 12px;
|
|
974
1214
|
color: #94a3b8;
|
|
1215
|
+
cursor: pointer;
|
|
1216
|
+
padding: 3px 6px;
|
|
1217
|
+
text-decoration: underline;
|
|
975
1218
|
transition: color 0.2s;
|
|
1219
|
+
white-space: nowrap;
|
|
976
1220
|
}
|
|
977
1221
|
|
|
978
|
-
.
|
|
979
|
-
color:
|
|
1222
|
+
.reset-filters-link:hover {
|
|
1223
|
+
color: #ef4444;
|
|
980
1224
|
}
|
|
981
1225
|
|
|
982
|
-
/*
|
|
983
|
-
.
|
|
984
|
-
|
|
985
|
-
width:
|
|
1226
|
+
/* 颜色选择面板 */
|
|
1227
|
+
.color-picker-palette {
|
|
1228
|
+
padding: 10px;
|
|
1229
|
+
width: 170px;
|
|
986
1230
|
}
|
|
987
1231
|
|
|
988
|
-
.
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
scroll-behavior: smooth;
|
|
994
|
-
scrollbar-width: none;
|
|
995
|
-
-ms-overflow-style: none;
|
|
1232
|
+
.palette-header-label {
|
|
1233
|
+
font-size: 12px;
|
|
1234
|
+
color: #64748b;
|
|
1235
|
+
margin-bottom: 8px;
|
|
1236
|
+
font-weight: 500;
|
|
996
1237
|
}
|
|
997
1238
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1239
|
+
.palette-swatch-grid {
|
|
1240
|
+
display: grid;
|
|
1241
|
+
grid-template-columns: repeat(5, 1fr);
|
|
1242
|
+
gap: 6px;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.swatch-item {
|
|
1246
|
+
width: 24px;
|
|
1247
|
+
height: 24px;
|
|
1248
|
+
border-radius: 6px;
|
|
1249
|
+
cursor: pointer;
|
|
1250
|
+
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
1251
|
+
transition: all 0.2s ease;
|
|
1005
1252
|
}
|
|
1006
1253
|
|
|
1007
|
-
.
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
height: 0;
|
|
1254
|
+
.swatch-item:hover {
|
|
1255
|
+
transform: scale(1.15);
|
|
1256
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
|
1011
1257
|
}
|
|
1012
1258
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
flex: 0 0 132px;
|
|
1016
|
-
width: 132px;
|
|
1017
|
-
max-width: 132px;
|
|
1018
|
-
min-width: 0;
|
|
1019
|
-
display: flex;
|
|
1020
|
-
flex-direction: column;
|
|
1021
|
-
gap: 6px;
|
|
1022
|
-
cursor: pointer;
|
|
1023
|
-
box-sizing: border-box;
|
|
1259
|
+
.swatch-item.active {
|
|
1260
|
+
box-shadow: 0 0 0 2px #ff5000;
|
|
1024
1261
|
}
|
|
1025
1262
|
|
|
1026
|
-
.
|
|
1027
|
-
|
|
1028
|
-
|
|
1263
|
+
.swatch-item.all-swatch {
|
|
1264
|
+
display: flex;
|
|
1265
|
+
align-items: center;
|
|
1266
|
+
justify-content: center;
|
|
1267
|
+
font-size: 10px;
|
|
1268
|
+
color: #64748b;
|
|
1029
1269
|
background: #f1f5f9;
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/* 加载中状态 */
|
|
1273
|
+
.gallery-loading-box {
|
|
1034
1274
|
display: flex;
|
|
1275
|
+
flex-direction: column;
|
|
1035
1276
|
align-items: center;
|
|
1036
1277
|
justify-content: center;
|
|
1037
|
-
|
|
1278
|
+
padding: 60px 0;
|
|
1279
|
+
gap: 12px;
|
|
1038
1280
|
}
|
|
1039
1281
|
|
|
1040
|
-
.
|
|
1041
|
-
width:
|
|
1042
|
-
height:
|
|
1043
|
-
|
|
1044
|
-
|
|
1282
|
+
.loading-ring-spinner {
|
|
1283
|
+
width: 28px;
|
|
1284
|
+
height: 28px;
|
|
1285
|
+
border: 3px solid #e2e8f0;
|
|
1286
|
+
border-top-color: #0f172a;
|
|
1287
|
+
border-radius: 50%;
|
|
1288
|
+
animation: spin 0.8s linear infinite;
|
|
1045
1289
|
}
|
|
1046
1290
|
|
|
1047
|
-
.
|
|
1048
|
-
|
|
1291
|
+
.loading-tip-text {
|
|
1292
|
+
font-size: 13px;
|
|
1293
|
+
color: #64748b;
|
|
1049
1294
|
}
|
|
1050
1295
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1296
|
+
/* 3. 主体内容区:纯瀑布流排版(红框4) */
|
|
1297
|
+
.waterfall-scroll-container {
|
|
1298
|
+
flex: 1;
|
|
1299
|
+
min-height: 0;
|
|
1300
|
+
overflow-y: auto;
|
|
1301
|
+
padding-right: 4px;
|
|
1054
1302
|
}
|
|
1055
1303
|
|
|
1056
|
-
.
|
|
1057
|
-
width:
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1304
|
+
.waterfall-scroll-container::-webkit-scrollbar {
|
|
1305
|
+
width: 5px;
|
|
1306
|
+
}
|
|
1307
|
+
.waterfall-scroll-container::-webkit-scrollbar-thumb {
|
|
1308
|
+
background: #cbd5e1;
|
|
1309
|
+
border-radius: 3px;
|
|
1310
|
+
}
|
|
1311
|
+
.waterfall-scroll-container::-webkit-scrollbar-thumb:hover {
|
|
1312
|
+
background: #94a3b8;
|
|
1064
1313
|
}
|
|
1065
1314
|
|
|
1066
|
-
.
|
|
1067
|
-
|
|
1315
|
+
.waterfall-masonry-layout {
|
|
1316
|
+
column-count: 4;
|
|
1317
|
+
column-gap: 12px;
|
|
1068
1318
|
}
|
|
1069
1319
|
|
|
1070
|
-
|
|
1071
|
-
.
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1320
|
+
@media (max-width: 820px) {
|
|
1321
|
+
.waterfall-masonry-layout {
|
|
1322
|
+
column-count: 3;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.waterfall-card-item {
|
|
1327
|
+
break-inside: avoid;
|
|
1328
|
+
margin-bottom: 12px;
|
|
1329
|
+
cursor: pointer;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.card-box {
|
|
1333
|
+
border-radius: 10px;
|
|
1076
1334
|
overflow: hidden;
|
|
1335
|
+
background: #ffffff;
|
|
1336
|
+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
|
|
1337
|
+
border: 1px solid #f1f5f9;
|
|
1338
|
+
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
1339
|
+
display: flex;
|
|
1340
|
+
flex-direction: column;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.card-box:hover {
|
|
1344
|
+
transform: translateY(-3px);
|
|
1345
|
+
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.08);
|
|
1346
|
+
border-color: #e2e8f0;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.card-media-viewport {
|
|
1077
1350
|
position: relative;
|
|
1078
|
-
|
|
1351
|
+
width: 100%;
|
|
1352
|
+
overflow: hidden;
|
|
1353
|
+
background: #f1f5f9;
|
|
1354
|
+
min-height: 90px;
|
|
1355
|
+
max-height: 260px;
|
|
1079
1356
|
display: flex;
|
|
1080
1357
|
align-items: center;
|
|
1081
1358
|
justify-content: center;
|
|
1082
|
-
transition: transform 0.25s, box-shadow 0.25s, border-color 0.25s;
|
|
1083
1359
|
}
|
|
1084
1360
|
|
|
1085
|
-
.
|
|
1086
|
-
padding: 8px;
|
|
1087
|
-
display: flex;
|
|
1088
|
-
flex-direction: column;
|
|
1089
|
-
gap: 6px;
|
|
1090
|
-
align-items: flex-start;
|
|
1361
|
+
.card-thumb-image {
|
|
1091
1362
|
width: 100%;
|
|
1092
|
-
height:
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
.
|
|
1097
|
-
font-size: 9px;
|
|
1098
|
-
font-weight: 600;
|
|
1099
|
-
background: #e2e8f0;
|
|
1100
|
-
color: #475569;
|
|
1101
|
-
padding: 1px 4px;
|
|
1102
|
-
border-radius: 3px;
|
|
1363
|
+
height: auto;
|
|
1364
|
+
max-height: 260px;
|
|
1365
|
+
display: block;
|
|
1366
|
+
object-fit: cover;
|
|
1367
|
+
transition: transform 0.4s ease;
|
|
1103
1368
|
}
|
|
1104
1369
|
|
|
1105
|
-
.
|
|
1106
|
-
|
|
1107
|
-
color: #334155;
|
|
1108
|
-
font-weight: 500;
|
|
1109
|
-
line-height: 1.3;
|
|
1370
|
+
.card-box:hover .card-thumb-image {
|
|
1371
|
+
transform: scale(1.04);
|
|
1110
1372
|
}
|
|
1111
1373
|
|
|
1112
|
-
.
|
|
1113
|
-
|
|
1114
|
-
|
|
1374
|
+
.card-empty-placeholder {
|
|
1375
|
+
padding: 30px 0;
|
|
1376
|
+
display: flex;
|
|
1377
|
+
align-items: center;
|
|
1378
|
+
justify-content: center;
|
|
1115
1379
|
}
|
|
1116
1380
|
|
|
1117
|
-
|
|
1381
|
+
/* 悬浮遮罩 */
|
|
1382
|
+
.card-hover-mask {
|
|
1118
1383
|
position: absolute;
|
|
1119
|
-
|
|
1120
|
-
|
|
1384
|
+
top: 0;
|
|
1385
|
+
left: 0;
|
|
1386
|
+
width: 100%;
|
|
1387
|
+
height: 100%;
|
|
1388
|
+
background: rgba(15, 23, 42, 0.55);
|
|
1389
|
+
backdrop-filter: blur(2px);
|
|
1121
1390
|
display: flex;
|
|
1391
|
+
flex-direction: column;
|
|
1122
1392
|
align-items: center;
|
|
1123
1393
|
justify-content: center;
|
|
1124
1394
|
opacity: 0;
|
|
1125
|
-
transition: opacity 0.2s;
|
|
1395
|
+
transition: opacity 0.2s ease;
|
|
1126
1396
|
}
|
|
1127
1397
|
|
|
1128
|
-
.
|
|
1398
|
+
.card-box:hover .card-hover-mask {
|
|
1129
1399
|
opacity: 1;
|
|
1130
1400
|
}
|
|
1131
1401
|
|
|
1132
|
-
.
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
border-radius:
|
|
1139
|
-
|
|
1402
|
+
.preview-float-btn {
|
|
1403
|
+
position: absolute;
|
|
1404
|
+
top: 8px;
|
|
1405
|
+
right: 8px;
|
|
1406
|
+
width: 26px;
|
|
1407
|
+
height: 26px;
|
|
1408
|
+
border-radius: 50%;
|
|
1409
|
+
background: rgba(255, 255, 255, 0.85);
|
|
1410
|
+
color: #1e293b;
|
|
1411
|
+
border: none;
|
|
1412
|
+
cursor: pointer;
|
|
1413
|
+
display: flex;
|
|
1414
|
+
align-items: center;
|
|
1415
|
+
justify-content: center;
|
|
1416
|
+
font-size: 13px;
|
|
1417
|
+
transition: all 0.2s;
|
|
1140
1418
|
}
|
|
1141
1419
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1420
|
+
.preview-float-btn:hover {
|
|
1421
|
+
background: #ffffff;
|
|
1422
|
+
transform: scale(1.1);
|
|
1145
1423
|
}
|
|
1146
1424
|
|
|
1147
|
-
.
|
|
1148
|
-
|
|
1149
|
-
|
|
1425
|
+
.insert-float-action {
|
|
1426
|
+
display: flex;
|
|
1427
|
+
flex-direction: column;
|
|
1428
|
+
align-items: center;
|
|
1429
|
+
gap: 6px;
|
|
1430
|
+
color: #ffffff;
|
|
1431
|
+
font-size: 12px;
|
|
1432
|
+
font-weight: 600;
|
|
1150
1433
|
}
|
|
1151
1434
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
width: 26px;
|
|
1158
|
-
height: 26px;
|
|
1159
|
-
border-radius: 4px;
|
|
1160
|
-
background: rgba(0, 0, 0, 0.55);
|
|
1435
|
+
.insert-circle-btn {
|
|
1436
|
+
width: 34px;
|
|
1437
|
+
height: 34px;
|
|
1438
|
+
border-radius: 50%;
|
|
1439
|
+
background: #ff5000;
|
|
1161
1440
|
color: #ffffff;
|
|
1162
|
-
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
1163
1441
|
display: flex;
|
|
1164
1442
|
align-items: center;
|
|
1165
1443
|
justify-content: center;
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
transition: all 0.2s ease;
|
|
1170
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
|
1444
|
+
font-size: 18px;
|
|
1445
|
+
box-shadow: 0 2px 8px rgba(255, 80, 0, 0.4);
|
|
1446
|
+
transition: transform 0.2s;
|
|
1171
1447
|
}
|
|
1172
1448
|
|
|
1173
|
-
.card-
|
|
1174
|
-
background: rgba(0, 0, 0, 0.85);
|
|
1175
|
-
color: #38bdf8;
|
|
1176
|
-
border-color: rgba(255, 255, 255, 0.45);
|
|
1449
|
+
.card-box:hover .insert-circle-btn {
|
|
1177
1450
|
transform: scale(1.08);
|
|
1178
1451
|
}
|
|
1179
1452
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
position: absolute;
|
|
1183
|
-
bottom: 6px;
|
|
1184
|
-
right: 6px;
|
|
1185
|
-
background: var(--theme-color-1, #2563eb);
|
|
1186
|
-
color: #ffffff;
|
|
1187
|
-
border: none;
|
|
1188
|
-
font-size: 11px;
|
|
1189
|
-
font-weight: 500;
|
|
1190
|
-
padding: 3px 9px;
|
|
1191
|
-
border-radius: 4px;
|
|
1192
|
-
cursor: pointer;
|
|
1193
|
-
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
|
1194
|
-
transition: all 0.2s ease;
|
|
1195
|
-
line-height: 1.35;
|
|
1453
|
+
.insert-tip {
|
|
1454
|
+
font-size: 12px;
|
|
1196
1455
|
}
|
|
1197
1456
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1457
|
+
/* 卡片底部栏(红框4版权信息条) */
|
|
1458
|
+
.card-bottom-info {
|
|
1459
|
+
display: flex;
|
|
1460
|
+
align-items: center;
|
|
1461
|
+
justify-content: space-between;
|
|
1462
|
+
padding: 8px 10px;
|
|
1463
|
+
background: #ffffff;
|
|
1464
|
+
border-top: 1px solid #f8fafc;
|
|
1201
1465
|
}
|
|
1202
1466
|
|
|
1203
|
-
.
|
|
1204
|
-
display:
|
|
1205
|
-
|
|
1206
|
-
|
|
1467
|
+
.format-badge-box {
|
|
1468
|
+
display: flex;
|
|
1469
|
+
align-items: center;
|
|
1470
|
+
gap: 6px;
|
|
1207
1471
|
min-width: 0;
|
|
1208
|
-
|
|
1472
|
+
overflow: hidden;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.format-tag {
|
|
1476
|
+
font-size: 10px;
|
|
1477
|
+
font-weight: 600;
|
|
1209
1478
|
color: #475569;
|
|
1479
|
+
background: #f1f5f9;
|
|
1480
|
+
padding: 1px 5px;
|
|
1481
|
+
border-radius: 3px;
|
|
1482
|
+
letter-spacing: 0.5px;
|
|
1483
|
+
flex-shrink: 0;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
.format-title-hint {
|
|
1487
|
+
font-size: 11px;
|
|
1488
|
+
color: #64748b;
|
|
1210
1489
|
white-space: nowrap;
|
|
1211
1490
|
overflow: hidden;
|
|
1212
1491
|
text-overflow: ellipsis;
|
|
1213
|
-
|
|
1214
|
-
box-sizing: border-box;
|
|
1492
|
+
max-width: 140px;
|
|
1215
1493
|
}
|
|
1216
1494
|
|
|
1217
|
-
.
|
|
1218
|
-
|
|
1219
|
-
|
|
1495
|
+
.more-options-dot-btn {
|
|
1496
|
+
border: none;
|
|
1497
|
+
background: transparent;
|
|
1220
1498
|
color: #94a3b8;
|
|
1499
|
+
cursor: pointer;
|
|
1500
|
+
padding: 2px 4px;
|
|
1501
|
+
border-radius: 4px;
|
|
1502
|
+
display: flex;
|
|
1503
|
+
align-items: center;
|
|
1504
|
+
transition: all 0.2s;
|
|
1221
1505
|
}
|
|
1222
1506
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
1232
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
|
|
1507
|
+
.more-options-dot-btn:hover {
|
|
1508
|
+
color: #334155;
|
|
1509
|
+
background: #f1f5f9;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/* 瀑布流底部加载更多区域 */
|
|
1513
|
+
.waterfall-footer-status {
|
|
1514
|
+
padding: 16px 0 24px;
|
|
1233
1515
|
display: flex;
|
|
1234
1516
|
align-items: center;
|
|
1235
1517
|
justify-content: center;
|
|
1236
|
-
|
|
1237
|
-
color: #334155;
|
|
1238
|
-
transition: transform 0.2s, background-color 0.2s, box-shadow 0.2s;
|
|
1239
|
-
z-index: 5;
|
|
1518
|
+
width: 100%;
|
|
1240
1519
|
}
|
|
1241
1520
|
|
|
1242
|
-
.
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1521
|
+
.loading-more-row {
|
|
1522
|
+
display: flex;
|
|
1523
|
+
align-items: center;
|
|
1524
|
+
gap: 8px;
|
|
1525
|
+
font-size: 13px;
|
|
1526
|
+
color: #64748b;
|
|
1247
1527
|
}
|
|
1248
1528
|
|
|
1249
|
-
.
|
|
1250
|
-
|
|
1529
|
+
.load-failed-row {
|
|
1530
|
+
display: flex;
|
|
1531
|
+
align-items: center;
|
|
1532
|
+
gap: 10px;
|
|
1533
|
+
background: #fef2f2;
|
|
1534
|
+
border: 1px solid #fecaca;
|
|
1535
|
+
padding: 6px 14px;
|
|
1536
|
+
border-radius: 20px;
|
|
1251
1537
|
}
|
|
1252
1538
|
|
|
1253
|
-
.
|
|
1254
|
-
|
|
1539
|
+
.load-error-text {
|
|
1540
|
+
font-size: 12px;
|
|
1541
|
+
color: #b91c1c;
|
|
1255
1542
|
}
|
|
1256
1543
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
padding:
|
|
1262
|
-
|
|
1544
|
+
.retry-load-btn {
|
|
1545
|
+
border: none;
|
|
1546
|
+
background: #dc2626;
|
|
1547
|
+
color: #ffffff;
|
|
1548
|
+
padding: 3px 10px;
|
|
1549
|
+
border-radius: 12px;
|
|
1550
|
+
font-size: 11.5px;
|
|
1551
|
+
cursor: pointer;
|
|
1552
|
+
font-weight: 500;
|
|
1553
|
+
transition: all 0.2s;
|
|
1263
1554
|
}
|
|
1264
1555
|
|
|
1265
|
-
.
|
|
1266
|
-
|
|
1267
|
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
1268
|
-
gap: 12px;
|
|
1556
|
+
.retry-load-btn:hover {
|
|
1557
|
+
background: #b91c1c;
|
|
1269
1558
|
}
|
|
1270
1559
|
|
|
1271
|
-
.
|
|
1560
|
+
.btn-spinner-ring.dark {
|
|
1561
|
+
border-color: rgba(15, 23, 42, 0.15);
|
|
1562
|
+
border-top-color: #0f172a;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.load-more-action-btn {
|
|
1566
|
+
display: inline-flex;
|
|
1567
|
+
align-items: center;
|
|
1568
|
+
gap: 6px;
|
|
1569
|
+
padding: 8px 24px;
|
|
1570
|
+
background: #f1f5f9;
|
|
1571
|
+
border: 1px solid #e2e8f0;
|
|
1572
|
+
border-radius: 20px;
|
|
1573
|
+
color: #334155;
|
|
1574
|
+
font-size: 12.5px;
|
|
1575
|
+
font-weight: 500;
|
|
1272
1576
|
cursor: pointer;
|
|
1577
|
+
transition: all 0.2s ease;
|
|
1273
1578
|
}
|
|
1274
1579
|
|
|
1275
|
-
.
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
border:
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
display: flex;
|
|
1282
|
-
flex-direction: column;
|
|
1283
|
-
transition: all 0.25s;
|
|
1580
|
+
.load-more-action-btn:hover {
|
|
1581
|
+
background: #0f172a;
|
|
1582
|
+
color: #ffffff;
|
|
1583
|
+
border-color: #0f172a;
|
|
1584
|
+
transform: translateY(-1px);
|
|
1585
|
+
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.12);
|
|
1284
1586
|
}
|
|
1285
1587
|
|
|
1286
|
-
.
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
border-color: var(--theme-color-1, #3b82f6);
|
|
1290
|
-
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
|
|
1588
|
+
.load-more-count {
|
|
1589
|
+
font-size: 11px;
|
|
1590
|
+
opacity: 0.8;
|
|
1291
1591
|
}
|
|
1292
1592
|
|
|
1293
|
-
.
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1593
|
+
.all-loaded-divider {
|
|
1594
|
+
font-size: 12px;
|
|
1595
|
+
color: #94a3b8;
|
|
1596
|
+
letter-spacing: 0.5px;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/* 空状态 */
|
|
1600
|
+
.gallery-empty-state {
|
|
1299
1601
|
display: flex;
|
|
1602
|
+
flex-direction: column;
|
|
1300
1603
|
align-items: center;
|
|
1301
1604
|
justify-content: center;
|
|
1302
|
-
|
|
1605
|
+
padding: 60px 16px;
|
|
1606
|
+
gap: 10px;
|
|
1607
|
+
text-align: center;
|
|
1303
1608
|
}
|
|
1304
1609
|
|
|
1305
|
-
.
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1610
|
+
.empty-title {
|
|
1611
|
+
font-size: 13.5px;
|
|
1612
|
+
color: #64748b;
|
|
1613
|
+
margin: 0;
|
|
1614
|
+
font-weight: 500;
|
|
1310
1615
|
}
|
|
1311
1616
|
|
|
1312
|
-
.
|
|
1313
|
-
|
|
1314
|
-
|
|
1617
|
+
.empty-subtitle {
|
|
1618
|
+
font-size: 12px;
|
|
1619
|
+
color: #94a3b8;
|
|
1315
1620
|
}
|
|
1316
1621
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
flex-direction: column;
|
|
1321
|
-
gap: 8px;
|
|
1322
|
-
width: 100%;
|
|
1323
|
-
height: 100%;
|
|
1324
|
-
box-sizing: border-box;
|
|
1622
|
+
@keyframes spin {
|
|
1623
|
+
from { transform: rotate(0deg); }
|
|
1624
|
+
to { transform: rotate(360deg); }
|
|
1325
1625
|
}
|
|
1326
1626
|
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
border-radius: 3px;
|
|
1334
|
-
align-self: flex-start;
|
|
1335
|
-
}
|
|
1627
|
+
/* ==========================================================================
|
|
1628
|
+
重点优化:当在右侧边栏(宽度 <= 520px / is-narrow-view)打开时的响应式完美适配
|
|
1629
|
+
========================================================================== */
|
|
1630
|
+
.template-gallery-container.is-narrow-view {
|
|
1631
|
+
padding: 10px 12px 14px;
|
|
1632
|
+
gap: 10px;
|
|
1336
1633
|
|
|
1337
|
-
.
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1634
|
+
/* 1. 搜索栏收敛紧凑 */
|
|
1635
|
+
.search-input-capsule {
|
|
1636
|
+
height: 38px;
|
|
1637
|
+
padding: 2px 4px 2px 10px;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
.native-search-input {
|
|
1641
|
+
font-size: 12.5px;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
.capsule-search-btn {
|
|
1645
|
+
height: 30px;
|
|
1646
|
+
padding: 0 10px;
|
|
1647
|
+
font-size: 12px;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/* 在狭窄侧边栏下,隐藏“搜索”文字,只保留放大镜图标,彻底防止截断输入框 */
|
|
1651
|
+
.capsule-search-btn .btn-text {
|
|
1652
|
+
display: none;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/* 2. 工具栏分为上下两行,彻底解决挤压溢出 */
|
|
1656
|
+
.gallery-toolbar-row {
|
|
1657
|
+
flex-direction: column;
|
|
1658
|
+
align-items: stretch;
|
|
1659
|
+
gap: 8px;
|
|
1660
|
+
padding-bottom: 8px;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
/* 第一行:分类 Tabs 单行横向平滑滚动 */
|
|
1664
|
+
.category-pill-group {
|
|
1665
|
+
width: 100%;
|
|
1666
|
+
overflow-x: auto;
|
|
1667
|
+
scrollbar-width: none;
|
|
1668
|
+
-ms-overflow-style: none;
|
|
1669
|
+
display: flex;
|
|
1670
|
+
flex-wrap: nowrap;
|
|
1671
|
+
padding: 2px 4px;
|
|
1672
|
+
box-sizing: border-box;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.category-pill-group::-webkit-scrollbar {
|
|
1676
|
+
display: none;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.pill-tab-btn {
|
|
1680
|
+
flex-shrink: 0;
|
|
1681
|
+
padding: 4px 10px;
|
|
1682
|
+
font-size: 12px;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/* 第二行:多维筛选下拉平铺 */
|
|
1686
|
+
.filter-dropdown-group {
|
|
1687
|
+
width: 100%;
|
|
1688
|
+
display: flex;
|
|
1689
|
+
align-items: center;
|
|
1690
|
+
justify-content: flex-start;
|
|
1691
|
+
gap: 6px;
|
|
1692
|
+
overflow-x: auto;
|
|
1693
|
+
scrollbar-width: none;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
.filter-dropdown-group::-webkit-scrollbar {
|
|
1697
|
+
display: none;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
.toolbar-dropdown-btn {
|
|
1701
|
+
padding: 3px 6px;
|
|
1702
|
+
font-size: 11.5px;
|
|
1703
|
+
flex-shrink: 0;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/* 3. 瀑布流自适应为精致的双列(2列)排版 */
|
|
1707
|
+
.waterfall-masonry-layout {
|
|
1708
|
+
column-count: 2 !important;
|
|
1709
|
+
column-gap: 8px !important;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
.waterfall-card-item {
|
|
1713
|
+
margin-bottom: 8px !important;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.card-media-viewport {
|
|
1717
|
+
min-height: 60px;
|
|
1718
|
+
max-height: 180px;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
.card-bottom-info {
|
|
1722
|
+
padding: 6px 8px;
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
.format-title-hint {
|
|
1726
|
+
max-width: 80px;
|
|
1727
|
+
font-size: 10px;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.insert-circle-btn {
|
|
1731
|
+
width: 28px;
|
|
1732
|
+
height: 28px;
|
|
1733
|
+
font-size: 15px;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.insert-tip {
|
|
1737
|
+
font-size: 10px;
|
|
1738
|
+
}
|
|
1342
1739
|
}
|
|
1740
|
+
</style>
|
|
1343
1741
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1742
|
+
<style>
|
|
1743
|
+
/* ==========================================================================
|
|
1744
|
+
全屏大图预览 Modal(脱离一切容器,z-index 最高层级)
|
|
1745
|
+
========================================================================== */
|
|
1746
|
+
.custom-fullscreen-preview-modal {
|
|
1747
|
+
position: fixed !important;
|
|
1748
|
+
inset: 0 !important;
|
|
1749
|
+
z-index: 999999 !important;
|
|
1750
|
+
background: rgba(10, 15, 29, 0.82) !important;
|
|
1751
|
+
backdrop-filter: blur(12px) !important;
|
|
1752
|
+
-webkit-backdrop-filter: blur(12px) !important;
|
|
1753
|
+
display: flex !important;
|
|
1754
|
+
flex-direction: column !important;
|
|
1755
|
+
align-items: center !important;
|
|
1756
|
+
justify-content: space-between !important;
|
|
1757
|
+
padding: 16px 24px !important;
|
|
1758
|
+
box-sizing: border-box !important;
|
|
1759
|
+
user-select: none !important;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
.preview-header-bar {
|
|
1763
|
+
width: 100%;
|
|
1348
1764
|
display: flex;
|
|
1349
1765
|
align-items: center;
|
|
1350
|
-
justify-content:
|
|
1351
|
-
|
|
1352
|
-
transition: opacity 0.2s;
|
|
1766
|
+
justify-content: space-between;
|
|
1767
|
+
z-index: 10;
|
|
1353
1768
|
}
|
|
1354
1769
|
|
|
1355
|
-
.
|
|
1356
|
-
|
|
1357
|
-
|
|
1770
|
+
.preview-title-wrap {
|
|
1771
|
+
display: flex;
|
|
1772
|
+
align-items: center;
|
|
1773
|
+
gap: 10px;
|
|
1358
1774
|
}
|
|
1359
1775
|
|
|
1360
|
-
.
|
|
1361
|
-
background:
|
|
1362
|
-
color: #
|
|
1363
|
-
|
|
1776
|
+
.preview-format-pill {
|
|
1777
|
+
background: #ff5000;
|
|
1778
|
+
color: #ffffff;
|
|
1779
|
+
font-size: 11px;
|
|
1780
|
+
font-weight: 700;
|
|
1781
|
+
padding: 2px 8px;
|
|
1364
1782
|
border-radius: 4px;
|
|
1365
|
-
|
|
1366
|
-
font-weight: 500;
|
|
1367
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
1783
|
+
letter-spacing: 0.5px;
|
|
1368
1784
|
}
|
|
1369
1785
|
|
|
1370
|
-
.
|
|
1371
|
-
|
|
1786
|
+
.preview-title-text {
|
|
1787
|
+
color: #ffffff;
|
|
1788
|
+
font-size: 15px;
|
|
1789
|
+
font-weight: 600;
|
|
1790
|
+
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
|
|
1791
|
+
max-width: 60vw;
|
|
1792
|
+
overflow: hidden;
|
|
1793
|
+
text-overflow: ellipsis;
|
|
1794
|
+
white-space: nowrap;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
.preview-close-cross-btn {
|
|
1798
|
+
border: none;
|
|
1799
|
+
background: rgba(255, 255, 255, 0.15);
|
|
1800
|
+
color: #ffffff;
|
|
1801
|
+
width: 36px;
|
|
1802
|
+
height: 36px;
|
|
1803
|
+
border-radius: 50%;
|
|
1372
1804
|
display: flex;
|
|
1373
1805
|
align-items: center;
|
|
1374
|
-
justify-content:
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
box-sizing: border-box;
|
|
1806
|
+
justify-content: center;
|
|
1807
|
+
font-size: 18px;
|
|
1808
|
+
cursor: pointer;
|
|
1809
|
+
transition: all 0.2s;
|
|
1379
1810
|
}
|
|
1380
1811
|
|
|
1381
|
-
.
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
text-overflow: ellipsis;
|
|
1812
|
+
.preview-close-cross-btn:hover {
|
|
1813
|
+
background: rgba(255, 255, 255, 0.3);
|
|
1814
|
+
transform: scale(1.1);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.preview-stage-viewport {
|
|
1388
1818
|
flex: 1;
|
|
1389
|
-
|
|
1819
|
+
width: 100%;
|
|
1820
|
+
display: flex;
|
|
1821
|
+
align-items: center;
|
|
1822
|
+
justify-content: center;
|
|
1823
|
+
overflow: hidden;
|
|
1824
|
+
padding: 10px;
|
|
1825
|
+
box-sizing: border-box;
|
|
1390
1826
|
}
|
|
1391
1827
|
|
|
1392
|
-
.
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1828
|
+
.preview-stage-img {
|
|
1829
|
+
max-width: 90vw;
|
|
1830
|
+
max-height: calc(85vh - 90px);
|
|
1831
|
+
object-fit: contain;
|
|
1832
|
+
border-radius: 8px;
|
|
1833
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
|
|
1834
|
+
transition: transform 0.25s ease;
|
|
1399
1835
|
}
|
|
1400
1836
|
|
|
1401
|
-
.
|
|
1837
|
+
.preview-arrow-btn {
|
|
1838
|
+
position: absolute;
|
|
1839
|
+
top: 50%;
|
|
1840
|
+
transform: translateY(-50%);
|
|
1841
|
+
width: 46px;
|
|
1842
|
+
height: 46px;
|
|
1843
|
+
border-radius: 50%;
|
|
1844
|
+
background: rgba(255, 255, 255, 0.18);
|
|
1845
|
+
color: #ffffff;
|
|
1846
|
+
border: none;
|
|
1402
1847
|
display: flex;
|
|
1403
|
-
flex-direction: column;
|
|
1404
1848
|
align-items: center;
|
|
1405
1849
|
justify-content: center;
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1850
|
+
font-size: 22px;
|
|
1851
|
+
cursor: pointer;
|
|
1852
|
+
transition: all 0.2s;
|
|
1853
|
+
z-index: 20;
|
|
1410
1854
|
}
|
|
1411
1855
|
|
|
1412
|
-
.
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
border: 3px solid rgba(59, 130, 246, 0.2);
|
|
1416
|
-
border-top-color: var(--theme-color-1, #3b82f6);
|
|
1417
|
-
border-radius: 50%;
|
|
1418
|
-
animation: spin 1s linear infinite;
|
|
1856
|
+
.preview-arrow-btn:hover {
|
|
1857
|
+
background: rgba(255, 255, 255, 0.38);
|
|
1858
|
+
transform: translateY(-50%) scale(1.1);
|
|
1419
1859
|
}
|
|
1420
1860
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1861
|
+
.prev-arrow {
|
|
1862
|
+
left: 24px;
|
|
1423
1863
|
}
|
|
1424
1864
|
|
|
1425
|
-
.
|
|
1426
|
-
|
|
1427
|
-
padding: 40px 20px;
|
|
1428
|
-
color: #94a3b8;
|
|
1429
|
-
font-size: 13px;
|
|
1865
|
+
.next-arrow {
|
|
1866
|
+
right: 24px;
|
|
1430
1867
|
}
|
|
1431
1868
|
|
|
1432
|
-
.
|
|
1433
|
-
margin-top: 16px;
|
|
1869
|
+
.preview-footer-bar {
|
|
1434
1870
|
display: flex;
|
|
1435
|
-
|
|
1436
|
-
|
|
1871
|
+
align-items: center;
|
|
1872
|
+
gap: 16px;
|
|
1873
|
+
z-index: 10;
|
|
1874
|
+
padding-bottom: 6px;
|
|
1437
1875
|
}
|
|
1438
1876
|
|
|
1439
|
-
.
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1877
|
+
.preview-page-indicator {
|
|
1878
|
+
color: rgba(255, 255, 255, 0.7);
|
|
1879
|
+
font-size: 13px;
|
|
1880
|
+
font-weight: 500;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
.preview-quick-insert-btn {
|
|
1884
|
+
display: inline-flex;
|
|
1885
|
+
align-items: center;
|
|
1886
|
+
gap: 6px;
|
|
1887
|
+
padding: 8px 20px;
|
|
1888
|
+
background: #ff5000;
|
|
1889
|
+
color: #ffffff;
|
|
1890
|
+
border: none;
|
|
1891
|
+
border-radius: 20px;
|
|
1892
|
+
font-size: 13px;
|
|
1893
|
+
font-weight: 600;
|
|
1447
1894
|
cursor: pointer;
|
|
1448
|
-
|
|
1895
|
+
box-shadow: 0 4px 14px rgba(255, 80, 0, 0.4);
|
|
1896
|
+
transition: all 0.2s;
|
|
1449
1897
|
}
|
|
1450
1898
|
|
|
1451
|
-
.
|
|
1452
|
-
background: #
|
|
1899
|
+
.preview-quick-insert-btn:hover {
|
|
1900
|
+
background: #ff6a00;
|
|
1901
|
+
transform: translateY(-2px);
|
|
1902
|
+
box-shadow: 0 6px 18px rgba(255, 80, 0, 0.5);
|
|
1453
1903
|
}
|
|
1454
1904
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1905
|
+
/* 过渡动效 */
|
|
1906
|
+
.preview-backdrop-fade-enter-active,
|
|
1907
|
+
.preview-backdrop-fade-leave-active {
|
|
1908
|
+
transition: opacity 0.25s ease;
|
|
1909
|
+
}
|
|
1910
|
+
.preview-backdrop-fade-enter-from,
|
|
1911
|
+
.preview-backdrop-fade-leave-to {
|
|
1912
|
+
opacity: 0;
|
|
1458
1913
|
}
|
|
1459
1914
|
</style>
|