界面样式调整

This commit is contained in:
hxuanyu 2025-06-26 00:06:59 +08:00
parent d70b962ef5
commit cdf5f81601
9 changed files with 233 additions and 184 deletions

View File

@ -1,62 +1,62 @@
# OBS Overlay Widget
# OBS 悬浮小组件
A collection of highly customizable widgets for OBS Studio streaming and recording, built with Vue 3, TypeScript, and Vite.
一个为 OBS Studio 直播和录制场景开发的高度可定制化小组件集合,基于 Vue 3、TypeScript 和 Vite 构建。
## Features
## 功能特点
- **Time and Date Display**: Customizable formats for showing current time and date
- **Text Display**: Show fixed text or API-returned content with custom styling
- **Image Display**: Display local or remote images with customization options
- **Split View Interface**: Configuration panel on the left, real-time preview on the right
- **Transparent Background**: All widgets have transparent backgrounds suitable for OBS overlay
- **URL Generation**: Automatically generates sharable URLs with encoded configuration
- **Pure Preview Mode**: Open generated URLs to display only the widget with transparent background
- **时间和日期显示**:可自定义格式的时间和日期显示
- **文本显示**:展示固定文字或 API 返回内容,支持自定义样式
- **图片显示**:展示本地或远程图片,支持自定义设置
- **分屏界面**:左侧为配置面板,右侧为实时预览
- **透明背景**:所有小组件均具有适合 OBS 悬浮的透明背景
- **URL 生成**:自动生成包含编码配置的可分享 URL
- **纯预览模式**:打开生成的 URL 仅显示小组件内容,无配置界面,背景透明
## Widget Types
## 小组件类型
1. **Clock Widget**: Display current time with customizable format, style, and effects
2. **Date Widget**: Show current date with customizable format, style, and effects
3. **Text Widget**: Display text with customizable styles including gradients, shadows, and fonts
4. **Image Widget**: Show images with customizable size, effects, and positioning
1. **时钟小组件**:显示当前时间,可自定义格式、样式和特效
2. **日期小组件**:显示当前日期,可自定义格式、样式和特效
3. **文本小组件**:显示文本,支持渐变、阴影、字体等自定义样式
4. **图片小组件**:显示图片,可自定义大小、特效和位置
## Usage
## 使用方法
1. Select a widget type from the dropdown
2. Configure the widget using the control panel on the left
3. See real-time preview on the right
4. Copy the generated URL to use in OBS Studio as a Browser Source
1. 从下拉菜单中选择小组件类型
2. 使用左侧控制面板配置小组件
3. 在右侧实时查看预览效果
4. 复制生成的 URL在 OBS Studio 中作为浏览器源使用
## Development
## 开发
```bash
# Install dependencies
# 安装依赖
npm install
# Start development server
# 启动开发服务器
npm run dev
# Build for production
# 构建生产版本
npm run build
# Preview production build
# 预览生产构建
npm run preview
```
## Troubleshooting
## 故障排除
If you encounter TypeScript errors related to undefined properties, make sure that:
如果遇到与未定义属性相关的 TypeScript 错误,请确保:
1. All widget components handle possible undefined configuration properties
2. Default values are provided for all configuration options
3. Use proper null checking (e.g., `props.config.property || defaultValue`)
1. 所有小组件组件都能处理可能未定义的配置属性
2. 为所有配置选项提供默认值
3. 使用适当的空值检查(例如:`props.config.property || defaultValue`
## Integration with OBS Studio
## 与 OBS Studio 集成
1. Run this application on a web server or locally
2. Configure your widget using the configuration interface
3. Copy the generated URL
4. In OBS Studio:
- Add a "Browser Source" to your scene
- Paste the URL into the Browser Source URL field
- Set width and height according to your needs
- Check "Shutdown source when not visible" for better performance
1. 在 Web 服务器或本地运行此应用
2. 使用配置界面设置您的小组件
3. 复制生成的 URL
4. 在 OBS Studio 中:
- 向您的场景添加"浏览器源"
- 将 URL 粘贴到浏览器源 URL 字段
- 根据需要设置宽度和高度
- 勾选"不可见时关闭源"以获得更好的性能

View File

@ -1,28 +1,47 @@
<template>
<div class="clock-config">
<h2>Clock Widget Settings</h2>
<h2>时钟小组件设置</h2>
<el-form label-position="top">
<el-form-item label="Time Format">
<el-select v-model="localConfig.format" placeholder="Select format">
<el-option label="HH:mm:ss (24-hour)" value="HH:mm:ss" />
<el-option label="HH:mm (24-hour)" value="HH:mm" />
<el-option label="hh:mm:ss A (12-hour)" value="hh:mm:ss A" />
<el-option label="hh:mm A (12-hour)" value="hh:mm A" />
<el-form-item label="时间格式">
<el-select v-model="localConfig.format" placeholder="选择格式">
<el-option label="HH:mm:ss (24小时制)" value="HH:mm:ss" />
<el-option label="HH:mm (24小时制)" value="HH:mm" />
<el-option label="hh:mm:ss A (12小时制)" value="hh:mm:ss A" />
<el-option label="hh:mm A (12小时制)" value="hh:mm A" />
</el-select>
</el-form-item>
<el-form-item label="Show Seconds">
<el-switch v-model="localConfig.showSeconds" />
<el-form-item>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<span>显示秒</span>
<el-switch v-model="localConfig.showSeconds" style="margin-left: 10px;" />
</div>
<div>
<span>显示日期</span>
<el-switch v-model="localConfig.showDate" style="margin-left: 10px;" />
</div>
</div>
</el-form-item>
<el-divider>Text Style</el-divider>
<el-form-item v-if="localConfig.showDate" label="日期格式">
<el-select v-model="localConfig.dateFormat" placeholder="选择格式">
<el-option label="YYYY-MM-DD" value="YYYY-MM-DD" />
<el-option label="MM/DD/YYYY" value="MM/DD/YYYY" />
<el-option label="DD/MM/YYYY" value="DD/MM/YYYY" />
<el-option label="MMMM D, YYYY" value="MMMM D, YYYY" />
<el-option label="D MMMM YYYY" value="D MMMM YYYY" />
</el-select>
</el-form-item>
<el-form-item label="Font Size">
<el-divider>文本样式</el-divider>
<el-form-item label="字体大小">
<el-slider v-model="localConfig.fontSize" :min="12" :max="120" show-input />
</el-form-item>
<el-form-item label="Font Family">
<el-form-item label="字体">
<el-select v-model="localConfig.fontFamily">
<el-option label="Arial" value="Arial" />
<el-option label="Helvetica" value="Helvetica" />
@ -34,58 +53,58 @@
</el-select>
</el-form-item>
<el-form-item label="Font Weight">
<el-form-item label="字重">
<el-select v-model="localConfig.fontWeight">
<el-option label="Normal" value="normal" />
<el-option label="Bold" value="bold" />
<el-option label="Light" value="lighter" />
<el-option label="普通" value="normal" />
<el-option label="粗体" value="bold" />
<el-option label="细体" value="lighter" />
</el-select>
</el-form-item>
<el-divider>Color Settings</el-divider>
<el-divider>颜色设置</el-divider>
<el-form-item label="Use Gradient Colors">
<el-form-item label="使用渐变色">
<el-switch v-model="localConfig.useGradient" />
</el-form-item>
<template v-if="!localConfig.useGradient">
<el-form-item label="Text Color">
<el-form-item label="文本颜色">
<el-color-picker v-model="localConfig.color" show-alpha />
</el-form-item>
</template>
<template v-else>
<el-form-item label="Gradient Start Color">
<el-form-item label="渐变起始颜色">
<el-color-picker v-model="localConfig.gradientColors[0]" show-alpha />
</el-form-item>
<el-form-item label="Gradient End Color">
<el-form-item label="渐变结束颜色">
<el-color-picker v-model="localConfig.gradientColors[1]" show-alpha />
</el-form-item>
</template>
<el-divider>Effects</el-divider>
<el-divider>特效</el-divider>
<el-form-item label="Text Shadow">
<el-form-item label="文字阴影">
<el-switch v-model="localConfig.textShadow" />
</el-form-item>
<template v-if="localConfig.textShadow">
<el-form-item label="Shadow Color">
<el-form-item label="阴影颜色">
<el-color-picker v-model="localConfig.shadowColor" show-alpha />
</el-form-item>
<el-form-item label="Shadow Blur">
<el-form-item label="阴影模糊度">
<el-slider v-model="localConfig.shadowBlur" :min="0" :max="20" show-input />
</el-form-item>
</template>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="applyPreset('modern')">Modern</el-button>
<el-button type="success" @click="applyPreset('neon')">Neon</el-button>
<el-button type="warning" @click="applyPreset('elegant')">Elegant</el-button>
<el-button type="danger" @click="applyPreset('minimal')">Minimal</el-button>
<el-button type="primary" @click="applyPreset('modern')">现代风格</el-button>
<el-button type="success" @click="applyPreset('neon')">霓虹风格</el-button>
<el-button type="warning" @click="applyPreset('elegant')">优雅风格</el-button>
<el-button type="danger" @click="applyPreset('minimal')">简约风格</el-button>
</el-button-group>
</el-form-item>
</el-form>
@ -95,7 +114,7 @@
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue';
// Define props interface for config
// props
interface ClockConfig {
format: string;
color: string;
@ -108,6 +127,8 @@ interface ClockConfig {
useGradient: boolean;
gradientColors: string[];
showSeconds: boolean;
showDate: boolean;
dateFormat: string;
}
// Define props with default values
@ -125,7 +146,9 @@ const props = withDefaults(defineProps<{
shadowBlur: 4,
useGradient: false,
gradientColors: ['#ff0000', '#0000ff'],
showSeconds: true
showSeconds: true,
showDate: false,
dateFormat: 'YYYY-MM-DD'
})
});
@ -146,7 +169,9 @@ const localConfig = ref<ClockConfig>({
shadowBlur: 4,
useGradient: false,
gradientColors: ['#ff0000', '#0000ff'],
showSeconds: true
showSeconds: true,
showDate: false,
dateFormat: 'YYYY-MM-DD'
});
// Sync with parent config on mount
@ -167,7 +192,9 @@ const presets = {
textShadow: true,
shadowColor: 'rgba(0, 0, 0, 0.3)',
shadowBlur: 10,
showSeconds: false
showSeconds: false,
showDate: true,
dateFormat: 'YYYY-MM-DD'
},
neon: {
format: 'HH:mm:ss',
@ -179,7 +206,8 @@ const presets = {
textShadow: true,
shadowColor: 'rgba(57, 255, 20, 0.8)',
shadowBlur: 15,
showSeconds: true
showSeconds: true,
showDate: false
},
elegant: {
format: 'hh:mm A',
@ -191,7 +219,9 @@ const presets = {
textShadow: true,
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 5,
showSeconds: false
showSeconds: false,
showDate: true,
dateFormat: 'MMMM D, YYYY'
},
minimal: {
format: 'HH:mm',
@ -201,7 +231,8 @@ const presets = {
color: '#ffffff',
useGradient: false,
textShadow: false,
showSeconds: false
showSeconds: false,
showDate: false
}
};

View File

@ -1,10 +1,10 @@
<template>
<div class="date-config">
<h2>Date Widget Settings</h2>
<h2>日期小组件设置</h2>
<el-form label-position="top">
<el-form-item label="Date Format">
<el-select v-model="localConfig.format" placeholder="Select format">
<el-form-item label="日期格式">
<el-select v-model="localConfig.format" placeholder="选择格式">
<el-option label="YYYY-MM-DD" value="YYYY-MM-DD" />
<el-option label="MM/DD/YYYY" value="MM/DD/YYYY" />
<el-option label="DD/MM/YYYY" value="DD/MM/YYYY" />
@ -13,17 +13,17 @@
</el-select>
</el-form-item>
<el-form-item label="Show Weekday">
<el-form-item label="显示星期">
<el-switch v-model="localConfig.showWeekday" />
</el-form-item>
<el-divider>Text Style</el-divider>
<el-divider>文本样式</el-divider>
<el-form-item label="Font Size">
<el-form-item label="字体大小">
<el-slider v-model="localConfig.fontSize" :min="12" :max="80" show-input />
</el-form-item>
<el-form-item label="Font Family">
<el-form-item label="字体">
<el-select v-model="localConfig.fontFamily">
<el-option label="Arial" value="Arial" />
<el-option label="Helvetica" value="Helvetica" />
@ -35,58 +35,58 @@
</el-select>
</el-form-item>
<el-form-item label="Font Weight">
<el-form-item label="字重">
<el-select v-model="localConfig.fontWeight">
<el-option label="Normal" value="normal" />
<el-option label="Bold" value="bold" />
<el-option label="Light" value="lighter" />
<el-option label="普通" value="normal" />
<el-option label="粗体" value="bold" />
<el-option label="细体" value="lighter" />
</el-select>
</el-form-item>
<el-divider>Color Settings</el-divider>
<el-divider>颜色设置</el-divider>
<el-form-item label="Use Gradient Colors">
<el-form-item label="使用渐变色">
<el-switch v-model="localConfig.useGradient" />
</el-form-item>
<template v-if="!localConfig.useGradient">
<el-form-item label="Text Color">
<el-form-item label="文本颜色">
<el-color-picker v-model="localConfig.color" show-alpha />
</el-form-item>
</template>
<template v-else>
<el-form-item label="Gradient Start Color">
<el-form-item label="渐变起始颜色">
<el-color-picker v-model="localConfig.gradientColors[0]" show-alpha />
</el-form-item>
<el-form-item label="Gradient End Color">
<el-form-item label="渐变结束颜色">
<el-color-picker v-model="localConfig.gradientColors[1]" show-alpha />
</el-form-item>
</template>
<el-divider>Effects</el-divider>
<el-divider>特效</el-divider>
<el-form-item label="Text Shadow">
<el-form-item label="文字阴影">
<el-switch v-model="localConfig.textShadow" />
</el-form-item>
<template v-if="localConfig.textShadow">
<el-form-item label="Shadow Color">
<el-form-item label="阴影颜色">
<el-color-picker v-model="localConfig.shadowColor" show-alpha />
</el-form-item>
<el-form-item label="Shadow Blur">
<el-form-item label="阴影模糊度">
<el-slider v-model="localConfig.shadowBlur" :min="0" :max="20" show-input />
</el-form-item>
</template>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="applyPreset('modern')">Modern</el-button>
<el-button type="success" @click="applyPreset('elegant')">Elegant</el-button>
<el-button type="warning" @click="applyPreset('casual')">Casual</el-button>
<el-button type="danger" @click="applyPreset('minimal')">Minimal</el-button>
<el-button type="primary" @click="applyPreset('modern')">现代风格</el-button>
<el-button type="success" @click="applyPreset('elegant')">优雅风格</el-button>
<el-button type="warning" @click="applyPreset('casual')">休闲风格</el-button>
<el-button type="danger" @click="applyPreset('minimal')">简约风格</el-button>
</el-button-group>
</el-form-item>
</el-form>

View File

@ -1,56 +1,56 @@
<template>
<div class="image-config">
<h2>Image Widget Settings</h2>
<h2>图片小组件设置</h2>
<el-form label-position="top">
<el-form-item label="Image URL">
<el-input v-model="localConfig.imageUrl" placeholder="Enter image URL" />
<el-form-item label="图片URL">
<el-input v-model="localConfig.imageUrl" placeholder="输入图片URL" />
</el-form-item>
<div class="preview-image" v-if="localConfig.imageUrl">
<img :src="localConfig.imageUrl" alt="Preview" style="max-width: 100%; max-height: 150px;" />
<img :src="localConfig.imageUrl" alt="预览" style="max-width: 100%; max-height: 150px;" />
</div>
<el-divider>Size & Appearance</el-divider>
<el-divider>尺寸与外观</el-divider>
<el-form-item label="Width (px)">
<el-form-item label="宽度 (像素)">
<el-slider v-model="localConfig.width" :min="50" :max="800" show-input />
</el-form-item>
<el-form-item label="Height (px)">
<el-form-item label="高度 (像素)">
<el-slider v-model="localConfig.height" :min="50" :max="800" show-input />
</el-form-item>
<el-form-item label="Opacity">
<el-form-item label="透明度">
<el-slider v-model="localConfig.opacity" :min="0" :max="1" :step="0.01" show-input />
</el-form-item>
<el-form-item label="Border Radius (px)">
<el-form-item label="圆角半径 (像素)">
<el-slider v-model="localConfig.borderRadius" :min="0" :max="100" show-input />
</el-form-item>
<el-divider>Effects</el-divider>
<el-divider>特效</el-divider>
<el-form-item label="Shadow">
<el-form-item label="阴影">
<el-switch v-model="localConfig.shadow" />
</el-form-item>
<template v-if="localConfig.shadow">
<el-form-item label="Shadow Color">
<el-form-item label="阴影颜色">
<el-color-picker v-model="localConfig.shadowColor" show-alpha />
</el-form-item>
<el-form-item label="Shadow Blur">
<el-form-item label="阴影模糊度">
<el-slider v-model="localConfig.shadowBlur" :min="0" :max="50" show-input />
</el-form-item>
</template>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="applyPreset('normal')">Normal</el-button>
<el-button type="success" @click="applyPreset('rounded')">Rounded</el-button>
<el-button type="warning" @click="applyPreset('shadow')">Shadow</el-button>
<el-button type="danger" @click="applyPreset('circular')">Circular</el-button>
<el-button type="primary" @click="applyPreset('normal')">正常</el-button>
<el-button type="success" @click="applyPreset('rounded')">圆角</el-button>
<el-button type="warning" @click="applyPreset('shadow')">阴影</el-button>
<el-button type="danger" @click="applyPreset('circular')">圆形</el-button>
</el-button-group>
</el-form-item>
</el-form>

View File

@ -1,19 +1,19 @@
<template>
<div class="text-config">
<h2>Text Widget Settings</h2>
<h2>文本小组件设置</h2>
<el-form label-position="top">
<el-form-item label="Text Content">
<el-input v-model="localConfig.text" type="textarea" :rows="3" placeholder="Enter text to display" />
<el-form-item label="文本内容">
<el-input v-model="localConfig.text" type="textarea" :rows="3" placeholder="输入要显示的文本" />
</el-form-item>
<el-divider>Text Style</el-divider>
<el-divider>文本样式</el-divider>
<el-form-item label="Font Size">
<el-form-item label="字体大小">
<el-slider v-model="localConfig.fontSize" :min="12" :max="100" show-input />
</el-form-item>
<el-form-item label="Font Family">
<el-form-item label="字体">
<el-select v-model="localConfig.fontFamily">
<el-option label="Arial" value="Arial" />
<el-option label="Helvetica" value="Helvetica" />
@ -25,58 +25,58 @@
</el-select>
</el-form-item>
<el-form-item label="Font Weight">
<el-form-item label="字重">
<el-select v-model="localConfig.fontWeight">
<el-option label="Normal" value="normal" />
<el-option label="Bold" value="bold" />
<el-option label="Light" value="lighter" />
<el-option label="普通" value="normal" />
<el-option label="粗体" value="bold" />
<el-option label="细体" value="lighter" />
</el-select>
</el-form-item>
<el-divider>Color Settings</el-divider>
<el-divider>颜色设置</el-divider>
<el-form-item label="Use Gradient Colors">
<el-form-item label="使用渐变色">
<el-switch v-model="localConfig.useGradient" />
</el-form-item>
<template v-if="!localConfig.useGradient">
<el-form-item label="Text Color">
<el-form-item label="文本颜色">
<el-color-picker v-model="localConfig.color" show-alpha />
</el-form-item>
</template>
<template v-else>
<el-form-item label="Gradient Start Color">
<el-form-item label="渐变起始颜色">
<el-color-picker v-model="localConfig.gradientColors[0]" show-alpha />
</el-form-item>
<el-form-item label="Gradient End Color">
<el-form-item label="渐变结束颜色">
<el-color-picker v-model="localConfig.gradientColors[1]" show-alpha />
</el-form-item>
</template>
<el-divider>Effects</el-divider>
<el-divider>特效</el-divider>
<el-form-item label="Text Shadow">
<el-form-item label="文字阴影">
<el-switch v-model="localConfig.textShadow" />
</el-form-item>
<template v-if="localConfig.textShadow">
<el-form-item label="Shadow Color">
<el-form-item label="阴影颜色">
<el-color-picker v-model="localConfig.shadowColor" show-alpha />
</el-form-item>
<el-form-item label="Shadow Blur">
<el-form-item label="阴影模糊度">
<el-slider v-model="localConfig.shadowBlur" :min="0" :max="20" show-input />
</el-form-item>
</template>
<el-form-item>
<el-button-group>
<el-button type="primary" @click="applyPreset('modern')">Modern</el-button>
<el-button type="success" @click="applyPreset('neon')">Neon</el-button>
<el-button type="warning" @click="applyPreset('retro')">Retro</el-button>
<el-button type="danger" @click="applyPreset('minimal')">Minimal</el-button>
<el-button type="primary" @click="applyPreset('modern')">现代风格</el-button>
<el-button type="success" @click="applyPreset('neon')">霓虹风格</el-button>
<el-button type="warning" @click="applyPreset('retro')">复古风格</el-button>
<el-button type="danger" @click="applyPreset('minimal')">简约风格</el-button>
</el-button-group>
</el-form-item>
</el-form>

View File

@ -2,7 +2,7 @@
<div class="config-view">
<div class="left-panel">
<div class="widget-selector">
<el-select v-model="selectedWidget" placeholder="Select Widget" @change="handleWidgetChange">
<el-select v-model="selectedWidget" placeholder="选择小组件" @change="handleWidgetChange">
<el-option v-for="widget in widgets" :key="widget.value" :label="widget.label" :value="widget.value" />
</el-select>
</div>
@ -15,7 +15,7 @@
<el-input v-model="generatedUrl" readonly>
<template #append>
<el-button @click="copyUrl">
<el-icon><CopyDocument /></el-icon> Copy
<el-icon><CopyDocument /></el-icon> 复制
</el-button>
</template>
</el-input>
@ -38,42 +38,42 @@ import { CopyDocument } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { encodeConfig, decodeConfig } from '../utils/configUtils';
// Widget components and their configs
//
import ClockWidget from '../widgets/ClockWidget.vue';
import DateWidget from '../widgets/DateWidget.vue';
import TextWidget from '../widgets/TextWidget.vue';
import ImageWidget from '../widgets/ImageWidget.vue';
// Config components
//
import ClockConfig from '../components/config/ClockConfig.vue';
import DateConfig from '../components/config/DateConfig.vue';
import TextConfig from '../components/config/TextConfig.vue';
import ImageConfig from '../components/config/ImageConfig.vue';
const widgets = [
{ label: 'Clock Widget', value: 'clock', component: ClockWidget, configComponent: ClockConfig },
{ label: 'Date Widget', value: 'date', component: DateWidget, configComponent: DateConfig },
{ label: 'Text Widget', value: 'text', component: TextWidget, configComponent: TextConfig },
{ label: 'Image Widget', value: 'image', component: ImageWidget, configComponent: ImageConfig },
{ label: '时钟小组件', value: 'clock', component: ClockWidget, configComponent: ClockConfig },
{ label: '日期小组件', value: 'date', component: DateWidget, configComponent: DateConfig },
{ label: '文本小组件', value: 'text', component: TextWidget, configComponent: TextConfig },
{ label: '图片小组件', value: 'image', component: ImageWidget, configComponent: ImageConfig },
];
const selectedWidget = ref('clock');
const currentWidgetConfig = ref({});
const generatedUrl = ref('');
// Get widget component based on selection
//
const currentWidgetComponent = computed(() => {
const widget = widgets.find(w => w.value === selectedWidget.value);
return widget?.component;
});
// Get config component based on selection
//
const currentConfigComponent = computed(() => {
const widget = widgets.find(w => w.value === selectedWidget.value);
return widget?.configComponent;
});
// Set default config for each widget type
//
const getDefaultConfig = (widgetType: string) => {
switch (widgetType) {
case 'clock':
@ -139,13 +139,13 @@ const handleWidgetChange = () => {
updateGeneratedUrl();
};
// Update widget configuration
//
const updateWidgetConfig = (newConfig: any) => {
currentWidgetConfig.value = newConfig;
updateGeneratedUrl();
};
// Generate preview URL
// URL
const updateGeneratedUrl = () => {
const baseUrl = window.location.origin;
const configStr = encodeConfig({
@ -155,16 +155,16 @@ const updateGeneratedUrl = () => {
generatedUrl.value = `${baseUrl}/preview?data=${configStr}`;
};
// Copy URL to clipboard
// URL
const copyUrl = () => {
navigator.clipboard.writeText(generatedUrl.value).then(() => {
ElMessage.success('URL copied to clipboard!');
ElMessage.success('URL 已复制到剪贴板!');
}).catch(() => {
ElMessage.error('Failed to copy URL');
ElMessage.error('复制 URL 失败');
});
};
// Check for query params on load (for direct linking)
//
onMounted(() => {
const queryParams = new URLSearchParams(window.location.search);
const data = queryParams.get('data');
@ -175,15 +175,15 @@ onMounted(() => {
selectedWidget.value = decodedData.type;
currentWidgetConfig.value = decodedData.config;
} catch (e) {
ElMessage.error('Invalid configuration in URL');
handleWidgetChange(); // Load default config
ElMessage.error('URL 中的配置无效');
handleWidgetChange(); //
}
} else {
handleWidgetChange(); // Load default config
handleWidgetChange(); //
}
});
// Update URL when configuration changes
// URL
watch([selectedWidget, currentWidgetConfig], () => {
updateGeneratedUrl();
}, { deep: true });

View File

@ -2,8 +2,8 @@
<div class="home-view">
<div class="container">
<div class="header">
<h1>OBS Overlay Widget</h1>
<p>Create customizable widgets for OBS Studio streaming and recording</p>
<h1>OBS 悬浮小组件</h1>
<p>OBS Studio 直播和录制场景创建可定制化小组件</p>
</div>
<div class="cards">
@ -11,9 +11,9 @@
<div class="card-icon">
<el-icon><Setting /></el-icon>
</div>
<div class="card-title">Configure Widgets</div>
<div class="card-title">配置小组件</div>
<div class="card-description">
Design and customize widgets for your OBS streams with an interactive interface
通过交互式界面设计和自定义 OBS 直播小组件
</div>
</div>
@ -21,53 +21,53 @@
<div class="card-icon">
<el-icon><Document /></el-icon>
</div>
<div class="card-title">Documentation</div>
<div class="card-title">使用文档</div>
<div class="card-description">
Learn how to use and integrate OBS Overlay Widgets into your streams
了解如何使用和集成 OBS 悬浮小组件到您的直播中
</div>
</div>
</div>
<div class="features">
<h2>Available Widgets</h2>
<h2>可用小组件</h2>
<div class="widget-list">
<div class="widget-item">
<div class="widget-icon"></div>
<div class="widget-info">
<h3>Clock Widget</h3>
<p>Display current time with customizable format, style, and effects</p>
<h3>时钟小组件</h3>
<p>显示当前时间可自定义格式样式和特效</p>
</div>
</div>
<div class="widget-item">
<div class="widget-icon">📅</div>
<div class="widget-info">
<h3>Date Widget</h3>
<p>Show current date with customizable format, style, and effects</p>
<h3>日期小组件</h3>
<p>显示当前日期可自定义格式样式和特效</p>
</div>
</div>
<div class="widget-item">
<div class="widget-icon">📝</div>
<div class="widget-info">
<h3>Text Widget</h3>
<p>Display text with customizable styles including gradients, shadows, and fonts</p>
<h3>文本小组件</h3>
<p>显示文本支持渐变阴影字体等自定义样式</p>
</div>
</div>
<div class="widget-item">
<div class="widget-icon">🖼</div>
<div class="widget-info">
<h3>Image Widget</h3>
<p>Show images with customizable size, effects, and positioning</p>
<h3>图片小组件</h3>
<p>显示图片可自定义大小特效和位置</p>
</div>
</div>
</div>
</div>
<div class="footer">
<p>OBS Overlay Widget &copy; 2025</p>
<p>OBS 悬浮小组件 &copy; 2025</p>
</div>
</div>
</div>
@ -84,7 +84,7 @@ const goToConfig = () => {
};
const goToDoc = () => {
// This would go to documentation in a real app
//
window.open('https://github.com/yourusername/obs-overlay-widget', '_blank');
};
</script>

View File

@ -12,13 +12,13 @@
import { ref, onMounted, computed } from 'vue';
import { decodeConfig } from '../utils/configUtils';
// Import widget components
//
import ClockWidget from '../widgets/ClockWidget.vue';
import DateWidget from '../widgets/DateWidget.vue';
import TextWidget from '../widgets/TextWidget.vue';
import ImageWidget from '../widgets/ImageWidget.vue';
// Widget registry
//
const widgetRegistry = {
'clock': ClockWidget,
'date': DateWidget,
@ -42,7 +42,7 @@ onMounted(() => {
widgetType.value = decodedData.type;
widgetConfig.value = decodedData.config;
} catch (e) {
console.error('Invalid configuration in URL', e);
console.error('URL 中的配置无效', e);
}
}
});

View File

@ -1,6 +1,7 @@
<template>
<div class="clock-widget" :style="clockStyle">
{{ currentTime }}
<div>{{ currentTime }}</div>
<div v-if="props.config.showDate" :style="clockStyle">{{ currentDate }}</div>
</div>
</template>
@ -21,6 +22,8 @@ interface ClockConfig {
useGradient: boolean;
gradientColors: string[];
showSeconds: boolean;
showDate: boolean;
dateFormat: string;
}
// Define props with default values
@ -38,18 +41,27 @@ const props = withDefaults(defineProps<{
shadowBlur: 4,
useGradient: false,
gradientColors: ['#ff0000', '#0000ff'],
showSeconds: true
showSeconds: true,
showDate: false,
dateFormat: 'YYYY-MM-DD'
})
});
// State for current time
// State for current time and date
const currentTime = ref('');
const currentDate = ref('');
// Update time string based on format
const updateTime = () => {
const currentFormat = props.config.format || 'HH:mm:ss';
const format = props.config.showSeconds ? currentFormat : currentFormat.replace(':ss', '');
currentTime.value = dayjs().format(format);
// Update date if enabled
if (props.config.showDate) {
const dateFormat = props.config.dateFormat || 'YYYY-MM-DD';
currentDate.value = dayjs().format(dateFormat);
}
};
// Interval for updating time
@ -71,7 +83,7 @@ onUnmounted(() => {
}
});
// Update interval if showSeconds changes
// Watch for showSeconds changes
watch(() => props.config.showSeconds, (newValue) => {
if (timeInterval !== null) {
window.clearInterval(timeInterval);
@ -82,6 +94,11 @@ watch(() => props.config.showSeconds, (newValue) => {
updateTime();
});
// Watch for showDate or dateFormat changes
watch([() => props.config.showDate, () => props.config.dateFormat], () => {
updateTime();
}, { deep: true });
// Computed styles for the clock
const clockStyle = computed(() => {
const style: Record<string, string> = {
@ -116,5 +133,6 @@ const clockStyle = computed(() => {
padding: 10px;
font-family: Arial, sans-serif;
user-select: none;
text-align: center;
}
</style>