mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-12 22:40:21 +08:00
parent
3673d6016d
commit
d98d3748d3
145
packages/editor/src/components/CodeDraftEditor.vue
Normal file
145
packages/editor/src/components/CodeDraftEditor.vue
Normal 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>
|
68
packages/editor/src/components/FunctionEditor.vue
Normal file
68
packages/editor/src/components/FunctionEditor.vue
Normal 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>
|
@ -4,8 +4,9 @@
|
|||||||
class="code-editor-dialog"
|
class="code-editor-dialog"
|
||||||
:title="currentTitle"
|
:title="currentTitle"
|
||||||
:fullscreen="true"
|
:fullscreen="true"
|
||||||
:before-close="close"
|
:close-on-press-escape="false"
|
||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
|
:show-close="false"
|
||||||
>
|
>
|
||||||
<Layout v-model:left="left" :min-left="45" class="code-editor-layout">
|
<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>
|
<slot name="code-block-edit-panel-header" :id="id"></slot>
|
||||||
<TMagicCard shadow="never">
|
<FunctionEditor
|
||||||
<template #header>
|
v-if="codeConfig"
|
||||||
<div class="code-name-wrapper">
|
:id="id"
|
||||||
<div class="code-name-label">代码块名称</div>
|
:name="codeConfig.name"
|
||||||
<TMagicInput
|
:content="codeConfig.content"
|
||||||
v-if="codeConfig"
|
:editable="!!editable"
|
||||||
class="code-name-input"
|
:autoSaveDraft="mode === CodeEditorMode.EDITOR"
|
||||||
v-model="codeConfig.name"
|
></FunctionEditor>
|
||||||
:disabled="!editable"
|
|
||||||
/>
|
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Layout>
|
</layout>
|
||||||
</TMagicDialog>
|
</TMagicDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="MEditorCodeBlockEditor">
|
<script lang="ts" setup name="MEditorCodeBlockEditor">
|
||||||
import { computed, inject, reactive, ref, watchEffect } from 'vue';
|
import { computed, inject, reactive, ref, watchEffect } from 'vue';
|
||||||
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
|
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
|
||||||
import type * as monaco from 'monaco-editor';
|
|
||||||
|
|
||||||
import {
|
import { TMagicDialog, TMagicTree } from '@tmagic/design';
|
||||||
TMagicButton,
|
|
||||||
TMagicCard,
|
|
||||||
TMagicDialog,
|
|
||||||
TMagicInput,
|
|
||||||
tMagicMessage,
|
|
||||||
tMagicMessageBox,
|
|
||||||
TMagicTree,
|
|
||||||
} from '@tmagic/design';
|
|
||||||
import { datetimeFormatter } from '@tmagic/utils';
|
|
||||||
|
|
||||||
|
import FunctionEditor from '../../../components/FunctionEditor.vue';
|
||||||
import Layout from '../../../components/Layout.vue';
|
import Layout from '../../../components/Layout.vue';
|
||||||
import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
|
import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type';
|
||||||
import { CodeEditorMode } from '../../../type';
|
import { CodeEditorMode } from '../../../type';
|
||||||
import { serializeConfig } from '../../../utils/editor';
|
import { serializeConfig } from '../../../utils/editor';
|
||||||
import MagicCodeEditor from '../../CodeEditor.vue';
|
|
||||||
|
|
||||||
const services = inject<Services>('services');
|
const services = inject<Services>('services');
|
||||||
|
|
||||||
const codeEditor = ref<InstanceType<typeof MagicCodeEditor>>();
|
|
||||||
const left = ref(200);
|
const left = ref(200);
|
||||||
const currentTitle = ref('');
|
const currentTitle = ref('');
|
||||||
// 编辑器当前需展示的代码块内容
|
// 编辑器当前需展示的代码块内容
|
||||||
const codeConfig = ref<CodeBlockContent | null>(null);
|
const codeConfig = ref<CodeBlockContent | null>(null);
|
||||||
// 原始代码内容
|
|
||||||
const originCodeContent = ref<string | null>(null);
|
|
||||||
// select选择的内容(ListState)
|
// select选择的内容(ListState)
|
||||||
const state = reactive<ListState>({
|
const state = reactive<ListState>({
|
||||||
codeList: [],
|
codeList: [],
|
||||||
@ -126,15 +87,7 @@ const selectedIds = computed(() => services?.codeBlockService.getCombineIds() ||
|
|||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
codeConfig.value = cloneDeep(await services?.codeBlockService.getCodeContentById(id.value)) || null;
|
codeConfig.value = cloneDeep(await services?.codeBlockService.getCodeContentById(id.value)) || null;
|
||||||
if (!codeConfig.value) return;
|
if (!codeConfig.value) return;
|
||||||
if (!originCodeContent.value) {
|
codeConfig.value.content = serializeConfig(codeConfig.value.content);
|
||||||
// 暂存原始的代码内容
|
|
||||||
originCodeContent.value = serializeConfig(codeConfig.value.content);
|
|
||||||
}
|
|
||||||
// 有草稿时展示上次保存的草稿内容
|
|
||||||
const codeDraft = services?.codeBlockService.getCodeDraft(id.value);
|
|
||||||
if (codeDraft) {
|
|
||||||
codeConfig.value.content = codeDraft;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
@ -150,67 +103,6 @@ watchEffect(async () => {
|
|||||||
currentTitle.value = state.codeList[0]?.name || '';
|
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) => {
|
const selectHandler = (data: CodeDslList) => {
|
||||||
services?.codeBlockService.setId(data.id);
|
services?.codeBlockService.setId(data.id);
|
||||||
currentTitle.value = data.name;
|
currentTitle.value = data.name;
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<template #default="{ data }">
|
<template #default="{ data }">
|
||||||
<div :id="data.id" class="list-container">
|
<div :id="data.id" class="list-container">
|
||||||
<div class="list-item">
|
<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">
|
<div class="right-tool">
|
||||||
<TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
<TMagicTooltip effect="dark" :content="editable ? '编辑' : '查看'" placement="bottom">
|
||||||
|
@ -103,7 +103,11 @@ class CodeBlock extends BaseService {
|
|||||||
if (!codeDsl) {
|
if (!codeDsl) {
|
||||||
// dsl中无代码块字段
|
// dsl中无代码块字段
|
||||||
codeDsl = {
|
codeDsl = {
|
||||||
[id]: codeConfig,
|
[id]: {
|
||||||
|
...codeConfig,
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
content: eval(codeConfig.content),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const existContent = codeDsl[id] || {};
|
const existContent = codeDsl[id] || {};
|
||||||
@ -112,6 +116,8 @@ class CodeBlock extends BaseService {
|
|||||||
[id]: {
|
[id]: {
|
||||||
...existContent,
|
...existContent,
|
||||||
...codeConfig,
|
...codeConfig,
|
||||||
|
// eslint-disable-next-line no-eval
|
||||||
|
content: eval(codeConfig.content),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
.el-tree-node__content {
|
.el-tree-node__content {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
.el-tree-node__label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.code-header-wrapper {
|
.code-header-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -109,6 +112,9 @@
|
|||||||
.side-tree {
|
.side-tree {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
.el-tree-node__label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.list-container {
|
.list-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user