UNPKG

unika-components

Version:

Unika Vue3 components library

1,232 lines (1,200 loc) 1.13 MB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue'), require('http'), require('https'), require('url'), require('stream'), require('assert'), require('tty'), require('util'), require('os'), require('zlib')) : typeof define === 'function' && define.amd ? define(['exports', 'vue', 'http', 'https', 'url', 'stream', 'assert', 'tty', 'util', 'os', 'zlib'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["unika-components"] = {}, global.vue, global.http, global.https, global.url, global.require$$0, global.assert, global.tty, global.util, global.os, global.zlib)); })(this, (function (exports, vue, http, https, url, require$$0, assert, tty, util, os, zlib) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var http__default = /*#__PURE__*/_interopDefaultLegacy(http); var https__default = /*#__PURE__*/_interopDefaultLegacy(https); var url__default = /*#__PURE__*/_interopDefaultLegacy(url); var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0); var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); var tty__default = /*#__PURE__*/_interopDefaultLegacy(tty); var util__default = /*#__PURE__*/_interopDefaultLegacy(util); var os__default = /*#__PURE__*/_interopDefaultLegacy(os); var zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib); if (typeof window !== 'undefined' && window.addEventListener) { window.addEventListener('resize', () => { }); } function px2rem(px) { // SSR 默认 37.5,客户端动态获取 if (typeof px === 'number' && px !== 0) { return px + 'px'; } if (px === 0) { return '0'; } return px; } var script$r = vue.defineComponent({ name: 'UniText', props: { element: { type: Object, required: true } }, emits: ['trigger'], setup(props, { emit }) { const element = props.element || {}; const properties = element.properties || {}; // 计算阴影样式 const boxShadowStyle = vue.computed(() => { const shadow = props.element && props.element.properties; if (!shadow || shadow.shadowSize <= 0) return 'none'; // Use px2rem for shadow values and remove props.unit (which does not exist) return `${px2rem(shadow.shadowX)} ${px2rem(shadow.shadowY)} ${px2rem(shadow.shadowBlur)} ${shadow.shadowColor}`; }); // 计算样式 - 使用 props.element.css 建立响应式依赖 const computedStyle = vue.computed(() => { const css = (props.element && props.element.css) || {}; return { position: 'absolute', left: px2rem(css.left || 0), top: px2rem(css.top || 0), width: px2rem(css.width || 100), height: 'auto', transform: `rotate(${css.transform || 0}deg)`, }; }); // 计算文本样式 - 使用 props.element.css 建立响应式依赖 const textCommonStyle = vue.computed(() => { const css = (props.element && props.element.css) || {}; const letterSpacingNum = Number(css.letterSpacing); const letterSpacingVal = !isNaN(letterSpacingNum) && letterSpacingNum !== 0 ? `${letterSpacingNum}px` : 'normal'; return { fontFamily: css.fontFamily || 'gorilla', fontSize: px2rem(css.fontSize || 16), lineHeight: css.lineHeight || 1, 'letter-spacing': letterSpacingVal, fontWeight: css.fontWeight || 'normal', fontStyle: css.fontStyle || 'normal', textDecoration: css.textDecoration || 'none', textAlign: css.textAlign || 'center', color: css.color || '#000', opacity: 1, padding: px2rem(css.padding || 0), textIndent: px2rem(css.textIndent || 0), display: 'none' }; }); const textEditorStyle = vue.computed(() => { const css = (props.element && props.element.css) || {}; return { ...textCommonStyle.value, writingMode: css.writingMode, display: 'block', // 确保文本编辑器可见 }; }); const aniWrapStyle = vue.computed(() => { // 仅当存在 type 为 'text' 的文字动画时,禁用通用 animation(由 applyTextAnimation 处理) // type 为 'normal' 的项走通用动画,需保留 css.animation const textAni = props.element && props.element.properties && props.element.properties.textAni; const hasTextTypeAni = textAni && Array.isArray(textAni) && textAni.some((item) => !item.type || item.type === 'text'); const elCss = (props.element && props.element.css) || {}; const opacity = elCss.opacity !== undefined && elCss.opacity !== null ? elCss.opacity : 1; return { backgroundColor: elCss.backgroundColor || 'transparent', borderRadius: `${px2rem(elCss.borderRadius || 0)}`, borderColor: elCss.borderColor, borderStyle: elCss.borderStyle, borderWidth: `${px2rem(elCss.borderWidth || 0)}`, boxShadow: boxShadowStyle.value, opacity, animation: hasTextTypeAni ? 'none' : (elCss.animation || '') }; }); const handleClick = () => { if (props.element.triggers.event) { emit('trigger', { elementId: props.element.id, eventType: 'click', data: { element: props.element.triggers } }); } }; return { properties, boxShadowStyle, computedStyle, textCommonStyle, textEditorStyle, aniWrapStyle, handleClick }; } }); const _hoisted_1$m = ["data-id", "data-pid", "data-type", "sign", "signsort", "layername"]; function render$n(_ctx, _cache, $props, $setup, $data, $options) { return (_ctx.element && (!_ctx.element.properties || _ctx.element.properties.visible !== false)) ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: vue.normalizeClass(['ele-text', 'eles']), "data-id": _ctx.element.id, "data-pid": _ctx.element.pid, "data-type": _ctx.element.type, style: vue.normalizeStyle(_ctx.computedStyle), sign: _ctx.element.sign, signsort: _ctx.element.signSort, layername: _ctx.element.layerName, onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))) }, [ vue.createElementVNode("div", { class: "ani-wrap", style: vue.normalizeStyle({ ..._ctx.aniWrapStyle, boxShadow: _ctx.boxShadowStyle }) }, [ vue.createElementVNode("div", { class: "text-common text-ani", style: vue.normalizeStyle(_ctx.textCommonStyle) }, null, 4 /* STYLE */), vue.createElementVNode("div", { contenteditable: "false", class: "text-common text-editor", style: vue.normalizeStyle(_ctx.textEditorStyle) }, vue.toDisplayString(_ctx.element.textContent), 5 /* TEXT, STYLE */) ], 4 /* STYLE */) ], 12 /* STYLE, PROPS */, _hoisted_1$m)) : vue.createCommentVNode("v-if", true) } script$r.render = render$n; script$r.__file = "src/components/UniText/UniText.vue"; script$r.install = (app) => { app.component(script$r.name, script$r); }; var script$q = vue.defineComponent({ name: 'uni-image', props: { element: { type: Object, required: true }, }, emits: ['trigger'], setup(props, { emit }) { // 计算属性 const properties = vue.computed(() => props.element.properties || {}); const css = vue.computed(() => props.element.css || {}); // 容器样式 const containerStyle = vue.computed(() => { const flipH = css.value.flipHorizontal ? -1 : 1; const flipV = css.value.flipVertical ? -1 : 1; const rotate = `rotate(${css.value.transform}deg)`; const scale = `scaleX(${flipH}) scaleY(${flipV})`; return { left: px2rem(css.value.left), top: px2rem(css.value.top), width: px2rem(css.value.width), height: px2rem(css.value.height), transform: `${rotate} ${scale}` }; }); // 图片样式 const imageStyle = vue.computed(() => ({ fontFamily: css.value.fontFamily, fontSize: px2rem(css.value.fontSize || 16), color: css.value.color, opacity: css.value.opacity, borderRadius: px2rem(css.value.borderRadius || 0), borderWidth: px2rem(css.value.borderWidth || 0), borderStyle: css.value.borderStyle, borderColor: css.value.borderColor, boxShadow: properties.value.shadowSize ? `${px2rem(properties.value.shadowX)} ${px2rem(properties.value.shadowY)} ${px2rem(properties.value.shadowBlur)} ${properties.value.shadowColor}` : 'none' })); // 背景图样式 const bgImageStyle = vue.computed(() => ({ backgroundImage: `url(${properties.value.src})`, backgroundSize: 'cover', backgroundPosition: '50% 50%' })); // img标签样式(用于非背景图模式) const imgTagStyle = vue.computed(() => { const aspectRatio = properties.value.realW || 0 / (properties.value.realH || 1); const containerWidth = css.value.width; const containerHeight = css.value.height; // 保持图片原始比例 if (containerWidth / containerHeight > aspectRatio) { return { height: '100%', width: 'auto', position: 'absolute', left: '50%', transform: 'translateX(-50%)' }; } else { return { width: '100%', height: 'auto', position: 'absolute', top: '50%', transform: 'translateY(-50%)' }; } }); // 遮罩样式 const maskStyle = vue.computed(() => ({ '-webkit-mask-box-image-source': properties.value.maskBoxImageSource ? `${properties.value.maskBoxImageSource}` : 'initial', 'mask-box-image-source': properties.value.maskBoxImageSource ? `${properties.value.maskBoxImageSource}` : 'initial', '-webkit-mask-box-image-slice': '0 fill', 'mask-box-image-slice': '0 fill', })); // 计算阴影样式 const shadowStyle = vue.computed(() => { const shadow = props.element.properties; if (shadow.shadowSize <= 0) return 'none'; // Use px2rem for shadow values and remove props.unit (which does not exist) return `${px2rem(shadow.shadowX)} ${px2rem(shadow.shadowY)} ${px2rem(shadow.shadowBlur)} ${shadow.shadowColor}`; }); // 动画样式 const aniWrapStyle = vue.computed(() => ({ backgroundColor: props.element.css.backgroundColor, borderRadius: px2rem(props.element.css.borderRadius || 0), borderWidth: px2rem(props.element.css.borderWidth || 0), borderStyle: props.element.css.borderStyle, borderColor: props.element.css.borderColor, opacity: props.element.css.opacity, boxShadow: shadowStyle, animation: props.element.css.animation, })); const handleClick = () => { if (props.element.triggers.event) { emit('trigger', { elementId: props.element.id, eventType: 'click', data: { element: props.element.triggers } }); } }; return { properties, containerStyle, imageStyle, bgImageStyle, imgTagStyle, maskStyle, aniWrapStyle, shadowStyle, handleClick }; } }); const _hoisted_1$l = ["data-id", "data-pid", "data-type", "data-show", "pid", "type", "layername", "sign", "signsort", "imgmattinginfo"]; const _hoisted_2$h = ["src"]; function render$m(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", { class: "ele-img eles", "data-id": _ctx.element.id, "data-pid": _ctx.element.pid, "data-type": _ctx.element.type, "data-show": _ctx.properties.visible, pid: _ctx.element.pid, type: _ctx.element.type, layername: _ctx.element.layerName, sign: _ctx.element.sign, signsort: _ctx.element.signSort, imgmattinginfo: JSON.stringify(_ctx.element.imgMattingInfo), style: vue.normalizeStyle(_ctx.containerStyle), onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))) }, [ vue.createElementVNode("div", { class: "ani-wrap", style: vue.normalizeStyle({ ..._ctx.aniWrapStyle, boxShadow: _ctx.shadowStyle }) }, [ vue.createElementVNode("div", { class: "rotate-wrap", style: vue.normalizeStyle(_ctx.maskStyle) }, [ vue.createCommentVNode(" 图片展示方式1:使用img标签 "), (_ctx.properties.maskBoxImageSource) ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: "img-wrap ele-image", style: vue.normalizeStyle(_ctx.imageStyle) }, [ vue.createElementVNode("img", { src: _ctx.properties.src, alt: "image", class: "ele-image", style: vue.normalizeStyle(_ctx.imgTagStyle) }, null, 12 /* STYLE, PROPS */, _hoisted_2$h) ], 4 /* STYLE */)) : (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [ vue.createCommentVNode(" 图片展示方式2:使用背景图 "), vue.createElementVNode("div", { class: "ele-img-bg", style: vue.normalizeStyle(_ctx.imageStyle) }, [ vue.createElementVNode("div", { class: "ele-bg-wrap", style: vue.normalizeStyle(_ctx.bgImageStyle) }, null, 4 /* STYLE */) ], 4 /* STYLE */) ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) ], 4 /* STYLE */) ], 4 /* STYLE */) ], 12 /* STYLE, PROPS */, _hoisted_1$l)) } script$q.render = render$m; script$q.__file = "src/components/UniImage/UniImage.vue"; script$q.install = (app) => { app.component(script$q.name, script$q); }; var script$p = vue.defineComponent({ name: 'uni-shape', props: { element: { type: Object, required: true, validator: (val) => val.type === 'shape' }, preserveAspectRatio: { type: String, default: 'none meet' } }, emits: ['trigger'], setup(props, { emit }) { const svgContent = vue.ref(null); const isLoading = vue.ref(false); const loadError = vue.ref(false); const properties = vue.computed(() => props.element.properties || {}); const css = vue.computed(() => props.element.css || {}); // 处理SVG内容,应用颜色 const processedSvg = vue.computed(() => { if (!svgContent.value) return null; try { let svg = svgContent.value; const colors = css.value.svgPathColor || []; // 如果提供了颜色数组,则替换SVG中的路径颜色 if (colors.length > 0) { svg = svg.replace(/<path([^>]*)fill="[^"]*"([^>]*)>/g, `<path$1fill="${colors[0]}"$2>`); svg = svg.replace(/<path([^>]*)>/g, (match, p1) => { if (!p1.includes('fill=')) { return `<path${p1} fill="${colors[0]}">`; } return match; }); } // 设置 preserveAspectRatio 和宽高为100% svg = svg.replace(/<svg([^>]*)>/, (match, p1) => { let newP1 = p1; // preserveAspectRatio if (/preserveAspectRatio=/.test(newP1)) { newP1 = newP1.replace(/preserveAspectRatio="[^"]*"/, `preserveAspectRatio=\"${props.preserveAspectRatio}\"`); } else { newP1 += ` preserveAspectRatio=\"${props.preserveAspectRatio}\"`; } // width if (/width=/.test(newP1)) { newP1 = newP1.replace(/width="[^"]*"/, 'width="100%"'); } else { newP1 += ' width="100%"'; } // height if (/height=/.test(newP1)) { newP1 = newP1.replace(/height="[^"]*"/, 'height="100%"'); } else { newP1 += ' height="100%"'; } return `<svg${newP1}>`; }); return svg; } catch (err) { console.error('SVG processing failed:', err); return svgContent.value; } }); // 加载远程SVG const loadSvg = async (url) => { try { isLoading.value = true; loadError.value = false; const response = await fetch(url); if (!response.ok) throw new Error('Network response was not ok'); const text = await response.text(); if (!text.includes('<svg')) throw new Error('Invalid SVG content'); svgContent.value = text; } catch (err) { console.error('SVG load failed:', err); svgContent.value = null; loadError.value = true; } finally { isLoading.value = false; } }; // 监听src变化 vue.watch(() => properties.value.src, (newVal) => { if (newVal) loadSvg(newVal); }, { immediate: true }); // 样式计算 const containerStyle = vue.computed(() => ({ position: (props.element && props.element.css && props.element.css.position) || 'absolute', left: px2rem(css.value.left), top: px2rem(css.value.top), width: px2rem(css.value.width), height: px2rem(css.value.height), opacity: css.value.opacity, transform: `rotate(${css.value.transform || 0}deg)` })); const wrapStyle = vue.computed(() => ({ borderRadius: px2rem(css.value.borderRadius || 0), animation: css.value.animation || 'none', border: `${px2rem(css.value.borderWidth || 0)} ${css.value.borderStyle || 'solid'} ${css.value.borderColor || 'transparent'}`, boxShadow: properties.value.shadowSize ? `${px2rem(properties.value.shadowX || 0)} ${px2rem(properties.value.shadowY || 0)} ${px2rem(properties.value.shadowBlur || 0)} ${properties.value.shadowColor || '#999999'}` : 'none' })); const shapeStyle = vue.computed(() => ({ color: css.value.color || 'inherit' })); const shadowStyle = vue.computed(() => { const shadow = properties.value; if (shadow.shadowSize <= 0) return 'none'; return `${px2rem(shadow.shadowX)} ${px2rem(shadow.shadowY)} ${px2rem(shadow.shadowBlur)} ${shadow.shadowColor}`; }); const handleClick = () => { if (props.element.triggers.event) { emit('trigger', { elementId: props.element.id, eventType: 'click', data: { element: props.element.triggers } }); } }; return { properties, css, svgContent, processedSvg, isLoading, loadError, containerStyle, wrapStyle, shapeStyle, shadowStyle, handleClick }; } }); const _hoisted_1$k = ["data-id"]; const _hoisted_2$g = ["innerHTML"]; const _hoisted_3$f = /*#__PURE__*/vue.createElementVNode("div", { class: "svg-loading" }, "Loading...", -1 /* HOISTED */); const _hoisted_4$c = { class: "svg-error" }; function render$l(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", { class: "ele-shape eles", "data-id": _ctx.element.id, style: vue.normalizeStyle(_ctx.containerStyle), onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))) }, [ vue.createElementVNode("div", { class: "ani-wrap", style: vue.normalizeStyle({ ..._ctx.wrapStyle, boxShadow: _ctx.shadowStyle }) }, [ vue.createElementVNode("div", { class: "e-shape", style: vue.normalizeStyle(_ctx.shapeStyle) }, [ vue.createCommentVNode(" 外部SVG内容渲染 "), (_ctx.svgContent) ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, innerHTML: _ctx.processedSvg, class: "svg-container", style: {"width":"100%","height":"100%"} }, null, 8 /* PROPS */, _hoisted_2$g)) : (_ctx.isLoading) ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [ vue.createCommentVNode(" 加载状态 "), _hoisted_3$f ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) : (_ctx.loadError) ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 2 }, [ vue.createCommentVNode(" 错误状态 "), vue.createElementVNode("div", _hoisted_4$c, [ vue.renderSlot(_ctx.$slots, "error", { src: _ctx.properties.src }, () => [ vue.createCommentVNode(" Failed to load SVG ") ]) ]) ], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */)) : vue.createCommentVNode("v-if", true) ], 4 /* STYLE */) ], 4 /* STYLE */) ], 12 /* STYLE, PROPS */, _hoisted_1$k)) } script$p.render = render$l; script$p.__file = "src/components/UniShape/UniShape.vue"; script$p.install = (app) => { app.component(script$p.name, script$p); }; const musicState = vue.ref({ isPlaying: false, currentMusicId: null, audioElement: null, isGlobalMusic: false, attemptedAutoplay: false, volume: 0.7, pendingPlay: false, canPlay: false }); const useMusicManager = () => { // 播放音乐 const playMusic = async (musicData, isUserAction = false) => { // 如果已经在播放同一首音乐,则暂停 if (musicState.value.isPlaying && (musicState.value.currentMusicId === musicData.music_id || musicState.value.currentMusicId === musicData.id)) { pauseCurrentMusic(); return true; } // 如果正在播放其他音乐,先停止 if (musicState.value.audioElement) { pauseCurrentMusic(); } // 创建新的音频实例 const audio = new Audio(musicData.url); audio.volume = musicState.value.volume; audio.loop = musicData.loop || false; musicState.value.audioElement = audio; musicState.value.isGlobalMusic = musicData.isGlobal || false; // 添加canplaythrough事件监听器 const canPlayPromise = new Promise((resolve) => { const canPlayHandler = () => { audio.removeEventListener('canplaythrough', canPlayHandler); resolve(); }; audio.addEventListener('canplaythrough', canPlayHandler); }); try { // 等待音频可以播放 await canPlayPromise; musicState.value.canPlay = true; // 尝试播放 const playPromise = audio.play(); if (playPromise !== undefined) { await playPromise .then(() => { // 播放成功 musicState.value.isPlaying = true; musicState.value.currentMusicId = musicData.music_id || musicData.id || null; musicState.value.pendingPlay = false; return true; }) .catch(error => { // 自动播放被阻止 if (!isUserAction) { musicState.value.pendingPlay = true; musicState.value.currentMusicId = musicData.music_id || musicData.id || null; } console.error('Autoplay prevented:', error); return false; }); } // 监听播放结束 audio.addEventListener('ended', () => { if (!audio.loop) { resetMusicState(); } }); return true; } catch (error) { console.error('播放失败:', error); if (!isUserAction) { musicState.value.pendingPlay = true; musicState.value.currentMusicId = musicData.music_id || musicData.id || null; } return false; } }; // 尝试播放待处理的音乐 const tryPlayPendingMusic = async () => { if (musicState.value.pendingPlay && musicState.value.audioElement) { try { await musicState.value.audioElement.play(); musicState.value.isPlaying = true; musicState.value.pendingPlay = false; return true; } catch (error) { console.error('Pending play failed:', error); return false; } } return false; }; // 暂停当前音乐 const pauseCurrentMusic = () => { if (musicState.value.audioElement) { musicState.value.audioElement.pause(); resetMusicState(); } }; // 暂停非全局音乐 const pauseNonGlobalMusic = () => { if (musicState.value.audioElement && !musicState.value.isGlobalMusic) { pauseCurrentMusic(); } }; // 切换播放状态 const toggleMusic = (musicData) => { if (musicState.value.isPlaying && (musicState.value.currentMusicId === musicData.music_id || musicState.value.currentMusicId === musicData.id)) { pauseCurrentMusic(); return Promise.resolve(false); } else { return playMusic(musicData, true); } }; // 设置音量 const setVolume = (volume) => { musicState.value.volume = Math.min(1, Math.max(0, volume)); if (musicState.value.audioElement) { musicState.value.audioElement.volume = musicState.value.volume; } }; // 重置音乐状态 const resetMusicState = () => { musicState.value.isPlaying = false; musicState.value.currentMusicId = null; musicState.value.isGlobalMusic = false; musicState.value.pendingPlay = false; musicState.value.canPlay = false; }; // 获取当前音乐状态 const getCurrentMusicState = () => { return { isPlaying: musicState.value.isPlaying, isGlobal: musicState.value.isGlobalMusic, musicId: musicState.value.currentMusicId, pendingPlay: musicState.value.pendingPlay, volume: musicState.value.volume, canPlay: musicState.value.canPlay }; }; // 尝试自动播放 const attemptAutoplay = async (musicData) => { if (musicState.value.attemptedAutoplay) return; const audio = new Audio(musicData.url); audio.volume = musicState.value.volume; audio.loop = musicData.loop || false; try { // 先尝试不静音播放 await audio.play(); musicState.value.audioElement = audio; musicState.value.isPlaying = true; musicState.value.currentMusicId = musicData.music_id || musicData.id || null; musicState.value.isGlobalMusic = musicData.isGlobal || false; musicState.value.canPlay = true; audio.addEventListener('ended', () => { if (!audio.loop) resetMusicState(); }); } catch (err) { // 如果失败,尝试静音播放然后解除静音 // try { // audio.muted = true // await audio.play() // // 播放成功后再解除静音 // setTimeout(() => { // audio.muted = false // }, 1000) // musicState.value.audioElement = audio // musicState.value.isPlaying = true // musicState.value.currentMusicId = musicData.music_id || musicData.id || null // musicState.value.isGlobalMusic = musicData.isGlobal || false // musicState.value.canPlay = true // } catch (mutedErr) { // console.log('Muted autoplay also failed:', mutedErr) // // 保留audio实例等待交互 // musicState.value.audioElement = audio // musicState.value.currentMusicId = musicData.music_id || musicData.id || null // musicState.value.isGlobalMusic = musicData.isGlobal || false // } } musicState.value.attemptedAutoplay = true; }; // 用户交互后正式播放 const playOnInteraction = async () => { if (!musicState.value.audioElement) return false; try { // 确保音频可以播放 if (!musicState.value.canPlay) { await new Promise((resolve) => { const audio = musicState.value.audioElement; if (!audio) { resolve(); return; } const canPlayHandler = () => { audio.removeEventListener('canplaythrough', canPlayHandler); musicState.value.canPlay = true; resolve(); }; audio.addEventListener('canplaythrough', canPlayHandler); }); } // 检查是否静音 if (musicState.value.audioElement.muted) { musicState.value.audioElement.muted = false; } await musicState.value.audioElement.play(); musicState.value.isPlaying = true; musicState.value.pendingPlay = false; return true; } catch (err) { console.log('Interaction play failed:', err); return false; } }; return { musicState, playMusic, pauseCurrentMusic, pauseNonGlobalMusic, toggleMusic, resetMusicState, getCurrentMusicState, tryPlayPendingMusic, setVolume, playOnInteraction, attemptAutoplay }; }; var script$o = vue.defineComponent({ name: 'uni-music', props: { musicData: { type: Object, required: true }, showBorder: { type: Boolean, default: true }, autoPlay: { type: Boolean, default: true }, }, emits: ['play', 'pause'], setup(props, { emit }) { const { musicState, playMusic, pauseCurrentMusic, toggleMusic } = useMusicManager(); const localIsPlaying = vue.computed(() => { return musicState.value.isPlaying && (musicState.value.currentMusicId === props.musicData.music_id || musicState.value.currentMusicId === props.musicData.id); }); const play = async () => { const success = await playMusic({ url: props.musicData.url, music_id: props.musicData.music_id, id: props.musicData.id, isGlobal: props.musicData.isGlobal }); if (success) { emit('play'); } }; const pause = () => { if (localIsPlaying.value) { pauseCurrentMusic(); emit('pause'); } }; const togglePlay = () => { toggleMusic({ url: props.musicData.url, music_id: props.musicData.music_id, id: props.musicData.id, isGlobal: props.musicData.isGlobal }); }; vue.onMounted(() => { if (props.autoPlay) { setTimeout(() => play(), 300); } }); vue.onBeforeUnmount(() => { pause(); }); const bgColor = vue.computed(() => props.musicData.bgcolor || 'rgba(128, 90, 218, 0.8)'); const playIconUrl = vue.computed(() => props.musicData.playIconUrl || props.musicData.iconUrl); return { localIsPlaying, bgColor, playIconUrl, play, pause, togglePlay }; } }); const _hoisted_1$j = { id: "audio" }; const _hoisted_2$f = ["src"]; const _hoisted_3$e = { key: 1, class: "iconfont" }; const _hoisted_4$b = ["src"]; const _hoisted_5$a = /*#__PURE__*/vue.createElementVNode("div", { class: "icon-cancel" }, [ /*#__PURE__*/vue.createElementVNode("div", { class: "icon-h" }) ], -1 /* HOISTED */); function render$k(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$j, [ vue.createElementVNode("div", { class: vue.normalizeClass(["audio", { 'a-border': _ctx.showBorder, 'mrotate': _ctx.localIsPlaying }]), style: vue.normalizeStyle({ background: _ctx.bgColor }), onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.togglePlay && _ctx.togglePlay(...args))) }, [ (_ctx.localIsPlaying) ? (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 0 }, [ (_ctx.playIconUrl) ? (vue.openBlock(), vue.createElementBlock("img", { key: 0, src: _ctx.playIconUrl, class: "music-icon", alt: "หยุดชั่วคราว" }, null, 8 /* PROPS */, _hoisted_2$f)) : (vue.openBlock(), vue.createElementBlock("span", _hoisted_3$e, "❚❚")) ], 64 /* STABLE_FRAGMENT */)) : (vue.openBlock(), vue.createElementBlock(vue.Fragment, { key: 1 }, [ (_ctx.playIconUrl) ? (vue.openBlock(), vue.createElementBlock("img", { key: 0, src: _ctx.playIconUrl, class: "music-icon", alt: "กำลังเล่น" }, null, 8 /* PROPS */, _hoisted_4$b)) : vue.createCommentVNode("v-if", true), _hoisted_5$a ], 64 /* STABLE_FRAGMENT */)) ], 6 /* CLASS, STYLE */) ])) } script$o.render = render$k; script$o.__file = "src/components/UniMusic/UniMusic.vue"; script$o.install = (app) => { app.component(script$o.name, script$o); }; var script$n = vue.defineComponent({ name: 'uni-video', props: { element: { type: Object, required: true }, }, setup(props) { const isPlaying = vue.ref(false); const containerStyle = vue.computed(() => ({ position: (props.element && props.element.css && props.element.css.position) || 'absolute', left: px2rem(props.element.css.left), top: px2rem(props.element.css.top), width: px2rem(props.element.css.width), height: px2rem(props.element.css.height), transform: `rotate(${props.element.css.transform}deg)`, opacity: props.element.css.opacity, })); // Extract YouTube video ID from embed code const youtubeVideoId = vue.computed(() => { const embedCode = props.element.videoCode || ''; const match = embedCode.match(/youtu\.be\/([^?&]+)/); return match ? match[1] : ''; }); const youtubeEmbedUrl = vue.computed(() => { if (!youtubeVideoId.value) return ''; let url = `https://www.youtube.com/embed/${youtubeVideoId.value}?`; url += props.element.playLoop ? '&loop=1' : ''; url += props.element.playControls ? '&controls=1' : '&controls=0'; url += '&autoplay=1'; url += '&rel=0'; return url; }); const css = vue.computed(() => props.element.css); const coverImage = vue.computed(() => { return props.element.properties.cover; }); const playVideo = () => { isPlaying.value = true; }; // 计算阴影样式 const shadowStyle = vue.computed(() => { const shadow = props.element.properties; if (shadow.shadowSize <= 0) return 'none'; // Use px2rem for shadow values and remove props.unit (which does not exist) return `${px2rem(shadow.shadowX)} ${px2rem(shadow.shadowY)} ${px2rem(shadow.shadowBlur)} ${shadow.shadowColor}`; }); const aniWrapStyle = vue.computed(() => ({ backgroundColor: props.element.css.backgroundColor, borderRadius: px2rem(props.element.css.borderRadius || 0), borderWidth: px2rem(props.element.css.borderWidth || 0), borderStyle: props.element.css.borderStyle, borderColor: props.element.css.borderColor, opacity: props.element.css.opacity, boxShadow: shadowStyle, animation: props.element.css.animation, })); return { isPlaying, youtubeEmbedUrl, css, coverImage, containerStyle, aniWrapStyle, shadowStyle, playVideo }; } }); const _hoisted_1$i = { key: 0, class: "video-container" }; const _hoisted_2$e = ["src"]; const _hoisted_3$d = ["src"]; function render$j(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", { class: "element-video eles", style: vue.normalizeStyle(_ctx.containerStyle) }, [ vue.createElementVNode("div", { class: "ani-wrap", style: vue.normalizeStyle({ ..._ctx.aniWrapStyle, boxShadow: _ctx.shadowStyle }) }, [ (_ctx.isPlaying) ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$i, [ vue.createElementVNode("iframe", { src: _ctx.youtubeEmbedUrl, type: "text/html", frameborder: "0", allowfullscreen: "", scrolling: "no", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" }, null, 8 /* PROPS */, _hoisted_2$e) ])) : (vue.openBlock(), vue.createElementBlock("div", { key: 1, class: "video-cover", onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.playVideo && _ctx.playVideo(...args))), style: vue.normalizeStyle({ backgroundImage: `url(${_ctx.coverImage})` }) }, [ vue.createElementVNode("img", { src: _ctx.element.properties.src, alt: "Play button", class: "play-btn" }, null, 8 /* PROPS */, _hoisted_3$d) ], 4 /* STYLE */)) ], 4 /* STYLE */) ], 4 /* STYLE */)) } script$n.render = render$j; script$n.__file = "src/components/UniVideo/UniVideo.vue"; script$n.install = (app) => { app.component(script$n.name, script$n); }; var script$m = vue.defineComponent({ name: 'uni-calendar', props: { element: { type: Object, required: true, }, }, emits: ['trigger'], setup(props, { emit }) { const weekDays = vue.computed(() => { const lang = props.element.language; if (lang === 'en') { return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; } // 泰语:th、zh 等旧数据与默认 return ['จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส', 'อา']; }); const month = vue.computed(() => { const lang = props.element.language; if (lang === 'en') { return 'month'; } return 'เดือน'; }); // 解析日期,优先使用elementData.endTime,其次使用weddingDate const endDate = vue.computed(() => { if (props.element.endTime) { const [year, month, day] = props.element.endTime.split('/').map(Number); return new Date(year, month - 1, day); } else if (props.element) { return new Date(Date.now()); } return new Date(); // 默认使用当前日期 }); const currentYear = vue.computed(() => endDate.value.getFullYear()); const currentMonth = vue.computed(() => endDate.value.getMonth() + 1); const currentDay = vue.computed(() => endDate.value.getDate()); // 生成当月日历天数 const days = vue.computed(() => { const year = currentYear.value; const month = currentMonth.value; // 获取当月第一天是星期几(0-6,0是星期日) const firstDay = new Date(year, month - 1, 1).getDay(); // 调整为中国习惯(星期一为一周的第一天) const firstDayOfWeek = firstDay === 0 ? 6 : firstDay - 1; // 获取当月天数 const daysInMonth = new Date(year, month, 0).getDate(); // 生成日历数组 const daysArray = []; // 填充上个月的空白 for (let i = 0; i < firstDayOfWeek; i++) { daysArray.push(null); } // 填充当月天数 for (let i = 1; i <= daysInMonth; i++) { daysArray.push(i); } return daysArray; }); // 判断是否是活动日期 const isActiveDay = (day) => { return day !== null && day === currentDay.value && currentMonth.value === endDate.value.getMonth() + 1 && currentYear.value === endDate.value.getFullYear(); }; // 计算日历容器的样式 const calendarStyle = vue.computed(() => { return { position: (props.element && props.element.css && props.element.css.position) || 'absolute', left: px2rem(props.element.css.left), top: px2rem(props.element.css.top), width: px2rem(props.element.css.width), height: px2rem(props.element.css.height), transform: props.element.showSize === 'small' ? 'scale(0.75)' : props.element.showSize === 'large' ? 'scale(1)' : 'scale(0.85)', opacity: props.element.css.opacity, }; }); // 计算动画容器的样式 const aniWrapStyle = vue.computed(() => { return { backgroundColor: props.element.css.backgroundColor, borderRadius: px2rem(props.element.css.borderRadius || 0), borderColor: props.element.themeColor, borderStyle: props.element.css.borderStyle, borderWidth: px2rem(props.element.css.borderWidth || 0), animation: props.element.css.animation }; }); const handleClick = () => { if (props.element.triggers.event) { emit('trigger', { elementId: props.element.id, eventType: 'click', data: { element: props.element.triggers } }); } }; return { isActiveDay, handleClick, weekDays, currentYear, currentMonth, currentDay, days, month, calendarStyle, aniWrapStyle }; } }); const _hoisted_1$h = ["data-id", "data-pid", "data-type", "data-show", "pid", "type", "layername", "sign", "signsort"]; const _hoisted_2$d = { key: 0, class: "can-wrap" }; const _hoisted_3$c = { class: "can-top" }; const _hoisted_4$a = { class: "can-main" }; const _hoisted_5$9 = { class: "can-date" }; const _hoisted_6$7 = { key: 1, class: "can-wrap2" }; const _hoisted_7$6 = { class: "can-top" }; const _hoisted_8$3 = /*#__PURE__*/vue.createElementVNode("span", null, "/", -1 /* HOISTED */); const _hoisted_9$3 = { class: "can-main" }; const _hoisted_10$3 = { class: "can-week" }; const _hoisted_11$3 = { class: "can-date" }; const _hoisted_12$3 = { key: 2, class: "can-wrap3" }; const _hoisted_13$3 = { class: "can-top" }; const _hoisted_14$3 = /*#__PURE__*/vue.createElementVNode("span", null, "/", -1 /* HOISTED */); const _hoisted_15$3 = { class: "can-main" }; const _hoisted_16$3 = { class: "can-date" }; function render$i(_ctx, _cache, $props, $setup, $data, $options) { return (vue.openBlock(), vue.createElementBlock("div", { class: "ele-calendar eles", "data-id": _ctx.element.id, "data-pid": _ctx.element.pid, "data-type": _ctx.element.type, "data-show": _ctx.element.properties.visible, pid: _ctx.element.pid, type: _ctx.element.type, layername: _ctx.element.layerName, sign: _ctx.element.sign, signsort: _ctx.element.signSort, style: vue.normalizeStyle(_ctx.calendarStyle), onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))) }, [ vue.createEleme