feat(editor): 代码编辑新增草稿功能

#440
This commit is contained in:
parisma 2022-10-21 15:17:27 +08:00
parent 3e78a0809b
commit 3673d6016d
5 changed files with 104 additions and 47 deletions

View File

@ -1,10 +1,10 @@
<template>
<TMagicDialog
v-model="isShowCodeBlockEditor"
:model-value="true"
class="code-editor-dialog"
:title="currentTitle"
:fullscreen="true"
:before-close="saveAndClose"
:before-close="close"
:append-to-body="true"
>
<Layout v-model:left="left" :min-left="45" class="code-editor-layout">
@ -58,7 +58,7 @@
ref="codeEditor"
class="m-editor-container"
:init-values="`${codeConfig.content}`"
@save="saveCode"
@save="saveCodeDraft"
:options="{
tabSize: 2,
fontSize: 16,
@ -68,10 +68,10 @@
></MagicCodeEditor>
<div class="m-editor-content-bottom" v-if="editable">
<TMagicButton type="primary" class="button" @click="saveCode">保存</TMagicButton>
<TMagicButton type="primary" class="button" @click="saveAndClose">关闭</TMagicButton>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
<div class="m-editor-content-bottom" v-else>
<TMagicButton type="primary" class="button" @click="saveAndClose">关闭</TMagicButton>
<TMagicButton type="primary" class="button" @click="close">关闭</TMagicButton>
</div>
</div>
</TMagicCard>
@ -83,14 +83,24 @@
<script lang="ts" setup name="MEditorCodeBlockEditor">
import { computed, inject, reactive, ref, watchEffect } from 'vue';
import { forIn, isEmpty } from 'lodash-es';
import { cloneDeep, forIn, isEmpty } from 'lodash-es';
import type * as monaco from 'monaco-editor';
import { TMagicButton, TMagicCard, TMagicDialog, TMagicInput, tMagicMessage, TMagicTree } from '@tmagic/design';
import {
TMagicButton,
TMagicCard,
TMagicDialog,
TMagicInput,
tMagicMessage,
tMagicMessageBox,
TMagicTree,
} from '@tmagic/design';
import { datetimeFormatter } from '@tmagic/utils';
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');
@ -100,15 +110,13 @@ 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: [],
});
//
const isShowCodeBlockEditor = computed(
() => (codeConfig.value && services?.codeBlockService.getCodeEditorShowStatus()) || false,
);
const mode = computed(() => services?.codeBlockService.getMode());
const id = computed(() => services?.codeBlockService.getId() || '');
const editable = computed(() => services?.codeBlockService.getEditStatus());
@ -116,7 +124,17 @@ const editable = computed(() => services?.codeBlockService.getEditStatus());
const selectedIds = computed(() => services?.codeBlockService.getCombineIds() || []);
watchEffect(async () => {
codeConfig.value = (await services?.codeBlockService.getCodeContentById(id.value)) || null;
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;
}
});
watchEffect(async () => {
@ -132,6 +150,18 @@ 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;
@ -151,14 +181,33 @@ const saveCode = async (): Promise<boolean> => {
content: codeConfig.value.content,
});
tMagicMessage.success('代码保存成功');
// 稿
services?.codeBlockService.removeCodeDraft(id.value);
return true;
};
//
const saveAndClose = async () => {
const saveRes = await saveCode();
if (saveRes) {
await services?.codeBlockService.setCodeEditorShowStatus(false);
//
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);
}
};

View File

@ -85,7 +85,7 @@
</TMagicTree>
<!-- 代码块编辑区 -->
<code-block-editor>
<code-block-editor v-if="isShowCodeBlockEditor">
<template #code-block-edit-panel-header="{ id }">
<slot name="code-block-edit-panel-header" :id="id"></slot>
</template>
@ -122,6 +122,9 @@ const state = reactive<ListRelationState>({
const editable = computed(() => services?.codeBlockService.getEditStatus());
//
const isShowCodeBlockEditor = computed(() => services?.codeBlockService.getCodeEditorShowStatus() || false);
// ID
const getBindCompsByCodeId = (codeId: string, codeBlockContent: CodeBlockContent) => {
if (isEmpty(codeBlockContent) || isEmpty(codeBlockContent.comps)) {

View File

@ -23,7 +23,7 @@ import { Id, MNode } from '@tmagic/schema';
import editorService from '../services/editor';
import type { CodeBlockContent, CodeBlockDSL, CodeState } from '../type';
import { CodeEditorMode, CodeSelectOp } from '../type';
import { CODE_DRAFT_STORAGE_KEY, CodeEditorMode, CodeSelectOp } from '../type';
import { error, info } from '../utils/logger';
import BaseService from './BaseService';
@ -321,6 +321,27 @@ class CodeBlock extends BaseService {
this.state.undeletableList = codeIds;
}
/**
* 稿
*/
public setCodeDraft(codeId: string, content: string): void {
globalThis.localStorage.setItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`, content);
}
/**
* 稿
*/
public getCodeDraft(codeId: string): string | null {
return globalThis.localStorage.getItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`);
}
/**
* 稿
*/
public removeCodeDraft(codeId: string): void {
globalThis.localStorage.removeItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`);
}
/**
* dsl数据源中删除指定id的代码块
* @param {string[]} codeIds id数组
@ -346,33 +367,6 @@ class CodeBlock extends BaseService {
return await this.getUniqueId();
}
/**
*
* @param {string} compId id
* @param {string} codeId id
* @param {string[]} codeHooks hook名称
* @returns {boolean}
*/
// public async unbind(compId: Id, codeId: string, codeHooks: string[]): Promise<boolean> {
// const nodeInfo = editorService.getNodeById(compId);
// if (!nodeInfo) return false;
// // 更新node节点信息
// codeHooks.forEach((hook) => {
// if (!isEmpty(nodeInfo[hook])) {
// const newHookInfo = nodeInfo[hook].filter((item: string) => item !== codeId);
// nodeInfo[hook] = newHookInfo;
// editorService.update(nodeInfo);
// }
// });
// // 更新绑定关系
// const oldRelation = await this.getCompRelation();
// const oldCodeIds = oldRelation[compId];
// // 不能直接splice修改原数组,会导致绑定关系更新错乱
// const newCodeIds = oldCodeIds.filter((item) => item !== codeId);
// this.setCompRelation(compId, newCodeIds);
// return true;
// }
/**
* id解除绑定关系
* @param {MNode} compId

View File

@ -392,3 +392,6 @@ export enum CodeSelectOp {
/** 单选修改 */
CHANGE = 'change',
}
// 代码块草稿localStorage key
export const CODE_DRAFT_STORAGE_KEY = 'magicCodeDraft';

View File

@ -16,13 +16,14 @@
* limitations under the License.
*/
import serialize from 'serialize-javascript';
import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema';
import { NodeType } from '@tmagic/schema';
import type StageCore from '@tmagic/stage';
import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
import { Layout } from '../type';
export const COPY_STORAGE_KEY = '$MagicEditorCopyData';
/**
@ -237,3 +238,10 @@ export const fixNodePosition = (config: MNode, parent: MContainer, stage: StageC
left: fixNodeLeft(config, parent, stage?.renderer.contentWindow?.document),
};
};
// 序列化配置
export const serializeConfig = (config: any) =>
serialize(config, {
space: 2,
unsafe: true,
}).replace(/"(\w+)":\s/g, '$1: ');