From 03a19beafa8590c026d883f42519d0cd9054324a Mon Sep 17 00:00:00 2001 From: hxuanyu <2252193204@qq.com> Date: Thu, 26 Jun 2025 00:25:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 242 +++++++++++++++++++++++++++++++++++++-- src/views/ConfigView.vue | 80 +------------ src/views/HomeView.vue | 91 ++++++++++----- src/widgets/registry.ts | 81 +++++++++++++ 4 files changed, 378 insertions(+), 116 deletions(-) create mode 100644 src/widgets/registry.ts diff --git a/README.md b/README.md index 7a81ddf..174ce55 100644 --- a/README.md +++ b/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 + + + + + +``` + +1. **创建配置组件**:在 `src/components/config/` 目录下创建对应的配置组件,如 `NewConfig.vue` + +```vue + + + +``` + +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 错误,请确保: diff --git a/src/views/ConfigView.vue b/src/views/ConfigView.vue index 496bead..2399876 100644 --- a/src/views/ConfigView.vue +++ b/src/views/ConfigView.vue @@ -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); diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index f1cb823..9aea0a3 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -32,35 +32,11 @@

可用小组件

-
-
+
+
{{ widget.icon }}
-

时钟小组件

-

显示当前时间,可自定义格式、样式和特效

-
-
- -
-
📅
-
-

日期小组件

-

显示当前日期,可自定义格式、样式和特效

-
-
- -
-
📝
-
-

文本小组件

-

显示文本,支持渐变、阴影、字体等自定义样式

-
-
- -
-
🖼️
-
-

图片小组件

-

显示图片,可自定义大小、特效和位置

+

{{ widget.label }}

+

{{ widget.description }}

@@ -76,16 +52,58 @@ @@ -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 { diff --git a/src/widgets/registry.ts b/src/widgets/registry.ts new file mode 100644 index 0000000..4fe467b --- /dev/null +++ b/src/widgets/registry.ts @@ -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 {}; + } +};