# 插件规范
⚠️注意:默认示例项目创建请参考初始化项目
# 生命周期
# 目录结构
插件开发工程目录结构
.
├── main.json (插件描述文件)
├── public (公共目录)
│ └── icon.png (插件图标)
├── src
│ ├── index.ts (项目入口)
│ ├── views
│ │ ├── PowerButton.vue (插件示例)
│ │ ├── CustomAttrsParserDemoView.vue (自定义插件解析器的示例)
│ │ └── assets (资源目录)
│ │ ├── btn_off.webp
│ │ └── btn_on.webp
│ ├── attrs
│ │ └── Switch.vue (自定义插件解析器)
│ ├── config
│ │ ├── index.ts (插件配置声明)
│ │ └── components
│ │ └── Servers.vue (自定义配置组件(可删除))
│ ├── hooks
│ │ └── useVuex.ts (vuex 辅助工具)
│ ├── locales (国际化翻译,需要 ccs-pro 2.1.0+ 和 sccs 0.4.0+)
│ │ ├── en.js (英文翻译表)
│ │ ├── zh_CN.js (中文翻译表)
│ │ └── i18n.ts (国际化翻译辅助工具)
│ ├── store
│ │ └── index.ts (vuex 仓库)
│ ├── global.d.ts (数据结构定义)
│ └── vue.d.ts (数据结构定义)
├── package.json (项目描述文件)
└── tsconfig.json (TS 配置)
# 关键文件/目录说明
文件/目录 | 类型 | 说明 |
---|---|---|
main.json | 文件 | 插件的描文件,用于声明插件的 ID,名称,版本、描述、图标、入口、样式等相关信息。 |
public/ | 目录 | 公共目录,用于存储一些公共资源文件,默认会放置一个图标文件。 |
src/ | 目录 | 项目目录,相关代码和资源。 |
src/index.ts | 文件 | 插件的入口,用于声明和注册相关组件、数据、配置、以及接收生命周期回调。 |
src/views/PowerButton.vue | 文件 | 默认的示例组件。 |
src/views/CustomAttrsParserDemoView.vue | 文件 | 使用插件解析器的示例。 |
src/views/assets/ | 目录 | 资源文件目录,可以修改到其它位置。 |
src/attrs/Switch.vue | 文件 | 插件解析器示例 |
src/config/index.ts | 文件 | 插件配置声明。 |
src/config/components/ | 目录 | 自定义的配置可视化组件,用于解析编辑默认类型不支持的条目。 |
src/store/index.ts | 文件 | 插件数据池(vuex)。 |
src/hooks/useVuex.ts | 文件 | 针对于插件的 vuex 封装,用于处理插件数据池的相关数据。 |
src/global.d.ts | 文件 | 数据结构定义,一般情况下无需调整。 |
src/vue.d.ts | 文件 | 数据结构定义,一般情况下无需调整。 |
package.json | 文件 | 项目描述文件。 |
tsconfig.json | 文件 | TS 配置。 |
src/locales/ | 目录 | 国际化翻译文件夹(需要 ccs-pro 2.1.0 及 sccs 0.4.0 以上版本) |
src/locales/i18n.ts | 文件 | 国际化翻译辅助工具(需要 ccs-pro 2.1.0 及 sccs 0.4.0 以上版本) |
src/locales/en.js | 文件 | 英文翻译表(需要 ccs-pro 2.1.0 及 sccs 0.4.0 以上版本) |
src/locales/zh_CN.js | 文件 | 中文翻译表(需要 ccs-pro 2.1.0 及 sccs 0.4.0 以上版本) |
# 描述文件
插件结构中最基本的描述信息是 main.json
,其基本结构如下:
{
"id": "demo",
"version": "1.0.0",
"icon": "icon.png",
"name": "未命名插件",
"description": "默认描述",
"entry": [
"index.js"
],
"style": [
"index.css"
]
}
# 描述文件字段
字段 | 类型 | 说明 |
---|---|---|
id | string | 插件唯一标识符,可以在初始化项目时自动生成,也可以手动指定。 |
version | string | 插件版本号,插件发布新版本时,注意修改此处的版本号。 |
icon | string | 插件图标。 |
name | string | 插件名称。 |
description | string | 插件描述。 |
entry | string[] | 插件入口,一般情况下请保持默认。 |
style | string[] | 插件样式表,一般情况下请保持默认。 |
# 插件入口
插件整体的入口是 index.ts
import PowerButton from './views/PowerButton.vue';
import CustomAttrsParserDemoView from './views/CustomAttrsParserDemoView.vue';
import Switch from './attrs/Switch.vue'
import { Store } from 'vuex';
import config from '@/config';
import store from '@/store';
import main from '@main';
export default {
...main,
elements: [PowerButton, CustomAttrsParserDemoView],
// 自定义插件属性解析器(可删除)
attrsComponents: { 'plg-switch': Switch },
// 插件数据池(可删除)
stores: [store],
config: config,
// 导入插件时调用
onInstall({ store }: { store: Store<any> }) {},
// 卸载插件时调用
onUninstall(_: { store: Store<any> }) {},
// 配置变化时调用
onConfigChanged({ config, store }: { config: any; store: Store<any> }) {
store.commit(main.id + '#store/setPrefix', config.prefix);
},
};
⚠️注意:在 export 中引用
...main
是为了统一使用main.json
中定义的字段,一般情况下保持不变即可。
# 插件入口字段
字段 | 说明 |
---|---|
elements | 插件的组件,会显示在组件面板中,可以添加到页面,需要符合插件组件规范。 |
attrsComponents | 自定义组件属性解析器,在插件组件的属性面板中使用,需要符合插件自定义属性解析器规范 |
stores | 插件数据池,插件加载时会自动注册,需要符合插件数据池规范。 |
config | 插件配置,支持自定义数据解析器,需要符合插件配置规范。 |
onInstall | 插件加载时回调,详情参见生命周期。 |
onUninstall | 插件卸载时回调,详情参见生命周期。 |
onConfigChanged | 插件配置加载/变化时回调接口,详情参见生命周期。 |
# 插件数据池规范
⚠️注意:
插件数据池使用非强制要求,如果需要使用,请按照 vuex 规范,如果不需要使用,可以移除。
当然,也可以使用其他数据传递方式,例如:
provider & inject
以及自定义 hook 实现数据共享。
/src/store/index.ts
export default {
name: 'store',
namespaced: true,
state: {
power: 'off',
prefix: '',
},
getters: {},
mutations: {
changePower: (state, { power }) => {
state.power = power;
},
setPrefix: (state, prefix) => {
state.prefix = prefix;
},
},
actions: {
switchPower: ({ state, commit }) => {
// 此处可以与服务器通信,同步状态
if (state.power === 'on') {
commit('changePower', { power: 'off' });
} else {
commit('changePower', { power: 'on' });
}
},
},
};
数据池基本遵照 vuex 4.x 的规范,详情请参考 vuex (opens new window),需要注意的是,导出的数据池里面应该包含 name 属性,该属性在后续使用中有重要的作用。
# 插件组件规范
下面是最基本的组件格式需求,相比于标准 vue 组件,需要导出一个 startup
属性并符合插件要求。
<template>
<div style="width: 100%; height: 100%; background-color: red"></div>
</template>
<script lang="ts" setup>
// 此处写 vue3 的逻辑代码
</script>
<script lang="ts">
export default {
startup: {
title: '测测View',
icon: '',
init: {
type: 'demo-view',
props: {
frame: { y: 0, x: 0, width: 100, height: 100 },
attrs: {},
},
},
schema: {
attrs: [],
},
},
};
</script>
# startup 字段说明
字段 | 类型 | 说明 |
---|---|---|
title | string | 插件显示名称。 |
icon | string | 插件预览图标。 |
init | object | 插件初始化结构。 |
init.type | string | 插件类型,和插件中其他组件的 type 不可重复。 |
init.props | object | 插件属性。 |
init.props.frame | object | 插件默认大小。 |
init.props.attrs | object | 插件自定义的可配置属性,会展示在编辑器右侧属性面板上。 |
schema | object | 属性辅助解释器。 |
schema.attrs | object | attrs 属性辅助解释器。 |
插件自定义 attrs 规范请参考附录一:插件可编辑属性格式定义
较为完整的插件组件示例,请参考附录二:完整插件组件示例
也可以通过 sccs
工具创建一个项目后参考其中的示例组件。
# 插件配置规范
插件配置是声明插件配置属性的方式,插件声明的配置信息会以可视化的形式展现在插件的配置菜单中,一个默认的插件配置是这样的:
⚠️注意:其中自定义解析器是可以不提供的,如果没有这方面的需求,可以不提供自定义解析器,直接使用默认解析器即可。
import Servers from "./components/Servers.vue";
// config 原始数据
const data: any = {
prefix: "插件配置",
myColor: "",
servers: [
{
type: "server",
url: "http://127.0.0.1:12409",
username: "",
password: ""
}
]
};
// 数据结构声明
const schema: any = [
{
component: "card",
props: {
header: "基本信息"
},
formProps: {},
fields: [
{
name: "prefix",
component: "input",
formProps: {
label: "前缀:"
},
inputProps: {}
},
{
name: "myColor",
component: "color-picker",
formProps: {
label: "颜色:"
},
inputProps: {}
},
{
name: "servers",
component: "servers",
formProps: {
label: "服务器地址:"
},
inputProps: {}
}
]
}
];
// 自定义的数据解析器
const components = {
servers: Servers
};
// 导出相关信息
export default {
data,
schema,
components
};
自定义解析器(可选):
<template>
<div>
<div v-if="!props.modelValue || !Array.isArray(props.modelValue)" style="color: red">数据类型错误</div>
<div v-else-if="props.modelValue.length <= 0">
<ElButton style="width: 100%" @click="addServer">添加服务器</ElButton>
</div>
<template v-else>
<div class="header">
<span style="width: 40%">服务器地址</span>
<span style="width: 25%">用户名</span>
<span style="width: 25%">密码</span>
<span style="width: 10%">删除</span>
</div>
<div v-for="(model, index) of props.modelValue" style="width: 100%; padding: 1px 0">
<ElInput v-model="model.url" style="width: 40%" size="small"></ElInput>
<ElInput v-model="model.username" style="width: 25%" size="small"></ElInput>
<ElInput v-model="model.password" style="width: 25%" size="small"></ElInput>
<icon name="delete" width="10" height="22" style="width: 10%" class="delete" @click="del(index)"></icon>
</div>
<ElButton style="width: 100%" @click="addServer">添加服务器</ElButton>
</template>
</div>
</template>
<script setup lang="ts">
import { ElButton, ElInput } from "element-plus";
const props = defineProps(["modelValue", "options"]);
const defaultOptions = [{ label: "v3pro", value: "v3pro" }];
const options = props.options || defaultOptions;
function addServer() {
props.modelValue.push({
url: "",
username: "",
password: ""
});
}
function del(index: number) {
props.modelValue.splice(index, 1);
}
</script>
<style scoped lang="less">
.header {
width: 100%;
span {
padding: 1px;
display: inline-block;
text-align: center;
border: #d3d3d3 1px solid;
}
}
.delete {
color: black;
&:hover {
color: red;
}
}
</style>
默认解析器类型:
component | 支持字段属性 | 说明 |
---|---|---|
input | string | 文本输入框 |
image | string | 图片选择器 |
select | string | 内容选择器(单选) |
switch | boolean | 开关按钮 |
date-picker | string | 日期选择器 |
time-picker | string | 时间选择器 |
color-picker | string | 颜色选择器 |
rate | number | 评分 |
# 插件自定义属性解析器规范
# 1. 定义解析器
自定义属性解析器本质上是一个 vue 组件,如果要自定义属性解析器可以按照如下方式定义,其中原始数据通过 modelValue
传递进来,如果数据有更新,则通过 update:modelValue
发送出去。
属性解析器负责数据解析显示和编辑后发送更新通知,属性解析器不需要知道数据的具体来源和字段名称,例如下面的解析器本质上就是一个接收 boolean 类型的的 switch 组件,可以接受任意的 boolean 类型数据。
src/attrs/Switch.vue
<template>
<div>
<el-switch :model-value="props.modelValue" v-bind="$attrs" @change="changed"></el-switch>
</div>
</template>
<script setup lang="ts">
const props = defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
const changed = (val: boolean) => emit('update:modelValue', val);
</script>
# 2. 声明解析器
定义完解析器如果想要使用使用则还需要在插件的入口处进行声明,例如:
src/index.ts
import PowerButton from './views/PowerButton.vue';
import CustomAttrsParserDemoView from './views/CustomAttrsParserDemoView.vue';
import Switch from './attrs/Switch.vue'
import { Store } from 'vuex';
import config from '@/config';
import store from '@/store';
import main from '@main';
export default {
...main,
elements: [PowerButton, CustomAttrsParserDemoView],
// 注意此处,声明自定义插件属性解析器
attrsComponents: { 'plg-switch': Switch },
stores: [store],
config: config,
onInstall({ store }: { store: Store<any> }) {},
onUninstall(_: { store: Store<any> }) {},
onConfigChanged({ config, store }: { config: any; store: Store<any> }) {
store.commit(main.id + '#store/setPrefix', config.prefix);
},
};
# 3. 使用解析器
使用解析器则需要在 startup.schema.attrs[i].component
里面指定声明的自定义解析器。
src/views/CustomAttrsParserDemoView.vue
<template>
<div style="width: 100%; height: 100%" :style="myStyle"></div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
const props = defineProps(['view']);
const attrs = computed(() => props.view.props.attrs || {}); // attrs 属性
const myStyle = computed(() => {
return attrs.value.red ? { backgroundColor: 'red' } : { backgroundColor: 'blue' };
});
</script>
<script lang="ts">
export default {
startup: {
title: '测试View',
icon: '',
init: {
type: 'demo-view',
props: {
frame: { y: 0, x: 0, width: 100, height: 100 },
// 定义属性
attrs: {
red: false,
},
},
},
schema: {
attrs: [
{
name: 'red',
component: 'plg-switch', // 指定使用自定义解析器
label: '背景颜色',
props: { 'active-text': '红', 'inactive-text': '蓝' },
},
]
},
},
};
</script>
下面是使用该解析器的显示效果:
# 4. 注意事项
# 4.1 插件中声明的解析器优先级要高于默认的解析器
插件中声明的解析器名称如果和内置解析器名称相同,则优先使用插件中的解析器,例如:中控平台提供了一个名为 switch
的解析器,如果在插件中同样声明了一个 switch
的解析器,则在组件的 schema.attrs
中指定了 switch
时会使用插件中声明的那个,而不是使用默认的解析器。
# 4.2 不同插件之间的解析器是不共享
在 A 插件中声明的解析器不可以在 B 插件中使用。
# 插件图片资源引用方式
插件图片资源推荐放置在一个统一的目录下,然后使用相对路径进行引用。
# HTML
<img src="../assets/img/test.png" alt="" />
<!--目前不支持下面这种方式-->
<div style="background-image: url('../assets/img/test.png')" />
# JS
import img from '../assets/img/test.png'
# CSS
.image {
background-image: url('../assets/img/test.png');
}
# 支持和不支持的引用方式示例
<template>
<div>
<!--有效-->
<img src="../assets/img/test.png" alt="" />
<div :style="testStyle">有效方式</div>
<div class="test">有效方式</div>
<div style="background-image: url('https://abc.com/assets/img/test.png')">有效方式</div>
<!--无效-->
<div style="background-image: url('../assets/img/test.png')">无效方式</div>
<div :style="{ backgroundImage: 'url(' + require('../assets/img/test.png') + ')' }">无效方式</div>
<div :style="{ backgroundImage: 'url(' + import('../assets/img/test.png') + ')' }">无效方式</div>
</div>
</template>
<script setup lang="ts">
import test from '../assets/img/test.png';
const testStyle = {
backgroundImage: 'url(' + test + ')',
color: 'red',
};
</script>
<style scoped lang="less">
.test {
background-image: url('../assets/img/test.png');
}
</style>
# 插件更新自身的方式
插件通过调用 onViewChanged
这个 emit 进行更新自身,使用示例如下:
<script setup lang='ts'>
const props = defineProps(['view']);
function updateViewData(data) {
const view = props.view;
view.data = data;
emit('onViewChanged', { view });
}
<script>
# 国际化支持
如果是新项目,可以使用 ccs-pro 2.1.0 以上版本,并安装最新版本的 sccs (0.4.0 以上版本),创建项目,项目中默认带有国际化翻译示例。
如果是旧项目,可以根据以下步骤来升级到支持国际化版本。
# 1. 升级 ccs-pro 和 sccs
升级 ccs-pro 到 2.1.0 以上的版本。
升级 sccs 工具到 0.4.0 以上的版本。
# 2. 添加 i18n 翻译工具
在项目根目录下使用终端输入以下命令安装翻译工具。
npm install vue-i18n -S
# 3. 创建 locales 翻译文件夹
在 src 目录下创建 locales 文件夹,并创建以下文件。
# i18n.ts
import { createI18n } from 'vue-i18n';
import en from './en';
import zh from './zh_CN';
export const i18n = createI18n({
legacy: false,
locale: localStorage.getItem('language') || 'zh_CN',
globalInjection: false,
messages: {
zh_CN: zh,
en: en,
},
});
// @ts-ignore
export default i18n.global.t;
# en.js
const en = {
lang: {
language: 'English'
},
};
export default en;
# zh_CN.js
const zh = {
lang: {
language: '中文'
},
};
export default zh;
# 4. 组件翻译
组件翻译按照如下方式书写即可。
<template>
<div class="test">
<!--1. 在 template 中使用-->
<div>{{ $t('lang.language') }}</div>
<div>{{ language }}</div>
</div>
</template>
<script setup lang='ts'>
// 2. 在 setup 中使用,注意,导入 $t 的位置在下面的 script 中
const language = $t('lang.language')
</script>
<script lang='ts'>
import icon from './assets/btn_on.webp';
import $t from '@/locales/i18n'
export default {
startup: {
// 3. 翻译组件名称
title: $t('lang.language'),
icon: icon,
init: {
id: '',
type: 'test',
attrs: {},
props: {
frame: {y: 0, x: 0, width: 130, height: 50},
config: {},
hideCustomEvent: true, // 隐藏自定义事件
constraints: [],
attrs: {
lang: '',
},
},
children: []
},
schema: {
attrs: [
// 4. 翻译组件参数
{name: 'color', component: 'input', label: $t('lang.language')},
]
}
}
};
</script>
<style scoped lang='less'>
.test {
color: white;
}
</style>
# 5. 插件名称图标翻译
在 main.json 描述文件中添加 locales 属性,并按照如下格式书写。
{
"id": "demo",
"version": "1.0.0",
"icon": "icon_zh_CN.png",
"name": "未命名插件",
"description": "默认描述",
"locales": {
"en": {
"icon": "icon.png",
"name": "Demo Plugin",
"description": "Demo Description"
},
"zh_CN": {
"icon": "icon_zh_CN.png",
"name": "示例插件",
"description": "默认描述"
}
},
"entry": [
"index.js"
],
"style": [
"index.css"
]
}
# 5. 其它组件翻译
对于其它文件同样可以导入 locales/i18n
后使用 $t
进行翻译。
# 6. 为什么不支持全局 $t
因为中控平台运行需要多插件同时进行,如果启用全局 $t, 则插件翻译数据需要合并到全局中,目前无法保障插件之间的命名空间和前缀不会产生冲突,因此关闭了全局 $t,以避免误用导致插件间冲突。
目前所有的插件都应该创建自己的局部翻译,并在使用翻译功能前手动导入。
# 附录一:插件可编辑属性格式定义
插件可编辑属性统一放置在 init.props.attrs
中,并通过 schema.attrs
对这些属性进行描述,所有通过 schema 描述的属性均可被中控编辑器的属性面板进行编辑,下面是常用的一些数据类型和对应的描述方式,以及最终在属性面板渲染效果图。
# input
⚠️注意:以下的 startup 结构省略了其余不相关的字段
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
title: '按钮'
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'title', component: 'input', label: '按钮文本', props: { clearable: true } }
]
}
}
};
props 属性参考:Input 属性 (opens new window)
# color
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
bgColor: ''
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'bgColor', component: 'color', label: '背景颜色', props: {} }
]
}
}
};
props 属性参考:color 属性 (opens new window)
# pixel
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
bdWidth: ''
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'bdWidth', component: 'pixel', label: '边框大小', props: {} }
]
}
}
};
props 属性参考:Input 属性 (opens new window)
# image
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
normalImage: '',
activedImage: ''
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'normalImage', component: 'image', label: '正常状态', suggest: 'NORMAL', { useSmartLink: true, useSmartSize: true } },
{ name: 'activedImage', component: 'image', label: '激活状态', suggest: 'ACTIVE', { useSmartLink: true, useSmartSize: true } },
]
}
}
};
props 属性参考:
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
useSmartLink | 是否启用自动链接(需要 suggest 属性支持) | boolean | — | false |
useSmartSize | 是否根据图片自动重设大小 | boolean | — | false |
suggest 属性支持的参数(枚举)
参数 | 示意 | 对应后缀 |
---|---|---|
NORMAL | 正常、健康 | ['_n.', '_normal.', '_health.', '_zc.'] |
ACTIVE | 激活 | ['_a.', '_active.'] |
SELECTED | 选中 | ['_s.', '_select.', '_selected.'] |
DISABLED | 禁用 | ['_d.', '_disable.', '_disabled.'] |
UNBIND | 未绑定 | ['_unbound.', '_unbind.', '_none.'] |
WARING | 警告 | ['_warn.', '_waring.', '_yc.'] |
ERROR | 错误 | ['_error.', '_abnormal.', '_gz.'] |
UNKNOWN | 未知 | ['_unknown.', '_wz.'] |
推荐使用的的 suggest 组合类型的后缀组。
// 按钮 btn_n.png、btn_s.png、btn_a.png、btn_d.png
// 按钮 btn_normal.png、btn_selected.png、btn_active.png、btn_disabled.png
// 健康管理 health_zc.png、health_yc.png、health_gz.png、health_wz.png、health_none.png
// 健康管理 health_normal.png、health_warn.png、health_error.png、health_unknown.png、health_unbind.png
已知按钮中图片具有四种状态(正常、激活、选择、禁用),这四种状态是存在关联的,正常情况下绑定按钮需要连续绑定四次,本方案用于优化类似于按钮等组件的绑定逻辑,可以实现一次绑定多个状态的图片,当然,前提是这一系列图片需要按照指定的规则进行命名。
例如: btn_n.png、btn_s.png、btn_a.png、btn_d.png
之后在对应的 schema attrs 属性处声明对应的 suggest 属性为 'NORMAL', 'ACTIVE', 'SELECTED', 'DISABLED',之后在对应的 props 里面配置 useSmartLink
为 true 即可在选择图片时自动实现状态关联。
# switch
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
autoCycle: false
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'autoCycle', component: 'switch', props: { 'active-text': '自动轮巡' } }
]
}
}
};
props 属性参考: switch 属性 (opens new window)
属性 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
exchangeWidthHeight | 属性变动后自动交换宽高 | boolean | — | true |
使用该属性可以在 switch 状态变更后自动切换视图的宽高属性。
# select
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
version: '5'
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'version', component: 'select', label: '版本号', props: { options: ['3', '4', '5'] } }
]
}
}
};
props 属性参考: select 属性 (opens new window)
# font-size
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
fontSize: '14px'
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'fontSize', component: 'font-size', label: '字体大小', props: {} }
]
}
}
};
props 属性参考:Input 属性 (opens new window)
# alignment
居中属性,用于确定内容的居中特性。
声明属性:
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
alignment: ['center', 'center']
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'alignment', component: 'alignment' }
]
}
}
};
使用属性:
省略了大多数逻辑,仅保留核心内容
<template>
<div class="text" :style="[itemStyle]"></div>
</template>
<script lang="ts">
export default {
setup(props: any) {
const itemStyle = computed(() => ({
'--align-item': attrs.value.alignment?.[1] ?? 'center',
'--justify-content': attrs.value.alignment?.[0] ?? 'center'
}));
return {
itemStyle
};
}
};
</script>
<style lang="less" scoped>
.text {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
justify-content: var(--justify-content);
align-items: var(--align-item);
text-align: var(--justify-content);
}
</style>
# font-bold | font-italic | font-underline
本组属性用于控制字体的样式。
声明属性:
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
bold: false,
italic: false,
underline: false
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{
name: 'bold',
component: 'font-bold',
style: { display: 'inline-flex', justifyContent: 'start', width: '33%' }
},
{
name: 'italic',
component: 'font-italic',
style: { display: 'inline-flex', justifyContent: 'center', width: '34%' }
},
{
name: 'underline',
component: 'font-underline',
style: { display: 'inline-flex', justifyContent: 'flex-end', width: '33%' }
}
]
}
}
};
使用属性:
省略了大多数逻辑,仅保留核心内容
<template>
<div class="text" :style="[itemStyle]"></div>
</template>
<script lang="ts">
export default {
setup(props: any) {
const itemStyle = computed(() => ({
'--bold': attrs.value.bold ? 'bold' : 400,
'--italic': attrs.value.italic ? 'italic' : 'initial',
'--underline': attrs.value?.underline ? 'underline' : 'none'
}));
return {
itemStyle
};
}
};
</script>
<style lang="less" scoped>
.text {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
font-weight: var(--bold);
font-style: var(--italic);
text-decoration: var(--underline);
}
</style>
# font-family
用于定义字体属性。
声明属性:
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
latinFamily: '',
asianFamily: ''
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'asianFamily', component: 'font-family', label: '中文字体' },
{ name: 'latinFamily', component: 'font-family', label: '西文字体' }
]
}
}
};
使用属性:
省略了大多数逻辑,仅保留核心内容
<template>
<div class="text" :style="[itemStyle]"></div>
</template>
<script lang="ts">
export default {
setup(props: any) {
const itemStyle = computed(() => {
// 字体加载
let family = '';
if (attrs.value?.latinFamily) family += attrs.value?.latinFamily + ','; // 西文字体
if (attrs.value?.asianFamily) family += attrs.value?.asianFamily + ','; // 中文字体
if (family) family += 'serif'; // 默认字体
return {
'--family': family
};
});
return {
itemStyle
};
}
};
</script>
<style lang="less" scoped>
.text {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
font-family: var(--family);
}
</style>
# padding
padding 属性用于处理内边距。
声明属性:
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {
padding: [0, 0, 0, 0]
},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ name: 'padding', component: 'padding', label: '内边距' }
]
}
}
};
使用属性:
省略了大多数逻辑,仅保留核心内容
<template>
<div class="text" :style="[itemStyle]"></div>
</template>
<script lang="ts">
export default {
setup(props: any) {
const itemStyle = computed(() => ({
padding: attrs.value.padding ? attrs.value.padding.join('px ') + 'px' : '',
}));
return {
itemStyle
};
}
};
</script>
<style lang="less" scoped>
.text {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
}
</style>
# button-emit
export default {
startup: {
init: {
props: {
// 1. 自定义 attrs 属性
attrs: {},
},
},
schema: {
// 2. 声明 attrs 属性解析器
attrs: [
{ component: 'button-emit', props: { name: '绑定屏幕', action: 'bindScreen', type: 'primary' } },
{ component: 'button-emit', props: { name: '解绑屏幕', action: 'unbindScreen', type: 'danger' } },
]
}
}
};
接收 emit 事件:
<script setup lang="ts">
const { proxy }: any = getCurrentInstance();
onMounted(() => proxy.$mitt.on(props.view.props.id, emitAction));
onBeforeUnmount(() => proxy.$mitt.off(props.view.props.id, emitAction));
function emitAction(event: any) {
if (event === 'bindScreen') showBindDialog();
else if (event === 'unbindScreen') tryUnbind();
}
</script>
props 属性参考: button 属性 (opens new window)
# 附录二:完整插件组件示例
展示了插件组件可以使用的一些基本功能:
- 组件代码结构
- 组件自定义样式
- 组件数据池的使用
- 配置面板和组件之间的数据联动
由于需要演示内容较多,所以代码逻辑比较长,但其中部分功能不是必须的,在实际使用中可以根据需求进行裁剪。
<template>
<div :style='btnStyle' class='my-btn'
@click.stop='switchPower'>
<div style='width: 100%; line-height: 100%; text-align: center'>{{ text }}</div>
</div>
</template>
<script setup lang='ts'>
import { ElMessage } from 'element-plus';
import { useActions, useState } from '@hooks/useVuex';
import { computed, getCurrentInstance, onBeforeUnmount, onMounted } from 'vue';
// region 外部参数 ------------------------------------------------------------
const props = defineProps(['view', 'edit_mode']);
const states = useState('store', ['power', 'prefix']);
const { switchPower } = useActions('store', ['switchPower']);
// endregion
// region 内容样式 ------------------------------------------------------------
const text = computed(() => states.prefix.value + '-' + (states.power.value === 'on' ? '关' : '开'));
import btn_on_n from './assets/btn_on.webp';
import btn_off_n from './assets/btn_off.webp';
const btnStyle = computed(() => {
const attrs = props.view?.props?.attrs;
const bgOnImg = attrs?.powerOnImage || btn_on_n;
const bgOffImg = attrs?.powerOffImage || btn_off_n;
const bgImg = states.power.value === 'on' ? bgOffImg : bgOnImg;
return {
color: attrs?.color,
fontSize: attrs?.fontSize || '14px',
borderColor: attrs?.borderColor,
borderWidth: attrs?.borderWidth || '0px',
borderRadius: attrs?.borderRadius || '5px',
borderStyle: 'solid',
backgroundColor: attrs?.backgroundColor,
backgroundImage: `url(${bgImg})`,
backgroundSize: '100% 100%'
};
});
// endregion
// region 属性按钮回调 ------------------------------------------------------------
const { proxy } = getCurrentInstance() as any;
onMounted(() => proxy.$mitt.on(props.view.props.id, emitAction));
onBeforeUnmount(() => proxy.$mitt.off(props.view.props.id, emitAction));
function emitAction(event: any) {
if (event === 'bindDevice') {
ElMessage.success('绑定按钮被点击了');
}
}
// endregion
</script>
<script lang='ts'>
import icon from './assets/btn_on.webp';
export default {
// v3pro button
name: 'PowerButton',
startup: {
title: '开关按钮',
icon: icon,
init: {
id: '',
type: 'power-button',
attrs: {},
props: {
frame: { y: 0, x: 0, width: 130, height: 50 },
config: {},
title: '开关',
hideCustomEvent: true, // 隐藏自定义事件
constraints: [],
attrs: {
color: '',
backgroundColor: '',
borderColor: '',
fontSize: '14px',
borderWidth: '0px',
borderRadius: '5px',
powerOnImage: '',
powerOffImage: ''
},
},
children: []
},
schema: {
attrs: [
{ name: 'color', component: 'color', label: '文本颜色' },
{ name: 'backgroundColor', component: 'color', label: '背景颜色' },
{ name: 'borderColor', component: 'color', label: '边框颜色' },
{ name: 'fontSize', component: 'font-size', label: '字体大小' },
{ name: 'borderWidth', component: 'pixel', label: '边框宽度' },
{ name: 'borderRadius', component: 'pixel', label: '边框圆角' },
{ component: 'button-emit', props: { name: '绑定测试', action: 'bindDevice', type: 'primary' } },
{ name: 'powerOnImage', component: 'image', label: '开启状态' },
{ name: 'powerOffImage', component: 'image', label: '关闭状态' }
]
}
}
};
</script>
<style scoped lang='less'>
.my-btn {
width: 100%;
height: 100%;
position: absolute;
display: flex;
align-items: center;
&:hover {
opacity: 0.85;
}
&:active {
opacity: 1;
}
}
</style>
上述组件在中控编辑器中渲染出来是这样的,在左侧会显示插件名称和预览图标,通过拖动添加到中间的编辑区域上, 选中该组件后,即可在右侧的属性面板看到 attrs 定义的相关属性信息。
# 附录三:全局数据
全局数据通过 vue 的 provider 和 inject 方式提供,详情参考:Provide / Inject (opens new window)
标题中带有括号的表示所需最低版本,例如:(2.0.8+) 表示使用该功能最低需要安装中控 2.0.8 版本。
# 1. 获取项目中某一类组件(2.0.8+)
在组件中可能会需要读取当前项目中某一类的组件信息,例如:机柜状态需要获取当前项目中一共有多少个机柜和机柜内容、页面容器控制组件需要获取当前项目中有多少个容器的信息。
具体使用方式如下所示,该示例展示了如何获取当前项目中的所有按钮组件。
const getViewByType = inject<(type: string) => any[]>('getAllViewsByType');
if (getViewByType) {
const buttons = getViewByType('button')
console.log(buttons);
}
# 2. 获取当前用户(2.0.8+)
用于获取当前登录用户,包括用户名和权限字段,使用方式如下:
const getCurrentUser = inject<() => { username: string; role: string }>('getCurrentUser');
const user = getCurrentUser();
if (user) {
console.log('用户名:', user.username);
if (user.role === 'admin') {
console.log('角色:管理员');
} else if (user.role === 'user') {
console.log('角色:普通用户');
}
}
# 3. 获取当前项目所有页面(2.0.14+)
获取当前项目所有页面的详细信息:
const pages = inject('pages');
console.log('pages', pages);
# 4. 获取当前页面详情(2.0.14+)
获取当前所在页面的详情:
const currentPage = inject('currentPage');
console.log('currentPage', currentPage);
# 4. 获取当前项目详情(2.0.14+)
获取当前所在项目的详情:
const currentProject = inject('currentProject');
console.log('currentProject', currentProject);
# 常见问题
# sccs 版本升级后编译报错,TS2580: Cannot find name 'process'
发生错误: {
code: 'ERROR',
error: [TS2580: Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.] {
frame: '\n' +
'\x1B[7m86\x1B[0m console.log("process.env.NODE_ENV =", process.env.NODE_ENV);\n' +
'\x1B[7m \x1B[0m \x1B[91m ~~~~~~~\x1B[0m\n',
code: 'PLUGIN_ERROR',
length: 7,
loc: {
file: '/Users/gcssloop/WorkSpace/Sansi/sccs-plugin/sccs-plugin-v3pro/src/views/V3PowerButton.vue?vue&type=script&setup=true&lang.ts',
line: 86,
column: 41
},
pos: 0,
pluginCode: 'TS2580',
plugin: 'Typescript',
hook: 'generateBundle'
},
...
}
解决方案:按照提示运行 npm i --save-dev @types/node
即可。