unika-components
Version:
Unika Vue3 components library
1,232 lines (1,200 loc) • 1.13 MB
JavaScript
(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