feat(editor): 代码块新增,编辑器保存至dsl

This commit is contained in:
parisma 2022-09-06 15:08:49 +08:00 committed by jia000
parent e69e193874
commit 0c2c33f854
10 changed files with 274 additions and 4 deletions

View File

@ -22,6 +22,10 @@
<template #component-list-item="{ component }"> <template #component-list-item="{ component }">
<slot name="component-list-item" :component="component"></slot> <slot name="component-list-item" :component="component"></slot>
</template> </template>
<template #code-block-panel-header>
<slot name="code-block-panel-header"></slot>
</template>
</sidebar> </sidebar>
</slot> </slot>
</template> </template>
@ -43,6 +47,9 @@
<template #props-panel-header> <template #props-panel-header>
<slot name="props-panel-header"></slot> <slot name="props-panel-header"></slot>
</template> </template>
<template #props-panel-code-block>
<slot name="props-panel-code-block"></slot>
</template>
</props-panel> </props-panel>
</slot> </slot>
</template> </template>

View File

@ -1,6 +1,9 @@
<template> <template>
<div class="`m-editor-props-panel"> <div class="m-editor-props-panel">
<slot name="props-panel-header"></slot> <slot name="props-panel-header"></slot>
<slot name="props-panel-code-block">
<code-block-editor />
</slot>
<m-form <m-form
ref="configForm" ref="configForm"
:class="`m-editor-props-panel ${propsPanelSize}`" :class="`m-editor-props-panel ${propsPanelSize}`"
@ -23,6 +26,8 @@ import type StageCore from '@tmagic/stage';
import type { Services } from '../type'; import type { Services } from '../type';
import CodeBlockEditor from './sidebar/code-block/CodeBlockEditor.vue';
const emit = defineEmits(['mounted']); const emit = defineEmits(['mounted']);
const internalInstance = getCurrentInstance(); const internalInstance = getCurrentInstance();

View File

@ -22,6 +22,10 @@
<template #component-list-item="{ component }" v-if="item === 'component-list'"> <template #component-list-item="{ component }" v-if="item === 'component-list'">
<slot name="component-list-item" :component="component"></slot> <slot name="component-list-item" :component="component"></slot>
</template> </template>
<template #code-block-panel-header v-if="item === 'code-block'">
<slot name="code-block-panel-header"></slot>
</template>
</tab-pane> </tab-pane>
</el-tabs> </el-tabs>
</template> </template>
@ -41,7 +45,7 @@ export default defineComponent({
props: { props: {
data: { data: {
type: Object as PropType<SideBarData>, type: Object as PropType<SideBarData>,
default: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer'] }), default: () => ({ type: 'tabs', status: '组件', items: ['component-list', 'layer', 'code-block'] }),
}, },
}, },

View File

@ -48,11 +48,12 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent, PropType } from 'vue'; import { computed, defineComponent, PropType } from 'vue';
import { Coin, Files } from '@element-plus/icons-vue'; import { Coin, EditPen, Files } from '@element-plus/icons-vue';
import MIcon from '../../components/Icon.vue'; import MIcon from '../../components/Icon.vue';
import { SideComponent, SideItem } from '../../type'; import { SideComponent, SideItem } from '../../type';
import CodeBlockList from './code-block/CodeBlockList.vue';
import ComponentListPanel from './ComponentListPanel.vue'; import ComponentListPanel from './ComponentListPanel.vue';
import LayerPanel from './LayerPanel.vue'; import LayerPanel from './LayerPanel.vue';
@ -89,6 +90,14 @@ export default defineComponent({
component: LayerPanel, component: LayerPanel,
slots: {}, slots: {},
}; };
case 'code-block':
return {
type: 'component',
icon: EditPen,
text: '代码编辑',
component: CodeBlockList,
slots: {},
};
default: default:
return undefined; return undefined;
} }

View File

@ -0,0 +1,115 @@
<template>
<div ref="codeBlockEditor" class="m-editor-code-block-editor-panel" v-if="state.isShowCodeBlockEditor">
<div class="header">
<h3 class="title">代码块编辑面板</h3>
<el-button class="close-btn" circle :icon="Close" type="danger" bg :text="false" @click="closePanel"></el-button>
</div>
<el-row :gutter="20" class="code-name-wrapper">
<el-col :span="6">
<span>代码块名称</span>
</el-col>
<el-col :span="6">
<el-input size="small" v-model="state.codeConfig.name" />
</el-col>
</el-row>
<div class="m-editor-content" :class="isFullScreen ? 'fullScreen' : 'normal'">
<magic-code-editor
ref="codeEditor"
class="m-editor-content"
:init-values="state.codeConfig.content"
@save="saveCode"
:options="{
tabSize: 2,
fontSize: 16,
formatOnPaste: true,
}"
></magic-code-editor>
<div class="m-editor-content-bottom clearfix">
<el-button type="primary" class="button" @click="toggleFullScreen">
{{ isFullScreen ? '退出全屏' : '全屏' }}
</el-button>
<el-button type="primary" class="button" @click="saveCode">保存</el-button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { inject, ref, watchEffect } from 'vue';
import { Close } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import type { Services } from '../type';
import useCodeBlock from './useCodeBlock';
const { state, closePanel } = useCodeBlock();
let parentElement: HTMLElement | null = null;
const isFullScreen = ref(false);
const codeBlockEditor = ref<HTMLElement | null>(null);
const codeEditor = ref<any | null>(null);
const services = inject<Services>('services');
watchEffect(() => {
if (state.isShowCodeBlockEditor) {
if (!codeBlockEditor.value) {
return;
}
parentElement = codeBlockEditor.value.parentElement;
}
});
//
const toggleFullScreen = () => {
isFullScreen.value = !isFullScreen.value;
if (!codeBlockEditor.value) return;
if (isFullScreen.value) {
globalThis.document.body.appendChild(codeBlockEditor.value);
} else if (parentElement) {
parentElement.appendChild(codeBlockEditor.value);
} else {
codeBlockEditor.value.remove();
}
setTimeout(() => {
if (!codeEditor.value) return;
codeEditor.value.focus();
codeEditor.value.getEditor().layout();
});
};
//
const saveCode = () => {
if (!codeEditor.value || !state.codeConfig) return;
try {
const codeContent = codeEditor.value.getEditor().getValue();
/* eslint no-eval: "off" */
state.codeConfig.content = eval(codeContent);
const { id, ...codeConfig } = state.codeConfig;
const { editorService } = services || {};
if (!editorService) return;
const root = editorService.get('root');
// idkey
let codeBlockList = root?.method || {};
codeBlockList = {
...codeBlockList,
...{
[state.codeConfig.id]: codeConfig,
},
};
// dsl
editorService.set('root', {
...root,
method: codeBlockList,
});
ElMessage.success('代码保存成功');
} catch (e: any) {
ElMessage.error(e.stack);
}
};
</script>

View File

@ -0,0 +1,16 @@
<template>
<div class="m-editor-code-block-list">
<div class="create-code-button">
<el-button type="primary" size="small" @click="createCodeBlock">新增代码块</el-button>
</div>
<el-divider class="divider" />
<!-- 代码块列表 -->
<div class="list-container"></div>
</div>
</template>
<script lang="ts" setup>
import useCodeBlock from './useCodeBlock';
const { createCodeBlock } = useCodeBlock();
</script>

View File

@ -0,0 +1,37 @@
import { reactive } from 'vue';
import { CodeBlockConfig } from '../../../type';
interface State {
/** 是否展示代码块编辑区 */
isShowCodeBlockEditor: boolean;
/** 代码块配置 */
codeConfig: CodeBlockConfig | null;
}
const state = reactive<State>({
isShowCodeBlockEditor: false,
codeConfig: null,
});
const getUniqueId = () => Date.now().toString(36) + Math.random().toString(36).substring(2);
// 关闭代码块面板
const closePanel = () => (state.isShowCodeBlockEditor = false);
// 新增代码块
const createCodeBlock = () => {
// todo 如果已有代码块打开,需要先保存关闭
closePanel();
state.isShowCodeBlockEditor = true;
state.codeConfig = {
id: getUniqueId(),
name: '代码块',
content: `(app) => {\n // place your code here\n}`,
};
};
export default () => ({
state,
closePanel,
createCodeBlock,
});

View File

@ -0,0 +1,67 @@
.m-editor-code-block-list {
padding-top: 20px;
.create-code-button {
display: flex;
justify-content: center;
align-items: center;
}
.divider {
margin: 20px 0;
}
}
.m-editor-code-block-editor-panel {
position: absolute;
top: 0;
left: 4px;
width: 100%;
height: 100%;
z-index: 10;
background: #fff;
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 20px;
h3.title {
font-size: 16px;
font-weight: bold;
}
}
.close-btn {
background: rgb(213, 71, 71);
color: #fff;
}
.code-name-wrapper {
padding: 10px 20px;
display: flex;
align-items: center;
}
.m-editor-content {
&.fullScreen {
height: 100%;
width: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
}
.m-editor-content-bottom {
text-align: right;
padding-right: 20px;
height: 40px;
position: absolute;
width: 100%;
display: flex;
justify-content: center;
bottom: 0;
right: 0;
background: #fff;
> button {
height: 30px;
margin-top: 4px;
}
}
}

View File

@ -12,3 +12,4 @@
@import "./stage.scss"; @import "./stage.scss";
@import "./code-editor.scss"; @import "./code-editor.scss";
@import "./icon.scss"; @import "./icon.scss";
@import "./code-block.scss";

View File

@ -243,7 +243,7 @@ export interface SideComponent extends MenuComponent {
* component-list: 组件列表 * component-list: 组件列表
* layer: 已选组件树 * layer: 已选组件树
*/ */
export type SideItem = 'component-list' | 'layer' | SideComponent; export type SideItem = 'component-list' | 'layer' | 'code-block' | SideComponent;
/** 工具栏 */ /** 工具栏 */
export interface SideBarData { export interface SideBarData {
@ -305,3 +305,12 @@ export interface ScrollViewerEvent {
scrollHeight: number; scrollHeight: number;
scrollWidth: number; scrollWidth: number;
} }
export interface CodeBlockConfig {
/** 代码块唯一id */
id: string;
/** 代码块名称 */
name: string;
/** 代码块内容 */
content: string;
}