文档完善
This commit is contained in:
parent
de73a041b1
commit
03a19beafa
242
README.md
242
README.md
@ -4,27 +4,63 @@
|
||||
|
||||
## 功能特点
|
||||
|
||||
- **时间和日期显示**:可自定义格式的时间和日期显示
|
||||
- **文本显示**:展示固定文字或 API 返回内容,支持自定义样式
|
||||
- **图片显示**:展示本地或远程图片,支持自定义设置
|
||||
- **多种小组件**:内置时钟、日期、文本、图片等多种可配置小组件
|
||||
- **动态首页**:自动展示所有已注册小组件,便于预览和选择
|
||||
- **分屏界面**:左侧为配置面板,右侧为实时预览
|
||||
- **透明背景**:所有小组件均具有适合 OBS 悬浮的透明背景
|
||||
- **URL 生成**:自动生成包含编码配置的可分享 URL
|
||||
- **纯预览模式**:打开生成的 URL 仅显示小组件内容,无配置界面,背景透明
|
||||
- **可扩展性**:集中化的小组件注册机制,便于添加新的小组件类型
|
||||
|
||||
## 小组件类型
|
||||
|
||||
1. **时钟小组件**:显示当前时间,可自定义格式、样式和特效
|
||||
1. **时钟小组件**:显示当前时间,可自定义格式、样式和特效,支持显示日期
|
||||
2. **日期小组件**:显示当前日期,可自定义格式、样式和特效
|
||||
3. **文本小组件**:显示文本,支持渐变、阴影、字体等自定义样式
|
||||
4. **图片小组件**:显示图片,可自定义大小、特效和位置
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 从下拉菜单中选择小组件类型
|
||||
2. 使用左侧控制面板配置小组件
|
||||
3. 在右侧实时查看预览效果
|
||||
4. 复制生成的 URL,在 OBS Studio 中作为浏览器源使用
|
||||
1. 在首页浏览并选择需要的小组件类型
|
||||
2. 在配置页面中,从下拉菜单中选择小组件类型
|
||||
3. 使用左侧控制面板配置小组件属性和样式
|
||||
4. 在右侧实时查看预览效果
|
||||
5. 复制生成的 URL,在 OBS Studio 中作为浏览器源使用
|
||||
|
||||
## 小组件配置详解
|
||||
|
||||
### 时钟小组件
|
||||
|
||||
- **时间格式**:支持 24 小时制 (HH:mm:ss) 和 12 小时制 (hh:mm:ss A)
|
||||
- **显示秒**:切换是否显示秒数
|
||||
- **显示日期**:切换是否在时钟下方显示日期
|
||||
- **日期格式**:多种日期格式可选,如 YYYY-MM-DD、MM/DD/YYYY 等
|
||||
- **样式设置**:
|
||||
- 字体大小、字体类型、字重
|
||||
- 文本颜色或渐变色
|
||||
- 文字阴影及模糊度
|
||||
|
||||
### 日期小组件
|
||||
|
||||
- **日期格式**:多种格式可选,支持年月日不同顺序排列
|
||||
- **显示星期**:切换是否显示星期几
|
||||
- **样式设置**:与时钟小组件类似,支持字体、颜色和阴影自定义
|
||||
|
||||
### 文本小组件
|
||||
|
||||
- **文本内容**:自定义显示的文字内容
|
||||
- **样式设置**:
|
||||
- 字体大小、字体类型、字重
|
||||
- 文本颜色或渐变色
|
||||
- 文字阴影及模糊度
|
||||
|
||||
### 图片小组件
|
||||
|
||||
- **图片 URL**:输入远程图片的 URL
|
||||
- **尺寸设置**:自定义宽度和高度
|
||||
- **透明度**:调整图片透明度
|
||||
- **圆角**:调整图片圆角半径
|
||||
- **阴影效果**:添加阴影及调整阴影颜色和模糊度
|
||||
|
||||
## 开发
|
||||
|
||||
@ -42,6 +78,196 @@ npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── assets/ # 静态资源
|
||||
├── components/ # 组件
|
||||
│ └── config/ # 小组件配置面板组件
|
||||
│ ├── ClockConfig.vue
|
||||
│ ├── DateConfig.vue
|
||||
│ ├── TextConfig.vue
|
||||
│ └── ImageConfig.vue
|
||||
├── router/ # 路由配置
|
||||
├── utils/ # 工具函数
|
||||
│ └── configUtils.ts # 配置编码/解码工具
|
||||
├── views/ # 页面视图
|
||||
│ ├── HomeView.vue # 主页
|
||||
│ ├── ConfigView.vue # 配置页面
|
||||
│ └── PreviewView.vue# 预览页面
|
||||
├── widgets/ # 小组件实现
|
||||
│ ├── registry.ts # 小组件注册中心
|
||||
│ ├── ClockWidget.vue
|
||||
│ ├── DateWidget.vue
|
||||
│ ├── TextWidget.vue
|
||||
│ └── ImageWidget.vue
|
||||
├── App.vue # 应用入口组件
|
||||
└── main.ts # 应用入口文件
|
||||
```
|
||||
|
||||
## 实现原理
|
||||
|
||||
本项目基于 Vue 3 组合式 API 和 TypeScript 构建,核心实现包括:
|
||||
|
||||
1. **小组件系统**:每个小组件都是独立的 Vue 组件,通过 props 接收配置
|
||||
2. **配置系统**:每种小组件对应一个配置组件,使用 v-model 进行双向绑定
|
||||
3. **集中注册机制**:通过 `registry.ts` 统一管理所有小组件,便于扩展和维护
|
||||
4. **动态首页展示**:自动从注册表中读取小组件信息展示在首页,通过映射添加图标和描述
|
||||
5. **URL 参数传递**:使用 Base64 编码将配置数据序列化到 URL 中
|
||||
6. **预览模式**:通过 URL 参数判断是否处于纯预览模式,隐藏配置界面
|
||||
7. **响应式更新**:使用 Vue 的响应式系统实现配置变更时的实时预览
|
||||
|
||||
## 最近更新
|
||||
|
||||
- **界面全面中文化**:所有小组件界面、配置面板、提示文本已全部中文化
|
||||
- **时钟小组件增强**:时钟小组件支持显示日期功能,样式与时间保持一致
|
||||
- **动态首页改进**:首页自动读取注册的小组件列表,支持内容滚动
|
||||
- **小组件注册中心**:新增 `registry.ts` 作为小组件注册中心,统一管理
|
||||
- **响应式布局优化**:改进移动端和小屏幕适配体验
|
||||
|
||||
## 如何添加新小组件
|
||||
|
||||
要添加新的小组件,需要以下步骤:
|
||||
|
||||
1. **创建小组件组件**:在 `src/widgets/` 目录下创建新的 Vue 组件,如 `NewWidget.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="new-widget" :style="widgetStyle">
|
||||
<!-- 小组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
// 定义配置接口
|
||||
interface NewWidgetConfig {
|
||||
// 添加所需配置属性
|
||||
property1: string;
|
||||
property2: number;
|
||||
// ...
|
||||
}
|
||||
|
||||
// 定义 props(注意包含默认值和类型)
|
||||
const props = withDefaults(defineProps<{
|
||||
config: Partial<NewWidgetConfig>;
|
||||
}>(), {
|
||||
config: () => ({
|
||||
property1: 'default',
|
||||
property2: 0,
|
||||
// ...提供默认值
|
||||
})
|
||||
});
|
||||
|
||||
// 计算样式
|
||||
const widgetStyle = computed(() => {
|
||||
// 返回基于配置的样式对象
|
||||
return {
|
||||
// ...
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.new-widget {
|
||||
/* 基础样式 */
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
1. **创建配置组件**:在 `src/components/config/` 目录下创建对应的配置组件,如 `NewConfig.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="new-config">
|
||||
<h2>新小组件设置</h2>
|
||||
|
||||
<el-form label-position="top">
|
||||
<!-- 配置表单项 -->
|
||||
<el-form-item label="属性1">
|
||||
<el-input v-model="localConfig.property1" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="属性2">
|
||||
<el-slider v-model="localConfig.property2" :min="0" :max="100" show-input />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 更多配置项 -->
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue';
|
||||
|
||||
// 定义配置接口
|
||||
interface NewWidgetConfig {
|
||||
property1: string;
|
||||
property2: number;
|
||||
// ...
|
||||
}
|
||||
|
||||
// 定义 props
|
||||
const props = withDefaults(defineProps<{
|
||||
config: Partial<NewWidgetConfig>;
|
||||
}>(), {
|
||||
config: () => ({
|
||||
property1: 'default',
|
||||
property2: 0,
|
||||
// ...
|
||||
})
|
||||
});
|
||||
|
||||
// 定义 emit
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:config', config: NewWidgetConfig): void;
|
||||
}>();
|
||||
|
||||
// 本地配置用于双向绑定
|
||||
const localConfig = ref<NewWidgetConfig>({
|
||||
property1: 'default',
|
||||
property2: 0,
|
||||
// ...
|
||||
});
|
||||
|
||||
// 同步初始配置
|
||||
onMounted(() => {
|
||||
localConfig.value = { ...localConfig.value, ...props.config };
|
||||
});
|
||||
|
||||
// 监听本地变更并发送更新
|
||||
watch(localConfig, (newConfig) => {
|
||||
emit('update:config', { ...newConfig });
|
||||
}, { deep: true });
|
||||
</script>
|
||||
```
|
||||
|
||||
1. **注册新小组件**:在 `src/widgets/registry.ts` 中添加新小组件
|
||||
|
||||
```typescript
|
||||
// 导入新组件
|
||||
import NewWidget from './NewWidget.vue';
|
||||
import NewConfig from '../components/config/NewConfig.vue';
|
||||
|
||||
// 小组件注册表
|
||||
export const widgets = [
|
||||
// 现有小组件...
|
||||
{ label: '新小组件', value: 'new', component: NewWidget, configComponent: NewConfig },
|
||||
];
|
||||
|
||||
// 在 getDefaultConfig 函数中添加默认配置
|
||||
case 'new':
|
||||
return {
|
||||
property1: 'default',
|
||||
property2: 0,
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
添加完成后,新小组件会自动出现在首页的可用小组件列表中,并可在配置页面中使用。
|
||||
|
||||
## 故障排除
|
||||
|
||||
如果遇到与未定义属性相关的 TypeScript 错误,请确保:
|
||||
|
@ -41,24 +41,8 @@ import { CopyDocument } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { encodeConfig, decodeConfig } from '../utils/configUtils';
|
||||
|
||||
// 小组件组件及其配置
|
||||
import ClockWidget from '../widgets/ClockWidget.vue';
|
||||
import DateWidget from '../widgets/DateWidget.vue';
|
||||
import TextWidget from '../widgets/TextWidget.vue';
|
||||
import ImageWidget from '../widgets/ImageWidget.vue';
|
||||
|
||||
// 配置组件
|
||||
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: '时钟小组件', 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 },
|
||||
];
|
||||
// 导入小组件注册表
|
||||
import { widgets, getDefaultConfig } from '../widgets/registry';
|
||||
|
||||
const selectedWidget = ref('clock');
|
||||
const currentWidgetConfig = ref({});
|
||||
@ -76,66 +60,6 @@ const currentConfigComponent = computed(() => {
|
||||
return widget?.configComponent;
|
||||
});
|
||||
|
||||
// 为每种小组件类型设置默认配置
|
||||
const getDefaultConfig = (widgetType: string) => {
|
||||
switch (widgetType) {
|
||||
case 'clock':
|
||||
return {
|
||||
format: 'HH:mm:ss',
|
||||
color: '#ffffff',
|
||||
fontSize: 48,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
showSeconds: true
|
||||
};
|
||||
case 'date':
|
||||
return {
|
||||
format: 'YYYY-MM-DD',
|
||||
color: '#ffffff',
|
||||
fontSize: 32,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
showWeekday: true
|
||||
};
|
||||
case 'text':
|
||||
return {
|
||||
text: 'Sample Text',
|
||||
color: '#ffffff',
|
||||
fontSize: 32,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
};
|
||||
case 'image':
|
||||
return {
|
||||
imageUrl: '',
|
||||
width: 200,
|
||||
height: 200,
|
||||
opacity: 1,
|
||||
borderRadius: 0,
|
||||
shadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 10
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
// Handle widget type change
|
||||
const handleWidgetChange = () => {
|
||||
currentWidgetConfig.value = getDefaultConfig(selectedWidget.value);
|
||||
|
@ -32,35 +32,11 @@
|
||||
<h2>可用小组件</h2>
|
||||
|
||||
<div class="widget-list">
|
||||
<div class="widget-item">
|
||||
<div class="widget-icon">⏰</div>
|
||||
<div v-for="widget in widgets" :key="widget.value" class="widget-item">
|
||||
<div class="widget-icon">{{ widget.icon }}</div>
|
||||
<div class="widget-info">
|
||||
<h3>时钟小组件</h3>
|
||||
<p>显示当前时间,可自定义格式、样式和特效</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-item">
|
||||
<div class="widget-icon">📅</div>
|
||||
<div class="widget-info">
|
||||
<h3>日期小组件</h3>
|
||||
<p>显示当前日期,可自定义格式、样式和特效</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-item">
|
||||
<div class="widget-icon">📝</div>
|
||||
<div class="widget-info">
|
||||
<h3>文本小组件</h3>
|
||||
<p>显示文本,支持渐变、阴影、字体等自定义样式</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget-item">
|
||||
<div class="widget-icon">🖼️</div>
|
||||
<div class="widget-info">
|
||||
<h3>图片小组件</h3>
|
||||
<p>显示图片,可自定义大小、特效和位置</p>
|
||||
<h3>{{ widget.label }}</h3>
|
||||
<p>{{ widget.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,16 +52,58 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Setting, Document } from '@element-plus/icons-vue';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
// 导入已注册的小组件信息
|
||||
import { widgets as registeredWidgets } from '../widgets/registry';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 定义小组件项的类型
|
||||
interface WidgetItem {
|
||||
value: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const widgets = ref<WidgetItem[]>([]);
|
||||
|
||||
// 小组件图标映射
|
||||
const widgetIcons = {
|
||||
'clock': '⏰',
|
||||
'date': '📅',
|
||||
'text': '📝',
|
||||
'image': '🖼️',
|
||||
// 可以为其他小组件添加图标
|
||||
};
|
||||
|
||||
// 小组件描述映射
|
||||
const widgetDescriptions = {
|
||||
'clock': '显示当前时间,可自定义格式、样式和特效',
|
||||
'date': '显示当前日期,可自定义格式、样式和特效',
|
||||
'text': '显示文本,支持渐变、阴影、字体等自定义样式',
|
||||
'image': '显示图片,可自定义大小、特效和位置',
|
||||
// 可以为其他小组件添加描述
|
||||
};
|
||||
|
||||
// 初始化时加载小组件
|
||||
onMounted(() => {
|
||||
widgets.value = registeredWidgets.map((widget: any) => ({
|
||||
value: widget.value as string,
|
||||
label: widget.label as string,
|
||||
icon: widgetIcons[widget.value as keyof typeof widgetIcons] || '🔧', // 默认图标
|
||||
description: widgetDescriptions[widget.value as keyof typeof widgetDescriptions] || '自定义小组件'
|
||||
}));
|
||||
});
|
||||
|
||||
const goToConfig = () => {
|
||||
router.push('/config');
|
||||
};
|
||||
|
||||
const goToDoc = () => {
|
||||
// 在实际应用中,这里会跳转到文档页面
|
||||
window.open('https://github.com/yourusername/obs-overlay-widget', '_blank');
|
||||
window.open('https://github.com/hanxuanyu/obs-overlay-widget', '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -94,11 +112,15 @@ const goToDoc = () => {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f7fa;
|
||||
padding: 20px;
|
||||
/* 添加溢出滚动,确保内容可以完整显示 */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
/* 添加底部间距,确保页脚有足够空间 */
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -175,8 +197,10 @@ const goToDoc = () => {
|
||||
|
||||
.widget-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
/* 确保底部有足够的边距 */
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.widget-item {
|
||||
@ -185,6 +209,13 @@ const goToDoc = () => {
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
|
||||
/* 添加过渡效果 */
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.widget-item:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.widget-icon {
|
||||
|
81
src/widgets/registry.ts
Normal file
81
src/widgets/registry.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// 小组件组件及其配置
|
||||
import ClockWidget from './ClockWidget.vue';
|
||||
import DateWidget from './DateWidget.vue';
|
||||
import TextWidget from './TextWidget.vue';
|
||||
import ImageWidget from './ImageWidget.vue';
|
||||
|
||||
// 配置组件
|
||||
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';
|
||||
|
||||
// 小组件注册表
|
||||
export const widgets = [
|
||||
{ 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 },
|
||||
];
|
||||
|
||||
// 获取小组件默认配置
|
||||
export const getDefaultConfig = (widgetType: string) => {
|
||||
switch (widgetType) {
|
||||
case 'clock':
|
||||
return {
|
||||
format: 'HH:mm:ss',
|
||||
color: '#ffffff',
|
||||
fontSize: 48,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
showSeconds: true,
|
||||
showDate: false,
|
||||
dateFormat: 'YYYY-MM-DD'
|
||||
};
|
||||
case 'date':
|
||||
return {
|
||||
format: 'YYYY-MM-DD',
|
||||
color: '#ffffff',
|
||||
fontSize: 32,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
showWeekday: true
|
||||
};
|
||||
case 'text':
|
||||
return {
|
||||
text: 'Sample Text',
|
||||
color: '#ffffff',
|
||||
fontSize: 32,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Arial',
|
||||
textShadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 4,
|
||||
useGradient: false,
|
||||
gradientColors: ['#ff0000', '#0000ff'],
|
||||
};
|
||||
case 'image':
|
||||
return {
|
||||
imageUrl: '',
|
||||
width: 200,
|
||||
height: 200,
|
||||
opacity: 1,
|
||||
borderRadius: 0,
|
||||
shadow: false,
|
||||
shadowColor: 'rgba(0,0,0,0.5)',
|
||||
shadowBlur: 10
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user