diff --git a/hunliji-api/main.go b/hunliji-api/main.go index 576981c..9c342b1 100644 --- a/hunliji-api/main.go +++ b/hunliji-api/main.go @@ -283,6 +283,47 @@ func saveOSSUpload(c *gin.Context, fileHeader *multipart.FileHeader, config Uplo return true } +func collectAlbumImages(configData string) []string { + var raw map[string]interface{} + if err := json.Unmarshal([]byte(configData), &raw); err != nil { + return []string{} + } + + seen := map[string]bool{} + images := []string{} + add := func(value interface{}) { + src, ok := value.(string) + if !ok { + return + } + src = strings.TrimSpace(src) + if src == "" || seen[src] { + return + } + seen[src] = true + images = append(images, src) + } + + add(raw["heroImg"]) + add(raw["endImg"]) + if pages, ok := raw["photoPages"].([]interface{}); ok { + for _, item := range pages { + page, ok := item.(map[string]interface{}) + if !ok { + continue + } + add(page["img1"]) + add(page["img2"]) + if pageImages, ok := page["images"].([]interface{}); ok { + for _, src := range pageImages { + add(src) + } + } + } + } + return images +} + func main() { initDB() r := gin.Default() @@ -320,6 +361,15 @@ func main() { c.JSON(200, gin.H{"code": 200, "data": config.ConfigData}) }) + api.GET("/album", func(c *gin.Context) { + var config TemplateConfig + if err := db.First(&config, 1).Error; err != nil { + c.JSON(200, gin.H{"code": 200, "data": []string{}}) + return + } + c.JSON(200, gin.H{"code": 200, "data": collectAlbumImages(config.ConfigData)}) + }) + api.POST("/config", func(c *gin.Context) { if !requireAdmin(c) { return diff --git a/vite-tailwindcss/dist (2).zip b/vite-tailwindcss/dist (2).zip deleted file mode 100644 index 0fbb8fa..0000000 Binary files a/vite-tailwindcss/dist (2).zip and /dev/null differ diff --git a/vite-tailwindcss/dist.zip b/vite-tailwindcss/dist.zip index 40ef656..a2f562b 100644 Binary files a/vite-tailwindcss/dist.zip and b/vite-tailwindcss/dist.zip differ diff --git a/vite-tailwindcss/src/api/wedding.js b/vite-tailwindcss/src/api/wedding.js index e1351f3..89ba0cf 100644 --- a/vite-tailwindcss/src/api/wedding.js +++ b/vite-tailwindcss/src/api/wedding.js @@ -46,6 +46,10 @@ export function getConfig() { return service.get('/config') } +export function getAlbumImages() { + return service.get('/album') +} + export function saveConfig(payload) { return service.post('/config', payload) } diff --git a/vite-tailwindcss/src/components/CanvasEffects.vue b/vite-tailwindcss/src/components/CanvasEffects.vue index b60c3d6..76ab583 100644 --- a/vite-tailwindcss/src/components/CanvasEffects.vue +++ b/vite-tailwindcss/src/components/CanvasEffects.vue @@ -8,6 +8,8 @@ import { onMounted, onUnmounted, watch, ref } from 'vue' const props = defineProps({ petalEnabled: { type: Boolean, default: true }, bubbleEnabled: { type: Boolean, default: true }, + active: { type: Boolean, default: true }, + performanceMode: { type: Boolean, default: false }, }) const canvasRef = ref(null) @@ -22,6 +24,7 @@ let width = 0 let height = 0 let dpr = 1 let lastTime = 0 +let lastPaint = 0 let resizeObserver = null let reduceMotion = false @@ -52,14 +55,16 @@ function resetHeart(h, initial = false) { } function seedParticles() { + const petalCount = props.performanceMode ? 8 : 18 + const heartCount = props.performanceMode ? 5 : 12 petals.length = 0 hearts.length = 0 - for (let i = 0; i < 18; i += 1) { + for (let i = 0; i < petalCount; i += 1) { const p = {} resetPetal(p, true) petals.push(p) } - for (let i = 0; i < 12; i += 1) { + for (let i = 0; i < heartCount; i += 1) { const h = {} resetHeart(h, true) hearts.push(h) @@ -72,7 +77,7 @@ function resize() { const rect = canvas.getBoundingClientRect() width = Math.max(1, rect.width) height = Math.max(1, rect.height) - dpr = Math.min(window.devicePixelRatio || 1, 2) + dpr = props.performanceMode ? 1 : Math.min(window.devicePixelRatio || 1, 1.5) canvas.width = Math.round(width * dpr) canvas.height = Math.round(height * dpr) ctx = canvas.getContext('2d') @@ -119,11 +124,24 @@ function drawHeart(h, now) { function tick(now) { if (!ctx) return + if (!props.active || reduceMotion || (!props.petalEnabled && !props.bubbleEnabled)) { + ctx.clearRect(0, 0, width, height) + rafId = 0 + return + } + + const frameInterval = props.performanceMode ? 50 : 16.7 + if (lastPaint && now - lastPaint < frameInterval) { + rafId = requestAnimationFrame(tick) + return + } + const delta = Math.min(48, now - (lastTime || now)) / 1000 lastTime = now + lastPaint = now ctx.clearRect(0, 0, width, height) - if (!reduceMotion && props.petalEnabled) { + if (props.petalEnabled) { for (const p of petals) { p.y += p.speed * delta p.rotation += p.rotationSpeed * delta @@ -132,7 +150,7 @@ function tick(now) { } } - if (!reduceMotion && props.bubbleEnabled) { + if (props.bubbleEnabled) { for (const h of hearts) { h.y -= h.speed * delta if (h.y < -80) resetHeart(h) @@ -146,6 +164,12 @@ function tick(now) { function start() { cancelAnimationFrame(rafId) lastTime = 0 + lastPaint = 0 + if (!props.active || reduceMotion || (!props.petalEnabled && !props.bubbleEnabled)) { + ctx?.clearRect(0, 0, width, height) + rafId = 0 + return + } rafId = requestAnimationFrame(tick) } @@ -157,7 +181,13 @@ onMounted(() => { start() }) -watch(() => [props.petalEnabled, props.bubbleEnabled], start) +watch( + () => [props.petalEnabled, props.bubbleEnabled, props.active, props.performanceMode], + () => { + resize() + start() + } +) onUnmounted(() => { cancelAnimationFrame(rafId) @@ -173,5 +203,7 @@ onUnmounted(() => { width: 100%; height: 100vh; pointer-events: none; + contain: strict; + transform: translateZ(0); } diff --git a/vite-tailwindcss/src/components/PhotoGallery.vue b/vite-tailwindcss/src/components/PhotoGallery.vue index c18d7f8..f7b6c02 100644 --- a/vite-tailwindcss/src/components/PhotoGallery.vue +++ b/vite-tailwindcss/src/components/PhotoGallery.vue @@ -10,18 +10,18 @@
-
+
- +
- +
@@ -29,10 +29,10 @@
- +
- +
@@ -42,7 +42,7 @@
- +
@@ -50,7 +50,7 @@
- +
@@ -58,7 +58,7 @@
@@ -73,7 +73,7 @@
- +
@@ -89,6 +89,7 @@ import { borderClass } from '@/composables/borderStyles' const props = defineProps({ page: { type: Object, required: true }, }) +const emit = defineEmits(['preview-image', 'layout-change']) // 边框样式:前台按 page.borderStyle 套用全局 gf-* 类 const frameClass = computed(() => borderClass(props.page.borderStyle)) @@ -130,6 +131,14 @@ function onScroll() { if (i !== index.value) index.value = i } +function previewImage(src) { + if (src) emit('preview-image', src) +} + +function notifyLayoutChange() { + emit('layout-change') +} + /* ---------- 拍立得旋转/位移 ---------- */ const polaroidAngles = [-5, 4, -3, 6, -4, 3, -2, 5] const polaroidShift = [0, 10, -8, 6, -10, 8, 4, -6] @@ -163,13 +172,14 @@ function getRevealConfig(imgIdx) { // 左右列 return { variant: col === 0 ? 'left' : 'right', - delay: 80 + ((imgIdx +80) * 12) , + delay: 80 + ((imgIdx +80) * 20) , } }