---
name: add-screen-component
description: 在摸鱼低代码自助大屏中新增自定义组件,包括创建组件 Vue 文件、数据定义文件、配置面板文件,并在 ComUtils.ts 中注册枚举、在 index.ts 中注册导出。当用户提到"大屏新增组件"、"自助大屏添加组件"、"screen 新增组件"、"添加大屏自定义组件"时使用此 skill。
---
# 自助大屏新增自定义组件
## 目录结构
组件位于:`packages/nocode/src/screen/charts/coms/`
每个组件对应一个子目录,包含:
```
MfXxx/
├── index.vue # 组件展示逻辑
├── MfXxxConfig.vue # 样式/配置面板(可选)
├── MfXxxDataConfig.vue # 数据配置面板(可选)
└── mfXxx.data.ts # 组件数据定义(必须)
```
**新增文件约束:**
- 新建 `SKILL.md`、`.vue`、`.ts` 文件时统一保存为 **UTF-8 without BOM**
- 如果文件开头带 BOM,某些解析器会把 frontmatter 或源码首行识别失败
- 新增源码默认使用 ASCII;只有项目已有同类中文注释/文案且确有必要时再写中文
---
## Step 1:在 ComUtils.ts 中注册枚举
文件:`packages/nocode/src/screen/charts/coms/ComUtils.ts`
在 `ComType` 枚举末尾追加新类型:
```ts
MfXxx = "MfXxx",
```
**先检查再追加:**
- 先确认 `ComType` 里是否已经存在该枚举,避免重复定义
- 若仓库中已存在空目录 `MfXxx/`,不要假设组件已实现,要继续检查其中是否真的有 `index.vue`、`mfXxx.data.ts` 等源文件
---
## Step 2:创建 index.vue(组件展示)
```vue
```
**关键约定:**
- `defineOptions({ name: "MfXxx" })` name 必须与组件目录名一致
- props 固定两个:`chart: Chart` 和 `chartContain: ChartContain`
- 如需自定义事件,加 `emit("eventHandler", e)` 并在 `useChartEventHandle` 第三参数传入事件名数组
- 如需字体样式,使用 `getFontStyle(props.chart)` 获取计算样式
---
## Step 3:创建 MfXxxConfig.vue(样式配置面板,可选)
```vue
```
**常用配置子组件:**
- `` — 文字样式(字体/大小/颜色/对齐)
- `` — 颜色选择器(见下方详细说明)
- `` — 图标选择
- `` — 文本输入
- `` — 复选框
- `` + `` — 单选组
通过 `screenEditStore.getCurConfigComponent.chart.options.xxx` 读写配置,通过 `screenEditStore.getCurConfigComponent.chart.data.dataSet.xxx` 读写数据。
### MfishColorPicker 颜色选择器使用规范
大屏组件中所有涉及颜色配置的地方,统一使用 `MfishColorPicker` 组件,不要使用 `AInput` 等文本输入来代替。
**引入方式:**
```ts
import { MfishColorPicker } from "../../../../common";
```
**type 属性规则:**
- `type` 不设置(默认 `3`):同时支持单色和渐变色
- `type` 设置为 `1`:仅支持单色选择,适用于背景色、边框色、图标色等无需渐变的场景
- `type` 设置为 `2`:仅支持渐变色
**模板用法:**
```vue
```
**事件处理:**
`MfishColorPicker` 通过 `confirmChange` 和 `inputChange` 两个事件向外传递颜色值,回调函数签名为 `(displayedColor: string, hex: string)`。`displayedColor` 为 rgba 格式的完整颜色值(含透明度),`hex` 为十六进制颜色值。通常只需使用第一个参数。
```ts
function bgColorChange(val: string) {
bgColor.value = val;
updateOption("bgColor", val);
}
```
**完整示例(参考 MfTag、MfCarousel):**
```vue
```
---
## Step 4:创建 MfXxxDataConfig.vue(数据配置面板,仅数据驱动组件需要)
数据驱动组件(需要绑定 API 数据)才需要此文件。可参考 `MfDataTag/MfDataTagDataConfig.vue`。
### 数据配置参考选择规则
- **单值组件**:参考 `MfInput/MfInputDataConfig.vue`
- 例:输入框、数字框、单个状态值
- **选项数组组件**:参考 `MfSelect/MfSelectDataConfig.vue`、`MfSegmented/MfSegmentedDataConfig.vue`
- 例:下拉、分段器、轮播图、卡片列表
- **表格类组件**:参考 `MfTable/MfTableDataConfig.vue`
- 例:表格、滚动表格、多列数据展示
- **纯静态展示组件**:不要为了“形式统一”硬加数据面板
- 例:`MfPicture` 当前不支持动态绑定,不能把它当成动态数据组件模板
### 图片/轮播类组件的动态数据约定
如果组件本质上是“多张图片 + 可选文案”的数组数据,优先按数组模式设计:
```ts
defaultData: [
{ key: "", title: "", link: "" }
]
```
推荐字段含义:
- `key`: 本地文件 key 或直接图片 URL
- `title`: 图片标题
- `link`: 点击跳转链接(可选)
推荐 `defaultHeaders`:
```ts
{
key: { colName: "key", dataType: "string", rename: "Image key/url" },
title: { colName: "title", dataType: "string", rename: "Image title" },
link: { colName: "link", dataType: "string", rename: "Link" }
}
```
`initData` / `setDynamicData` 要求:
- 从 `result.list` 遍历生成数组,而不是只取第一条
- 字段映射通过 `chart.data.fields?.xxx` 读取
- 动态数据无有效结果时,回退到 `defaultData`
- 展示层同时兼容本地文件 key 与外部 URL
---
## Step 5:创建 mfXxx.data.ts(组件数据定义)
这是最核心的注册文件:
```ts
import { createAsyncComponent } from "@mfish/core/utils/factory/CreateAsyncComponent";
import { cloneDeep } from "lodash-es";
import { Chart, MfComponent } from "../../../common/Constants.ts";
import { comConfigDisplay, ComponentData, ComType, DisplayConfig } from "../ComUtils.ts";
import { shallowRef } from "vue";
/**
* @description: [组件描述]
* @author: mfish
* @date: [日期]
*/
function mfXxxCreate(chart: MfComponent) {
chart.chart.data.dataSet = cloneDeep(mfXxxData.defaultData);
chart.chart.data.headers = mfXxxData.defaultHeaders && mfXxxData.defaultHeaders();
chart.chart.data.limit = 1;
chart.chart.data.maxLimit = 1;
chart.chartContain.dropInfo.width = 200; // 初始宽度(像素)
chart.chartContain.dropInfo.height = 100; // 初始高度(像素)
return chart;
}
const getConfig = () => {
const config = cloneDeep(comConfigDisplay) as DisplayConfig;
// 不需要数据配置时:config.data.show = false;
config.style.payload.component = shallowRef(
createAsyncComponent(() => import("./MfXxxConfig.vue"), {
loading: true,
size: "large"
})
);
// 有数据配置时:
// config.data.payload.component = shallowRef(
// createAsyncComponent(() => import("./MfXxxDataConfig.vue"), { loading: true, size: "large" })
// );
return config;
};
const mfXxxData: ComponentData = {
type: ComType.MfXxx,
component: createAsyncComponent(() => import("./index.vue"), { loading: true }),
create: mfXxxCreate,
config: getConfig(),
defaultData: { /* 静态默认数据 */ },
defaultHeaders: () => {
return cloneDeep({
fieldName: { colName: "fieldName", dataType: "string", rename: "字段名" }
});
},
initData: (chart: Chart, data?: any) => {
// 处理动态数据加载逻辑,静态组件直接留空 () => {}
},
// 可选:自定义事件
customEvents: [{ name: "事件名", value: "eventKey" }]
};
export default mfXxxData;
```
**关键说明:**
- `type` 必须与 `ComType` 枚举值匹配
- `create` 中设置组件初始宽高和默认数据
- `config.data.show = false` 表示不显示数据配置 Tab
- 静态纯展示组件 `initData: () => {}` 即可
- 数据驱动组件需在 `initData` 中解析 API 返回数据并写入 `chart.data.dataSet`
---
## 自定义事件实现规则
新增组件如果需要支持大屏事件联动,按 `MfSelect` 的 `selectChange` 模式实现。
1. 在 `index.vue` 中注册自定义事件 key:
```ts
const { commonEvents, emitEvent } = useChartEventHandle(props.chart, undefined, ["eventKey"]);
```
多个事件时传入多个 key:
```ts
const { commonEvents, emitEvent } = useChartEventHandle(props.chart, undefined, ["cardChange", "cardClick"]);
```
2. 在交互回调中触发事件:
```ts
function eventHandler(payload: any) {
emitEvent("eventKey", payload);
}
```
数组/选项类组件应优先按索引返回原始数据项,参考 `MfSelect`:
```ts
import { getChartData } from "../../../common/ScreenUtils.ts";
function getItemData(index: number) {
const data = getChartData(props.chart);
return data?.length > index ? data[index] : props.chart.data?.dataSet?.[index];
}
function itemClick(index: number) {
emitEvent("itemClick", getItemData(index));
}
```
3. 在 `mfXxx.data.ts` 中声明可配置事件:
```ts
customEvents: [
{ name: "事件显示名称", value: "eventKey" }
]
```
约定:
- `value` 必须和 `useChartEventHandle`、`emitEvent` 使用的事件 key 完全一致。
- `name` 是配置面板展示名称,使用清晰中文,例如 `卡片点击`、`卡片切换`、`行点击`。
- 选项/数组/表格类组件的事件 payload 优先返回当前数据项,不要只返回 DOM 事件。
- 如果组件同时保留通用点击事件 `v-on="commonEvents"`,自定义点击事件可以并存;需要阻止冒泡时在模板上显式使用 `.stop`。
---
## Step 6:在 index.ts 注册(仅需全局导出时)
如果组件需要被其他地方直接 import 使用(如 `MfCombine`、`MfTag`),在 `index.ts` 中添加:
```ts
export { default as MfXxx } from "../../charts/coms/MfXxx/index.vue";
```
> 注意:`.data.ts` 文件通过 `import.meta.glob("./**/*.data.ts")` 自动扫描注册,无需手动在 `index.ts` 中添加。
**很重要:**
- 大部分新组件只需要补 `mfXxx.data.ts`,会被 `import.meta.glob("./**/*.data.ts", { eager: true })` 自动注册
- **不要默认修改** `packages/nocode/src/screen/charts/coms/index.ts`
- 只有当组件确实需要像 `MfCombine`、`MfTag` 那样被其他源码直接 `import` 时,才额外加 `export`
---
## 常见组件模式参考
| 模式 | 参考组件 | 特征 |
|------|----------|------|
| 纯静态文本/标签 | `MfTag` | `data.show=false`,`initData: ()=>{}` |
| 静态+可交互按钮 | `MfButton` | `data.show=false`,含 `customEvents` |
| 动态数据驱动 | `MfDataTag`、`MfScrollTable` | 有 `DataConfig.vue`,`initData` 解析数据 |
| 选项/数组驱动 | `MfSelect`、`MfSegmented` | `dataSet` 为数组,字段拖拽绑定多个属性 |
| ECharts 图表 | `MfBar`、`MfLine` | 使用 `useEchartsData()`,含 series/grid 配置 |
| DataV 装饰 | `MfBorder*`、`MfDecoration*` | 无数据,样式配置简单 |
---
## 完整流程 Checklist
```
- [ ] Step 1: ComUtils.ts 枚举中添加 MfXxx = "MfXxx"
- [ ] Step 2: 创建 MfXxx/index.vue
- [ ] Step 3: 创建 MfXxx/MfXxxConfig.vue(如有样式配置)
- [ ] Step 4: 创建 MfXxx/MfXxxDataConfig.vue(如有数据配置)
- [ ] Step 5: 创建 MfXxx/mfXxx.data.ts
- [ ] Step 6: index.ts 添加导出(如需全局引用)
- [ ] 验证:ComType.MfXxx 枚举值与 mfXxx.data.ts 中 type 一致
- [ ] 验证:组件 defineOptions.name 与目录名一致
- [ ] 验证:如果只是普通组件,是否已避免无意义修改 `charts/coms/index.ts`
- [ ] 验证:动态组件的 `initData` 是否真的按 `result.list` 做了字段映射
- [ ] 验证:图片类组件是否兼容 fileKey 和外部 URL
- [ ] 验证:新增文件是否为 UTF-8 without BOM
```