feat(editor): 拆分代码块编辑器便于以后扩展,支持草稿自动保存,修复代码块列表的样式问题

#440
This commit is contained in:
parisma 2022-10-24 10:46:14 +08:00
parent 3673d6016d
commit d98d3748d3
6 changed files with 241 additions and 124 deletions

View File

@ -0,0 +1,145 @@
<template>
<div class="m-editor-wrapper">
<magic-code-editor
ref="codeEditor"
class="m-editor-container"
:init-values="`${codeContent}`"
@save="saveCodeDraft"
:options="{
tabSize: 2,
fontSize: 16,
formatOnPaste: true,
readOnly: !editable,
}"
></magic-code-editor>
<div class="m-editor-content-bottom" v-if="editable">
<TMagicButton type="primary" class="button" @click="saveCode">保存</TMagicButton>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
<div class="m-editor-content-bottom" v-else>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
</div>
</template>
<script lang="ts" setup>
import { inject, ref, watchEffect } from 'vue';
import type * as monaco from 'monaco-editor';
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
import { datetimeFormatter } from '@tmagic/utils';
import MagicCodeEditor from '../layouts/CodeEditor.vue';
import type { Services } from '../type';
// 稿
const draftTipTimeOut = 100;
const props = withDefaults(
defineProps<{
/** 代码id */
id: string;
/** 代码内容 */
content: string;
/** 是否可编辑 */
editable?: boolean;
/** 是否自动保存草稿 */
autoSaveDraft?: boolean;
}>(),
{
editable: true,
autoSaveDraft: true,
},
);
const emit = defineEmits(['save', 'close']);
const services = inject<Services>('services');
const codeContent = ref<string>('');
const codeEditor = ref<InstanceType<typeof MagicCodeEditor>>();
//
const originCodeContent = ref<string | null>(null);
// 稿
const shouldShowDraftTip = ref(true);
watchEffect(() => {
codeContent.value = props.content;
if (!originCodeContent.value) {
//
originCodeContent.value = codeContent.value;
}
// 稿稿
const codeDraft = services?.codeBlockService.getCodeDraft(props.id);
if (codeDraft) {
codeContent.value = codeDraft;
}
});
// 稿
const saveCodeDraft = async (codeValue: string) => {
if (!props.autoSaveDraft) return;
if (originCodeContent.value === codeValue) {
// 稿稿
services?.codeBlockService.removeCodeDraft(props.id);
return;
}
services?.codeBlockService.setCodeDraft(props.id, codeValue);
setTimeout(() => {
if (shouldShowDraftTip.value) {
tMagicMessage.success(`代码草稿保存成功 ${datetimeFormatter(new Date())}`);
}
}, draftTipTimeOut);
};
//
const saveCode = async (): Promise<boolean> => {
if (!codeEditor.value || !props.editable) return true;
try {
//
const editorContent = (codeEditor.value.getEditor() as monaco.editor.IStandaloneCodeEditor)?.getValue();
/* eslint no-eval: "off" */
eval(editorContent);
//
shouldShowDraftTip.value = false;
// 稿
services?.codeBlockService.removeCodeDraft(props.id);
emit('save', editorContent);
return true;
} catch (e: any) {
tMagicMessage.error(e.stack);
return false;
}
};
const beforeClose = async () => {
const codeDraft = services?.codeBlockService.getCodeDraft(props.id);
if (!codeDraft || !props.autoSaveDraft) {
return await saveCode();
}
let saveRes = true;
await tMagicMessageBox
.confirm('您有代码修改未保存,是否保存后再关闭?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
//
saveRes = await saveCode();
})
.catch(() => {
// 稿
services?.codeBlockService.removeCodeDraft(props.id);
});
return saveRes;
};
//
const close = async () => {
const shouldClose = await beforeClose();
if (shouldClose) {
emit('close');
}
};
</script>

View File

@ -0,0 +1,68 @@
<template>
<TMagicCard shadow="never">
<template #header>
<div class="code-name-wrapper">
<div class="code-name-label">代码块名称</div>
<TMagicInput class="code-name-input" v-model="codeName" :disabled="!editable" />
</div>
</template>
<CodeDraftEditor
:id="id"
:content="codeContent"
:editable="editable"
:autoSaveDraft="autoSaveDraft"
@save="saveCode"
@close="close"
></CodeDraftEditor>
</TMagicCard>
</template>
<script lang="ts" setup>
import { inject, ref, watchEffect } from 'vue';
import { TMagicCard, TMagicInput, tMagicMessage } from '@tmagic/design';
import type { Services } from '../type';
import CodeDraftEditor from './CodeDraftEditor.vue';
const props = withDefaults(
defineProps<{
id: string;
name: string;
content: string;
editable?: boolean;
autoSaveDraft?: boolean;
}>(),
{
editable: true,
autoSaveDraft: true,
},
);
const services = inject<Services>('services');
const codeName = ref<string>('');
const codeContent = ref<string>('');
watchEffect(() => {
codeName.value = props.name;
codeContent.value = props.content;
});
//
const saveCode = async (codeValue: string): Promise<void> => {
if (!props.editable) return;
// dsl
await services?.codeBlockService.setCodeDslById(props.id, {
name: codeName.value,
content: codeValue,
});
tMagicMessage.success('代码保存成功');
};
//
const close = () => {
services?.codeBlockService.setCodeEditorShowStatus(false);
};
</script>

View File

@ -4,8 +4,9 @@
class="code-editor-dialog"
:title="currentTitle"
:fullscreen="true"
:before-close="close"
:close-on-press-escape="false"
:append-to-body="true"
:show-close="false"
>
<Layout v-model:left="left" :min-left="45" class="code-editor-layout">
<!-- 左侧列表 -->
@ -40,78 +41,38 @@
]"
>
<slot name="code-block-edit-panel-header" :id="id"></slot>
<TMagicCard shadow="never">
<template #header>
<div class="code-name-wrapper">
<div class="code-name-label">代码块名称</div>
<TMagicInput
<FunctionEditor
v-if="codeConfig"
class="code-name-input"
v-model="codeConfig.name"
:disabled="!editable"
/>
:id="id"
:name="codeConfig.name"
:content="codeConfig.content"
:editable="!!editable"
:autoSaveDraft="mode === CodeEditorMode.EDITOR"
></FunctionEditor>
</div>
</template>
<div class="m-editor-wrapper">
<MagicCodeEditor
v-if="codeConfig"
ref="codeEditor"
class="m-editor-container"
:init-values="`${codeConfig.content}`"
@save="saveCodeDraft"
:options="{
tabSize: 2,
fontSize: 16,
formatOnPaste: true,
readOnly: !editable,
}"
></MagicCodeEditor>
<div class="m-editor-content-bottom" v-if="editable">
<TMagicButton type="primary" class="button" @click="saveCode">保存</TMagicButton>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
<div class="m-editor-content-bottom" v-else>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
</div>
</TMagicCard>
</div>
</template>
</Layout>
</layout>
</TMagicDialog>
</template>
<script lang="ts" setup name="MEditorCodeBlockEditor">
import { computed, inject, reactive, ref, watchEffect } from 'vue';
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
import type * as monaco from 'monaco-editor';
import {
TMagicButton,
TMagicCard,
TMagicDialog,
TMagicInput,
tMagicMessage,
tMagicMessageBox,
TMagicTree,
} from '@tmagic/design';
import { datetimeFormatter } from '@tmagic/utils';
import { TMagicDialog, TMagicTree } from '@tmagic/design';
import FunctionEditor from '../../../components/FunctionEditor.vue';
import Layout from '../../../components/Layout.vue';
import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
import { CodeEditorMode } from '../../../type';
import { serializeConfig } from '../../../utils/editor';
import MagicCodeEditor from '../../CodeEditor.vue';
const services = inject<Services>('services');
const codeEditor = ref<InstanceType<typeof MagicCodeEditor>>();
const left = ref(200);
const currentTitle = ref('');
//
const codeConfig = ref<CodeBlockContent | null>(null);
//
const originCodeContent = ref<string | null>(null);
// select(ListState)
const state = reactive<ListState>({
codeList: [],
@ -126,15 +87,7 @@ const selectedIds = computed(() => services?.codeBlockService.getCombineIds() ||
watchEffect(async () => {
codeConfig.value = cloneDeep(await services?.codeBlockService.getCodeContentById(id.value)) || null;
if (!codeConfig.value) return;
if (!originCodeContent.value) {
//
originCodeContent.value = serializeConfig(codeConfig.value.content);
}
// 稿稿
const codeDraft = services?.codeBlockService.getCodeDraft(id.value);
if (codeDraft) {
codeConfig.value.content = codeDraft;
}
codeConfig.value.content = serializeConfig(codeConfig.value.content);
});
watchEffect(async () => {
@ -150,67 +103,6 @@ watchEffect(async () => {
currentTitle.value = state.codeList[0]?.name || '';
});
// 稿
const saveCodeDraft = (codeValue: string) => {
if (!codeEditor.value) return;
if (originCodeContent.value === codeValue) {
// 稿稿
services?.codeBlockService.removeCodeDraft(id.value);
return;
}
services?.codeBlockService.setCodeDraft(id.value, codeValue);
tMagicMessage.success(`代码草稿保存成功 ${datetimeFormatter(new Date())}`);
};
//
const saveCode = async (): Promise<boolean> => {
if (!codeEditor.value || !codeConfig.value || !editable.value) return true;
try {
//
const codeContent = (codeEditor.value.getEditor() as monaco.editor.IStandaloneCodeEditor)?.getValue();
/* eslint no-eval: "off" */
codeConfig.value.content = eval(codeContent);
} catch (e: any) {
tMagicMessage.error(e.stack);
return false;
}
// dsl
await services?.codeBlockService.setCodeDslById(id.value, {
name: codeConfig.value.name,
content: codeConfig.value.content,
});
tMagicMessage.success('代码保存成功');
// 稿
services?.codeBlockService.removeCodeDraft(id.value);
return true;
};
//
const close = async () => {
const codeDraft = services?.codeBlockService.getCodeDraft(id.value);
let shouldClose = true;
if (codeDraft) {
await tMagicMessageBox
.confirm('您有代码修改未保存,是否保存后再关闭?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
//
shouldClose = await saveCode();
})
.catch(() => {
// 稿
services?.codeBlockService.removeCodeDraft(id.value);
});
}
if (shouldClose) {
services?.codeBlockService.setCodeEditorShowStatus(false);
}
};
const selectHandler = (data: CodeDslList) => {
services?.codeBlockService.setId(data.id);
currentTitle.value = data.name;

View File

@ -30,7 +30,7 @@
<template #default="{ data }">
<div :id="data.id" class="list-container">
<div class="list-item">
<div class="code-name">{{ data.name }}{{ data.id }}</div>
<span class="code-name">{{ data.name }}{{ data.id }}</span>
<!-- 右侧工具栏 -->
<div class="right-tool">
<TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">

View File

@ -103,7 +103,11 @@ class CodeBlock extends BaseService {
if (!codeDsl) {
// dsl中无代码块字段
codeDsl = {
[id]: codeConfig,
[id]: {
...codeConfig,
// eslint-disable-next-line no-eval
content: eval(codeConfig.content),
},
};
} else {
const existContent = codeDsl[id] || {};
@ -112,6 +116,8 @@ class CodeBlock extends BaseService {
[id]: {
...existContent,
...codeConfig,
// eslint-disable-next-line no-eval
content: eval(codeConfig.content),
},
};
}

View File

@ -3,6 +3,9 @@
.el-tree-node__content {
height: auto;
}
.el-tree-node__label {
width: 100%;
}
.code-header-wrapper {
display: flex;
align-items: center;
@ -109,6 +112,9 @@
.side-tree {
height: 100%;
overflow: auto;
.el-tree-node__label {
width: 100%;
}
.list-container {
width: 100%;