项目重构,增强扩展性

This commit is contained in:
hxuanyu 2025-06-27 10:24:02 +08:00
parent c1dfe48a06
commit 925c8ad6bc
20 changed files with 293 additions and 210 deletions

View File

@ -83,12 +83,7 @@ npm run preview
``` ```
src/ src/
├── assets/ # 静态资源 ├── assets/ # 静态资源
├── components/ # 组件 ├── components/ # 通用组件
│ └── config/ # 小组件配置面板组件
│ ├── ClockConfig.vue
│ ├── DateConfig.vue
│ ├── TextConfig.vue
│ └── ImageConfig.vue
├── router/ # 路由配置 ├── router/ # 路由配置
├── utils/ # 工具函数 ├── utils/ # 工具函数
│ └── configUtils.ts # 配置编码/解码工具 │ └── configUtils.ts # 配置编码/解码工具
@ -96,8 +91,91 @@ src/
│ ├── HomeView.vue # 主页 │ ├── HomeView.vue # 主页
│ ├── ConfigView.vue # 配置页面 │ ├── ConfigView.vue # 配置页面
│ └── PreviewView.vue# 预览页面 │ └── PreviewView.vue# 预览页面
├── widgets/ # 小组件实现 ├── widgets/ # 小组件模块
│ ├── registry.ts # 小组件注册中心 │ ├── clock/ # 时钟小组件模块
│ │ ├── index.ts # 模块导出
│ │ ├── types.ts # 类型定义
│ │ ├── Widget.vue # 小组件组件
│ │ └── Config.vue # 配置组件
│ ├── date/ # 日期小组件模块
│ ├── text/ # 文本小组件模块
│ ├── image/ # 图片小组件模块
│ └── registry.ts # 小组件注册中心
```
## 小组件开发指南
### 创建新小组件
1. **创建小组件文件夹**:在 `src/widgets/` 下创建新的文件夹,如 `newwidget/`
2. **创建必要文件**
- `types.ts` - 定义配置接口和默认配置
- `Widget.vue` - 小组件显示组件
- `Config.vue` - 小组件配置组件
- `index.ts` - 模块导出文件
3. **实现类型定义**types.ts
```typescript
// 小组件配置接口
export interface NewWidgetConfig {
// 配置属性定义
}
// 默认配置
export const defaultConfig: NewWidgetConfig = {
// 默认值
};
```
4. **实现小组件组件**Widget.vue
```vue
<template>
<!-- 小组件UI -->
</template>
<script setup lang="ts">
import type { NewWidgetConfig } from './types';
// 组件逻辑
</script>
```
5. **实现配置组件**Config.vue
```vue
<template>
<!-- 配置UI -->
</template>
<script setup lang="ts">
import type { NewWidgetConfig } from './types';
// 配置逻辑
</script>
```
6. **导出模块**index.ts
```typescript
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
export default {
label: '新小组件',
value: 'newwidget',
component: Widget,
configComponent: Config,
getDefaultConfig: () => defaultConfig
};
```
7. **注册小组件**:在 `registry.ts` 中导入并添加到 `widgets` 数组
### 架构优势
- **模块化设计**:每个小组件独立成模块,包含所有相关代码
- **类型安全**TypeScript 提供完整的类型检查和智能提示
- **可扩展性**:添加新小组件无需修改核心逻辑,只需创建新模块并注册
- **代码组织**:相关代码集中在同一文件夹,便于维护和理解
- **零侵入**:新增小组件不会影响现有小组件和核心功能
│ ├── ClockWidget.vue │ ├── ClockWidget.vue
│ ├── DateWidget.vue │ ├── DateWidget.vue
│ ├── TextWidget.vue │ ├── TextWidget.vue

View File

@ -11,25 +11,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { decodeConfig } from '../utils/configUtils'; import { decodeConfig } from '../utils/configUtils';
import { widgets } from '../widgets/registry';
// //
import ClockWidget from '../widgets/ClockWidget.vue'; const widgetRegistry: Record<string, any> = {};
import DateWidget from '../widgets/DateWidget.vue'; widgets.forEach(widget => {
import TextWidget from '../widgets/TextWidget.vue'; widgetRegistry[widget.value] = widget.component;
import ImageWidget from '../widgets/ImageWidget.vue'; });
//
const widgetRegistry = {
'clock': ClockWidget,
'date': DateWidget,
'text': TextWidget,
'image': ImageWidget
};
const widgetType = ref(''); const widgetType = ref('');
const widgetConfig = ref({}); const widgetConfig = ref({});
const widgetComponent = computed(() => { const widgetComponent = computed(() => {
return widgetRegistry[widgetType.value as keyof typeof widgetRegistry]; return widgetRegistry[widgetType.value];
}); });
onMounted(() => { onMounted(() => {

View File

@ -99,6 +99,8 @@
</el-form-item> </el-form-item>
</template> </template>
<el-divider>预设样式</el-divider>
<el-form-item> <el-form-item>
<el-button-group> <el-button-group>
<el-button type="primary" @click="applyPreset('modern')">现代风格</el-button> <el-button type="primary" @click="applyPreset('modern')">现代风格</el-button>
@ -113,23 +115,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import type { ClockConfig } from './types';
// props
interface ClockConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showSeconds: boolean;
showDate: boolean;
dateFormat: string;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -8,23 +8,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'; import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { ClockConfig } from './types';
// Define props interface
interface ClockConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showSeconds: boolean;
showDate: boolean;
dateFormat: string;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -0,0 +1,11 @@
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
export default {
label: '时钟小组件',
value: 'clock',
component: Widget,
configComponent: Config,
getDefaultConfig: () => defaultConfig
};

View File

@ -0,0 +1,33 @@
// 时钟小组件配置接口
export interface ClockConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showSeconds: boolean;
showDate: boolean;
dateFormat: string;
}
// 时钟小组件默认配置
export const defaultConfig: ClockConfig = {
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'
};

View File

@ -95,21 +95,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import type { DateConfig } from './types';
// Define props interface for config
interface DateConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showWeekday: boolean;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -7,21 +7,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'; import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type { DateConfig } from './types';
// Define props interface
interface DateConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showWeekday: boolean;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

11
src/widgets/date/index.ts Normal file
View File

@ -0,0 +1,11 @@
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
export default {
label: '日期小组件',
value: 'date',
component: Widget,
configComponent: Config,
getDefaultConfig: () => defaultConfig
};

29
src/widgets/date/types.ts Normal file
View File

@ -0,0 +1,29 @@
// 日期小组件配置接口
export interface DateConfig {
format: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
showWeekday: boolean;
}
// 日期小组件默认配置
export const defaultConfig: DateConfig = {
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
};

View File

@ -59,18 +59,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import type { ImageConfig } from './types';
// Define props interface for config
interface ImageConfig {
imageUrl: string;
width: number;
height: number;
opacity: number;
borderRadius: number;
shadow: boolean;
shadowColor: string;
shadowBlur: number;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -14,18 +14,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import type { ImageConfig } from './types';
// Define props interface
interface ImageConfig {
imageUrl: string;
width: number;
height: number;
opacity: number;
borderRadius: number;
shadow: boolean;
shadowColor: string;
shadowBlur: number;
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -0,0 +1,11 @@
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
export default {
label: '图片小组件',
value: 'image',
component: Widget,
configComponent: Config,
getDefaultConfig: () => defaultConfig
};

View File

@ -0,0 +1,23 @@
// 图片小组件配置接口
export interface ImageConfig {
imageUrl: string;
width: number;
height: number;
opacity: number;
borderRadius: number;
shadow: boolean;
shadowColor: string;
shadowBlur: number;
}
// 图片小组件默认配置
export const defaultConfig: ImageConfig = {
imageUrl: '',
width: 200,
height: 200,
opacity: 1,
borderRadius: 0,
shadow: false,
shadowColor: 'rgba(0,0,0,0.5)',
shadowBlur: 10
};

View File

@ -1,81 +1,23 @@
// 小组件组件及其配置 // 导入各个小组件模块
import ClockWidget from './ClockWidget.vue'; import ClockWidget from './clock';
import DateWidget from './DateWidget.vue'; import DateWidget from './date';
import TextWidget from './TextWidget.vue'; import TextWidget from './text';
import ImageWidget from './ImageWidget.vue'; import ImageWidget from './image';
// 配置组件
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 = [ export const widgets = [
{ label: '时钟小组件', value: 'clock', component: ClockWidget, configComponent: ClockConfig }, ClockWidget,
{ label: '日期小组件', value: 'date', component: DateWidget, configComponent: DateConfig }, DateWidget,
{ label: '文本小组件', value: 'text', component: TextWidget, configComponent: TextConfig }, TextWidget,
{ label: '图片小组件', value: 'image', component: ImageWidget, configComponent: ImageConfig }, ImageWidget,
]; ];
// 获取小组件默认配置 // 获取小组件默认配置
export const getDefaultConfig = (widgetType: string) => { export const getDefaultConfig = (widgetType: string) => {
switch (widgetType) { for (const widget of widgets) {
case 'clock': if (widget.value === widgetType) {
return { return widget.getDefaultConfig();
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 {};
} }
return {};
}; };

View File

@ -85,20 +85,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from 'vue'; import { ref, watch, onMounted } from 'vue';
import type { TextConfig } from './types';
// Define props interface for config
interface TextConfig {
text: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

View File

@ -6,20 +6,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import type { TextConfig } from './types';
// Define props interface
interface TextConfig {
text: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
}
// Define props with default values // Define props with default values
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{

11
src/widgets/text/index.ts Normal file
View File

@ -0,0 +1,11 @@
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
export default {
label: '文本小组件',
value: 'text',
component: Widget,
configComponent: Config,
getDefaultConfig: () => defaultConfig
};

27
src/widgets/text/types.ts Normal file
View File

@ -0,0 +1,27 @@
// 文本小组件配置接口
export interface TextConfig {
text: string;
color: string;
fontSize: number;
fontWeight: string;
fontFamily: string;
textShadow: boolean;
shadowColor: string;
shadowBlur: number;
useGradient: boolean;
gradientColors: string[];
}
// 文本小组件默认配置
export const defaultConfig: TextConfig = {
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']
};

20
src/widgets/types.ts Normal file
View File

@ -0,0 +1,20 @@
import type { Component } from 'vue';
// 基础小组件配置接口
export interface BaseWidgetConfig {
[key: string]: any;
}
// 小组件注册信息接口
export interface WidgetRegistration {
label: string;
value: string;
component: Component;
configComponent: Component;
getDefaultConfig: () => BaseWidgetConfig;
}
// 小组件模块导出接口
export interface WidgetModule {
registration: WidgetRegistration;
}