diff --git a/magic-api-plugin-component/pom.xml b/magic-api-plugin-component/pom.xml
new file mode 100644
index 0000000..e671d42
--- /dev/null
+++ b/magic-api-plugin-component/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ org.ssssssss
+ magic-api-plugins
+ 2.0.0
+
+
+ magic-api-plugin-component
+ jar
+ magic-api-plugin-component
+ magic-api-plugin-component
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+
+ exec-npm-install
+ generate-resources
+
+ exec
+
+
+ npm
+
+ install
+
+ ${basedir}/src/console
+
+
+
+ exec-npm-run-build
+ generate-resources
+
+ exec
+
+
+ npm
+
+ run
+ build
+
+ ${basedir}/src/console
+
+
+
+
+
+
+
+ maven-resources-plugin
+ 3.2.0
+
+
+ copy-resource
+ generate-resources
+
+ copy-resources
+
+
+ ${basedir}/target/classes/magic-editor/plugins
+
+
+ ${basedir}/src/console/dist
+
+
+
+
+
+
+
+
+
+
diff --git a/magic-api-plugin-component/src/console/package.json b/magic-api-plugin-component/src/console/package.json
new file mode 100644
index 0000000..cb4ae01
--- /dev/null
+++ b/magic-api-plugin-component/src/console/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "magic-component",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "build": "vite build"
+ },
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "vue": "^3.2.31",
+ "@vitejs/plugin-vue": "^2.2.4",
+ "vite-plugin-svg-icons": "^1.1.0",
+ "vite": "^2.8.6"
+ }
+}
diff --git a/magic-api-plugin-component/src/console/src/components/magic-component-info.vue b/magic-api-plugin-component/src/console/src/components/magic-component-info.vue
new file mode 100644
index 0000000..440b9f4
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/components/magic-component-info.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
diff --git a/magic-api-plugin-component/src/console/src/i18n/en.js b/magic-api-plugin-component/src/console/src/i18n/en.js
new file mode 100644
index 0000000..e5c0292
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/i18n/en.js
@@ -0,0 +1,14 @@
+export default {
+ component: {
+ title: 'Component Info',
+ name: 'Component',
+ form: {
+ componentName: 'Component Name',
+ description: 'Component Description',
+ placeholder: {
+ componentName: 'Please Enter Component Name',
+ description: 'Please Enter Component Description'
+ }
+ }
+ },
+}
diff --git a/magic-api-plugin-component/src/console/src/i18n/zh-cn.js b/magic-api-plugin-component/src/console/src/i18n/zh-cn.js
new file mode 100644
index 0000000..f2114f5
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/i18n/zh-cn.js
@@ -0,0 +1,14 @@
+export default {
+ component: {
+ title: '组件信息',
+ name: '组件',
+ form: {
+ componentName: '组件名称',
+ description: '组件描述',
+ placeholder: {
+ componentName: '请输入组件名称',
+ description: '请输入组件描述'
+ }
+ }
+ }
+}
diff --git a/magic-api-plugin-component/src/console/src/icons/component.svg b/magic-api-plugin-component/src/console/src/icons/component.svg
new file mode 100644
index 0000000..3753bde
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/icons/component.svg
@@ -0,0 +1,2 @@
+
\ No newline at end of file
diff --git a/magic-api-plugin-component/src/console/src/index.js b/magic-api-plugin-component/src/console/src/index.js
new file mode 100644
index 0000000..dff880b
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/index.js
@@ -0,0 +1,35 @@
+import MagicComponent from './service/magic-component.js'
+import localZhCN from './i18n/zh-cn.js'
+import localEn from './i18n/en.js'
+import MagicComponentInfo from './components/magic-component-info.vue'
+import 'vite-plugin-svg-icons/register'
+export default (opt) => {
+ const i18n = opt.i18n
+ // 添加i18n 国际化信息
+ i18n.add('zh-cn', localZhCN)
+ i18n.add('en', localEn)
+ return {
+ // 左侧资源
+ resource: [{
+ // 资源类型,和后端存储结构一致
+ type: 'component',
+ // 展示图标
+ icon: '#magic-component-component', // #开头表示图标在插件中
+ // 展示名称
+ title: 'component.name',
+ // 运行服务
+ service: MagicComponent(opt.bus, opt.constants, i18n.format, opt.Message, opt.request),
+ }],
+ // 底部工具条
+ toolbars: [{
+ // 当打开的资源类型为 task 时显示
+ type: 'component',
+ // 工具条展示的标题
+ title: 'component.title',
+ // 展示图标
+ icon: 'parameter',
+ // 对应的组件
+ component: MagicComponentInfo,
+ }]
+ }
+}
diff --git a/magic-api-plugin-component/src/console/src/service/magic-component.js b/magic-api-plugin-component/src/console/src/service/magic-component.js
new file mode 100644
index 0000000..87cc2bc
--- /dev/null
+++ b/magic-api-plugin-component/src/console/src/service/magic-component.js
@@ -0,0 +1,26 @@
+export default function (bus, constants, $i, Message, request) {
+ return {
+ // svg text
+ getIcon: item => ['COMPONENT', '#9012FE'],
+ // 任务名称
+ name: $i('component.name'),
+ // 脚本语言
+ language: 'html',
+ // 默认脚本
+ defaultScript: `
+
+
+
+`,
+ // 是否允许执行测试
+ runnable: false,
+ // 是否需要填写路径
+ requirePath: false,
+ // 合并
+ merge: item => item
+ }
+}
diff --git a/magic-api-plugin-component/src/console/vite.config.js b/magic-api-plugin-component/src/console/vite.config.js
new file mode 100644
index 0000000..a4da66b
--- /dev/null
+++ b/magic-api-plugin-component/src/console/vite.config.js
@@ -0,0 +1,37 @@
+import vue from '@vitejs/plugin-vue'
+import viteSvgIcons from 'vite-plugin-svg-icons'
+import path from 'path'
+import pkg from './package.json'
+
+export default {
+ base: './',
+ build: {
+ minify: false,
+ cssCodeSplit: true, // 将组件的 style 打包到 js 文件中
+ outDir: 'dist',
+ lib: {
+ target: 'esnext',
+ formats: ['iife'],
+ entry: path.resolve(__dirname, 'src/index.js'),
+ name: 'MagicComponent',
+ fileName: (format) => `magic-component.${pkg.version}.${format}.js`
+ },
+ rollupOptions: {
+ // 确保外部化处理那些你不想打包进库的依赖
+ external: ['vue'],
+ output: {
+ // 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
+ globals: {
+ vue: 'Vue'
+ }
+ }
+ }
+ },
+ plugins: [
+ vue(),
+ viteSvgIcons({
+ iconDirs: [path.resolve(process.cwd(), 'src/icons')],
+ symbolId: 'magic-component-[name]'
+ }),
+ ]
+}
diff --git a/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/model/ComponentInfo.java b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/model/ComponentInfo.java
new file mode 100644
index 0000000..c9399c5
--- /dev/null
+++ b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/model/ComponentInfo.java
@@ -0,0 +1,55 @@
+package org.ssssssss.magicapi.component.model;
+
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.model.PathMagicEntity;
+
+import java.util.Objects;
+
+public class ComponentInfo extends PathMagicEntity {
+
+ /**
+ * 组件描述
+ */
+ private String description;
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public ComponentInfo copy() {
+ ComponentInfo info = new ComponentInfo();
+ super.copyTo(info);
+ info.setDescription(this.description);
+ return info;
+ }
+
+ @Override
+ public MagicEntity simple() {
+ ComponentInfo info = new ComponentInfo();
+ super.simple(info);
+ return info;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+ ComponentInfo componentInfo = (ComponentInfo) o;
+ return Objects.equals(id, componentInfo.id) &&
+ Objects.equals(path, componentInfo.path) &&
+ Objects.equals(script, componentInfo.script) &&
+ Objects.equals(name, componentInfo.name) &&
+ Objects.equals(description, componentInfo.description);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, path, script, name, groupId, description);
+ }
+
+}
diff --git a/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentInfoMagicResourceStorage.java b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentInfoMagicResourceStorage.java
new file mode 100644
index 0000000..16066eb
--- /dev/null
+++ b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentInfoMagicResourceStorage.java
@@ -0,0 +1,84 @@
+package org.ssssssss.magicapi.component.service;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.ssssssss.magicapi.component.model.ComponentInfo;
+import org.ssssssss.magicapi.core.exception.InvalidArgumentException;
+import org.ssssssss.magicapi.core.model.Group;
+import org.ssssssss.magicapi.core.model.JsonCode;
+import org.ssssssss.magicapi.core.model.MagicEntity;
+import org.ssssssss.magicapi.core.model.TreeNode;
+import org.ssssssss.magicapi.core.service.AbstractPathMagicResourceStorage;
+import org.ssssssss.magicapi.core.service.MagicResourceService;
+
+import java.util.List;
+import java.util.UUID;
+
+public class ComponentInfoMagicResourceStorage extends AbstractPathMagicResourceStorage {
+
+ @Override
+ public String folder() {
+ return "component";
+ }
+
+ @Override
+ public Class magicClass() {
+ return ComponentInfo.class;
+ }
+
+ private boolean strIsEnglish(String word) {
+ boolean sign = true;
+ for (int i = 0; i < word.length(); i++) {
+ if (!(word.charAt(i) >= 'A' && word.charAt(i) <= 'Z')
+ && !(word.charAt(i) >= 'a' && word.charAt(i) <= 'z')) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void isNameRepeat(List> groupChildren, String name, String id){
+ groupChildren.stream().forEach(it -> {
+ Group node = it.getNode();
+ List> chi = it.getChildren();
+ magicResourceService.listFiles(node.getId()).forEach(file -> {
+ if(null != id){
+ if(!file.getId().equals(id) && file.getName().equals(name)){
+ throw new InvalidArgumentException(new JsonCode(9004, "组件名称已存在"));
+ }
+ }else{
+ if(file.getName().equals(name)){
+ throw new InvalidArgumentException(new JsonCode(9004, "组件名称已存在"));
+ }
+ }
+ });
+ if(chi.size() > 0){
+ isNameRepeat(chi, name, id);
+ }
+ });
+ }
+
+ @Override
+ public void validate(ComponentInfo entity) {
+ if(null == entity.getPath() || entity.getPath().equals("")){
+ entity.setPath(UUID.randomUUID().toString().replace("-", ""));
+ }
+ isNameRepeat(magicResourceService.tree("component").getChildren(), entity.getName(), entity.getId());
+ notBlank(entity.getName(), new JsonCode(9001, "组件名称不能为空"));
+ if(!strIsEnglish(entity.getName())){
+ throw new InvalidArgumentException(new JsonCode(9002, "组件名称必须是英文"));
+ }
+ notBlank(entity.getDescription(), new JsonCode(9003, "组件描述不能为空"));
+ }
+
+ @Override
+ public String buildMappingKey(ComponentInfo info) {
+ return buildMappingKey(info, magicResourceService.getGroupPath(info.getGroupId()));
+ }
+
+ @Override
+ public boolean requirePath() {
+ return false;
+ }
+
+}
diff --git a/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentMagicDynamicRegistry.java b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentMagicDynamicRegistry.java
new file mode 100644
index 0000000..3300653
--- /dev/null
+++ b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/service/ComponentMagicDynamicRegistry.java
@@ -0,0 +1,28 @@
+package org.ssssssss.magicapi.component.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.event.EventListener;
+import org.ssssssss.magicapi.component.model.ComponentInfo;
+import org.ssssssss.magicapi.core.event.FileEvent;
+import org.ssssssss.magicapi.core.event.GroupEvent;
+import org.ssssssss.magicapi.core.service.AbstractMagicDynamicRegistry;
+import org.ssssssss.magicapi.core.service.MagicResourceStorage;
+
+public class ComponentMagicDynamicRegistry extends AbstractMagicDynamicRegistry {
+
+ public ComponentMagicDynamicRegistry(MagicResourceStorage magicResourceStorage) {
+ super(magicResourceStorage);
+ }
+
+ @EventListener(condition = "#event.type == 'component'")
+ public void onFileEvent(FileEvent event) {
+ processEvent(event);
+ }
+
+ @EventListener(condition = "#event.type == 'component'")
+ public void onGroupEvent(GroupEvent event) {
+ processEvent(event);
+ }
+
+}
diff --git a/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/starter/MagicAPIComponentConfiguration.java b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/starter/MagicAPIComponentConfiguration.java
new file mode 100644
index 0000000..c113f20
--- /dev/null
+++ b/magic-api-plugin-component/src/main/java/org/ssssssss/magicapi/component/starter/MagicAPIComponentConfiguration.java
@@ -0,0 +1,31 @@
+package org.ssssssss.magicapi.component.starter;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.ssssssss.magicapi.component.service.ComponentInfoMagicResourceStorage;
+import org.ssssssss.magicapi.component.service.ComponentMagicDynamicRegistry;
+import org.ssssssss.magicapi.core.config.MagicPluginConfiguration;
+import org.ssssssss.magicapi.core.model.Plugin;
+
+@Configuration
+public class MagicAPIComponentConfiguration implements MagicPluginConfiguration {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ComponentInfoMagicResourceStorage componentInfoMagicResourceStorage() {
+ return new ComponentInfoMagicResourceStorage();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public ComponentMagicDynamicRegistry componentMagicDynamicRegistry(ComponentInfoMagicResourceStorage componentInfoMagicResourceStorage) {
+ return new ComponentMagicDynamicRegistry(componentInfoMagicResourceStorage);
+ }
+
+ @Override
+ public Plugin plugin() {
+ return new Plugin("组件", "MagicComponent", "magic-component.1.0.0.iife.js");
+ }
+
+}
diff --git a/magic-api-plugin-component/src/main/resources/META-INF/spring.factories b/magic-api-plugin-component/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..def6a68
--- /dev/null
+++ b/magic-api-plugin-component/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.ssssssss.magicapi.component.starter.MagicAPIComponentConfiguration
diff --git a/magic-boot/pom.xml b/magic-boot/pom.xml
index b3d466f..248c48d 100644
--- a/magic-boot/pom.xml
+++ b/magic-boot/pom.xml
@@ -52,6 +52,16 @@
sa-token-spring-boot-starter
${sa-token.version}
+
+ org.ssssssss
+ magic-api-plugin-task
+ ${magic-api.version}
+
+
+ org.ssssssss
+ magic-api-plugin-component
+ ${magic-api.version}
+