项目架构调整,对组件进一步封装化

This commit is contained in:
hxuanyu 2025-06-27 14:24:16 +08:00
parent e571385d19
commit 5af0b3396e
9 changed files with 287 additions and 56 deletions

View File

@ -52,50 +52,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Setting, Document } from '@element-plus/icons-vue'; import { Setting, Document } from '@element-plus/icons-vue';
import { ref, onMounted } from 'vue';
// //
import { widgets as registeredWidgets } from '../widgets/registry'; import { getWidgetItems } from '../widgets/registry';
const router = useRouter(); const router = useRouter();
// //
interface WidgetItem { const widgets = getWidgetItems();
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 = () => { const goToConfig = () => {
router.push('/config'); router.push('/config');

184
src/widgets/TEMPLATE.md Normal file
View File

@ -0,0 +1,184 @@
# 新建小组件模板
这是一个模板,用于快速创建新的小组件。
## 步骤
### 1. 创建小组件目录结构
```
src/widgets/[widget-name]/
├── index.ts # 小组件注册入口
├── types.ts # 类型定义和默认配置
├── Widget.vue # 小组件显示组件
└── Config.vue # 小组件配置组件
```
### 2. 实现 types.ts
```typescript
import type { BaseWidgetConfig } from '../types';
// 小组件配置接口
export interface YourWidgetConfig extends BaseWidgetConfig {
// 定义你的配置属性
text: string;
color: string;
fontSize: number;
// ... 其他配置
}
// 默认配置
export const defaultConfig: YourWidgetConfig = {
text: 'Hello World',
color: '#ffffff',
fontSize: 24,
// ... 其他默认值
};
```
### 3. 实现 Widget.vue
```vue
<template>
<div class="your-widget" :style="widgetStyle">
{{ config.text }}
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { YourWidgetConfig } from './types';
// 接收配置props
const props = defineProps<{
config: YourWidgetConfig;
}>();
// 计算样式
const widgetStyle = computed(() => ({
color: props.config.color,
fontSize: props.config.fontSize + 'px',
// ... 其他样式
}));
</script>
<style scoped>
.your-widget {
/* 基础样式 */
}
</style>
```
### 4. 实现 Config.vue
```vue
<template>
<div class="config-panel">
<el-form :model="config" label-width="120px">
<el-form-item label="文本内容">
<el-input v-model="config.text" @input="updateConfig" />
</el-form-item>
<el-form-item label="文字颜色">
<el-color-picker v-model="config.color" @change="updateConfig" />
</el-form-item>
<el-form-item label="字体大小">
<el-slider
v-model="config.fontSize"
:min="12"
:max="72"
@change="updateConfig"
/>
</el-form-item>
<!-- 其他配置项... -->
</el-form>
</div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue';
import type { YourWidgetConfig } from './types';
// 接收配置和更新函数
const props = defineProps<{
modelValue: YourWidgetConfig;
}>();
const emit = defineEmits<{
'update:modelValue': [config: YourWidgetConfig];
}>();
// 本地配置状态
const config = reactive({ ...props.modelValue });
// 监听外部配置变化
watch(() => props.modelValue, (newValue) => {
Object.assign(config, newValue);
}, { deep: true });
// 更新配置
const updateConfig = () => {
emit('update:modelValue', { ...config });
};
</script>
<style scoped>
.config-panel {
padding: 20px;
}
</style>
```
### 5. 实现 index.ts
```typescript
import Widget from './Widget.vue';
import Config from './Config.vue';
import { defaultConfig } from './types';
import { createWidget } from '../createWidget';
export default createWidget({
label: '你的小组件',
value: 'your-widget',
icon: '🎯', // 选择合适的emoji图标
description: '这是你的小组件的描述',
component: Widget,
configComponent: Config,
defaultConfig
});
```
### 6. 在 registry.ts 中注册
```typescript
// 在 src/widgets/registry.ts 中添加导入
import YourWidget from './your-widget';
// 在小组件注册表中添加
export const widgets: WidgetRegistration[] = [
ClockWidget,
DateWidget,
TextWidget,
ImageWidget,
WeatherWidget,
YourWidget, // 添加你的小组件
];
```
## 完成!
现在你的新小组件就会自动出现在:
- 首页的小组件列表中
- 配置页面的小组件选择器中
- 预览页面中可以显示
## 最佳实践
1. **命名规范**: 使用kebab-case命名目录和文件
2. **图标选择**: 选择有意义的emoji作为图标
3. **配置项**: 提供合理的默认值,让小组件开箱即用
4. **样式**: 使用CSS变量和计算属性让样式可配置
5. **类型安全**: 充分利用TypeScript的类型检查

View File

@ -1,11 +1,14 @@
import Widget from './Widget.vue'; import Widget from './Widget.vue';
import Config from './Config.vue'; import Config from './Config.vue';
import { defaultConfig } from './types'; import { defaultConfig } from './types';
import { createWidget } from '../createWidget';
export default { export default createWidget({
label: '时钟小组件', label: '时钟小组件',
value: 'clock', value: 'clock',
icon: '⏰',
description: '显示当前时间,可自定义格式、样式和特效',
component: Widget, component: Widget,
configComponent: Config, configComponent: Config,
getDefaultConfig: () => defaultConfig defaultConfig
}; });

View File

@ -0,0 +1,47 @@
import type { Component } from 'vue';
import type { WidgetRegistration, BaseWidgetConfig } from './types';
/**
*
*
* @param options
* @returns
*/
export function createWidget(options: {
/** 小组件显示名称 */
label: string;
/** 小组件唯一标识符 */
value: string;
/** 小组件图标emoji 或图标字符) */
icon: string;
/** 小组件描述 */
description: string;
/** 小组件组件 */
component: Component;
/** 小组件配置组件 */
configComponent: Component;
/** 默认配置 */
defaultConfig: BaseWidgetConfig;
}): WidgetRegistration {
return {
label: options.label,
value: options.value,
icon: options.icon,
description: options.description,
component: options.component,
configComponent: options.configComponent,
getDefaultConfig: () => options.defaultConfig
};
}
/**
*
*
* @param defaultConfig
* @returns
*/
export function createWidgetConfig<T extends BaseWidgetConfig>(defaultConfig: T) {
return {
defaultConfig
};
}

View File

@ -1,11 +1,14 @@
import Widget from './Widget.vue'; import Widget from './Widget.vue';
import Config from './Config.vue'; import Config from './Config.vue';
import { defaultConfig } from './types'; import { defaultConfig } from './types';
import { createWidget } from '../createWidget';
export default { export default createWidget({
label: '日期小组件', label: '日期小组件',
value: 'date', value: 'date',
icon: '📅',
description: '显示当前日期,可自定义格式、样式和特效',
component: Widget, component: Widget,
configComponent: Config, configComponent: Config,
getDefaultConfig: () => defaultConfig defaultConfig
}; });

View File

@ -1,11 +1,14 @@
import Widget from './Widget.vue'; import Widget from './Widget.vue';
import Config from './Config.vue'; import Config from './Config.vue';
import { defaultConfig } from './types'; import { defaultConfig } from './types';
import { createWidget } from '../createWidget';
export default { export default createWidget({
label: '图片小组件', label: '图片小组件',
value: 'image', value: 'image',
icon: '🖼️',
description: '显示图片,可自定义大小、特效和位置',
component: Widget, component: Widget,
configComponent: Config, configComponent: Config,
getDefaultConfig: () => defaultConfig defaultConfig
}; });

View File

@ -4,14 +4,28 @@ import DateWidget from './date';
import TextWidget from './text'; import TextWidget from './text';
import ImageWidget from './image'; import ImageWidget from './image';
// 导入类型定义
import type { WidgetRegistration, WidgetItem } from './types';
// 小组件注册表 // 小组件注册表
export const widgets = [ export const widgets: WidgetRegistration[] = [
ClockWidget, ClockWidget,
DateWidget, DateWidget,
TextWidget, TextWidget,
ImageWidget, ImageWidget,
]; ];
// 获取小组件显示信息列表(用于 UI 展示)
export const getWidgetItems = (): WidgetItem[] => {
return widgets.map(widget => ({
value: widget.value,
label: widget.label,
icon: widget.icon,
description: widget.description
}));
};
// 获取小组件默认配置 // 获取小组件默认配置
export const getDefaultConfig = (widgetType: string) => { export const getDefaultConfig = (widgetType: string) => {
for (const widget of widgets) { for (const widget of widgets) {
@ -21,3 +35,8 @@ export const getDefaultConfig = (widgetType: string) => {
} }
return {}; return {};
}; };
// 根据类型获取小组件注册信息
export const getWidget = (widgetType: string): WidgetRegistration | undefined => {
return widgets.find(widget => widget.value === widgetType);
};

View File

@ -1,11 +1,14 @@
import Widget from './Widget.vue'; import Widget from './Widget.vue';
import Config from './Config.vue'; import Config from './Config.vue';
import { defaultConfig } from './types'; import { defaultConfig } from './types';
import { createWidget } from '../createWidget';
export default { export default createWidget({
label: '文本小组件', label: '文本小组件',
value: 'text', value: 'text',
icon: '📝',
description: '显示文本,支持渐变、阴影、字体等自定义样式',
component: Widget, component: Widget,
configComponent: Config, configComponent: Config,
getDefaultConfig: () => defaultConfig defaultConfig
}; });

View File

@ -9,12 +9,17 @@ export interface BaseWidgetConfig {
export interface WidgetRegistration { export interface WidgetRegistration {
label: string; label: string;
value: string; value: string;
icon: string;
description: string;
component: Component; component: Component;
configComponent: Component; configComponent: Component;
getDefaultConfig: () => BaseWidgetConfig; getDefaultConfig: () => BaseWidgetConfig;
} }
// 小组件模块导出接口 // 小组件列表项接口(用于 UI 显示)
export interface WidgetModule { export interface WidgetItem {
registration: WidgetRegistration; value: string;
label: string;
icon: string;
description: string;
} }