mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-25 02:58:36 +08:00
feat(editor): 代码块新增,编辑器保存至dsl
This commit is contained in:
parent
e69e193874
commit
0c2c33f854
@ -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>
|
||||||
|
@ -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();
|
||||||
|
@ -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'] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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');
|
||||||
|
|
||||||
|
// 代码块id作为key
|
||||||
|
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>
|
@ -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>
|
@ -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,
|
||||||
|
});
|
67
packages/editor/src/theme/code-block.scss
Normal file
67
packages/editor/src/theme/code-block.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user