style(editor): 完善service use-plugin的ts类型定义

This commit is contained in:
roymondchen 2024-03-05 14:55:41 +08:00
parent 5cf137e5e8
commit 16e45cb45d
16 changed files with 263 additions and 156 deletions

View File

@ -84,6 +84,7 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sass": "^1.35.1", "sass": "^1.35.1",
"tsc-alias": "^1.8.5", "tsc-alias": "^1.8.5",
"type-fest": "^4.10.3",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^5.0.7", "vite": "^5.0.7",
"vue-tsc": "^1.8.25" "vue-tsc": "^1.8.25"

View File

@ -194,6 +194,9 @@ export default class extends EventEmitter {
}); });
} }
/**
* @deprecated 使usePlugin代替
*/
public use(options: Record<string, Function>) { public use(options: Record<string, Function>) {
Object.entries(options).forEach(([methodName, method]: [string, Function]) => { Object.entries(options).forEach(([methodName, method]: [string, Function]) => {
if (typeof method === 'function') this.middleware[methodName].push(method); if (typeof method === 'function') this.middleware[methodName].push(method);

View File

@ -18,16 +18,24 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { keys, pick } from 'lodash-es'; import { keys, pick } from 'lodash-es';
import type { Writable } from 'type-fest';
import type { ColumnConfig } from '@tmagic/form'; import type { ColumnConfig } from '@tmagic/form';
import type { CodeBlockContent, CodeBlockDSL, Id } from '@tmagic/schema'; import type { CodeBlockContent, CodeBlockDSL, Id } from '@tmagic/schema';
import type { CodeState } from '@editor/type'; import type { AsyncHookPlugin, CodeState } from '@editor/type';
import { CODE_DRAFT_STORAGE_KEY } from '@editor/type'; import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
import { getConfig } from '@editor/utils/config'; import { getConfig } from '@editor/utils/config';
import BaseService from './BaseService'; import BaseService from './BaseService';
const canUsePluginMethods = {
async: ['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds'] as const,
sync: [],
};
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
class CodeBlock extends BaseService { class CodeBlock extends BaseService {
private state = reactive<CodeState>({ private state = reactive<CodeState>({
codeDsl: null, codeDsl: null,
@ -38,13 +46,7 @@ class CodeBlock extends BaseService {
}); });
constructor() { constructor() {
super([ super(canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })));
{ name: 'setCodeDslById', isAsync: true },
{ name: 'setEditStatus', isAsync: true },
{ name: 'setCombineIds', isAsync: true },
{ name: 'setUndeleteableList', isAsync: true },
{ name: 'deleteCodeDslByIds', isAsync: true },
]);
} }
/** /**
@ -243,6 +245,10 @@ class CodeBlock extends BaseService {
this.removeAllListeners(); this.removeAllListeners();
this.removeAllPlugins(); this.removeAllPlugins();
} }
public usePlugin(options: AsyncHookPlugin<AsyncMethodName, CodeBlock>): void {
super.usePlugin(options);
}
} }
export type CodeBlockService = CodeBlock; export type CodeBlockService = CodeBlock;

View File

@ -1,12 +1,13 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { Writable } from 'type-fest';
import type { EventOption } from '@tmagic/core'; import type { EventOption } from '@tmagic/core';
import type { FormConfig } from '@tmagic/form'; import type { FormConfig } from '@tmagic/form';
import type { DataSourceSchema } from '@tmagic/schema'; import type { DataSourceSchema } from '@tmagic/schema';
import { guid } from '@tmagic/utils'; import { guid } from '@tmagic/utils';
import type { DatasourceTypeOption } from '@editor/type'; import type { DatasourceTypeOption, SyncHookPlugin } from '@editor/type';
import { getFormConfig, getFormValue } from '@editor/utils/data-source'; import { getFormConfig, getFormValue } from '@editor/utils/data-source';
import BaseService from './BaseService'; import BaseService from './BaseService';
@ -22,6 +23,27 @@ interface State {
} }
type StateKey = keyof State; type StateKey = keyof State;
const canUsePluginMethods = {
async: [],
sync: [
'getFormConfig',
'setFormConfig',
'getFormValue',
'setFormValue',
'getFormEvent',
'setFormEvent',
'getFormMethod',
'setFormMethod',
'add',
'update',
'remove',
'createId',
] as const,
};
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
class DataSource extends BaseService { class DataSource extends BaseService {
private state = reactive<State>({ private state = reactive<State>({
datasourceTypeList: [], datasourceTypeList: [],
@ -34,20 +56,7 @@ class DataSource extends BaseService {
}); });
constructor() { constructor() {
super([ super(canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })));
{ name: 'getFormConfig', isAsync: false },
{ name: 'setFormConfig', isAsync: false },
{ name: 'getFormValue', isAsync: false },
{ name: 'setFormValue', isAsync: false },
{ name: 'getFormEvent', isAsync: false },
{ name: 'setFormEvent', isAsync: false },
{ name: 'getFormMethod', isAsync: false },
{ name: 'setFormMethod', isAsync: false },
{ name: 'add', isAsync: false },
{ name: 'update', isAsync: false },
{ name: 'remove', isAsync: false },
{ name: 'createId', isAsync: false },
]);
} }
public set<K extends StateKey, T extends State[K]>(name: K, value: T) { public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
@ -123,6 +132,10 @@ class DataSource extends BaseService {
this.emit('remove', id); this.emit('remove', id);
} }
public createId(): string {
return `ds_${guid()}`;
}
public getDataSourceById(id: string) { public getDataSourceById(id: string) {
return this.get('dataSources').find((ds) => ds.id === id); return this.get('dataSources').find((ds) => ds.id === id);
} }
@ -137,8 +150,8 @@ class DataSource extends BaseService {
this.removeAllPlugins(); this.removeAllPlugins();
} }
private createId(): string { public usePlugin(options: SyncHookPlugin<SyncMethodName, DataSource>): void {
return `ds_${guid()}`; super.usePlugin(options);
} }
} }

View File

@ -18,6 +18,7 @@
import { reactive, toRaw } from 'vue'; import { reactive, toRaw } from 'vue';
import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es'; import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
import { Writable } from 'type-fest';
import { DepTargetType } from '@tmagic/dep'; import { DepTargetType } from '@tmagic/dep';
import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema'; import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
@ -29,7 +30,15 @@ import propsService from '@editor/services//props';
import depService from '@editor/services/dep'; import depService from '@editor/services/dep';
import historyService from '@editor/services/history'; import historyService from '@editor/services/history';
import storageService, { Protocol } from '@editor/services/storage'; import storageService, { Protocol } from '@editor/services/storage';
import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState, StoreStateKey } from '@editor/type'; import type {
AddMNode,
AsyncHookPlugin,
EditorNodeInfo,
PastePosition,
StepValue,
StoreState,
StoreStateKey,
} from '@editor/type';
import { LayerOffset, Layout } from '@editor/type'; import { LayerOffset, Layout } from '@editor/type';
import { import {
change2Fixed, change2Fixed,
@ -46,6 +55,36 @@ import {
} from '@editor/utils/editor'; } from '@editor/utils/editor';
import { beforePaste, getAddParent } from '@editor/utils/operator'; import { beforePaste, getAddParent } from '@editor/utils/operator';
const canUsePluginMethods = {
async: [
'getLayout',
'highlight',
'select',
'multiSelect',
'doAdd',
'add',
'doRemove',
'remove',
'doUpdate',
'update',
'sort',
'copy',
'paste',
'doPaste',
'doAlignCenter',
'alignCenter',
'moveLayer',
'moveToContainer',
'dragTo',
'undo',
'redo',
'move',
] as const,
sync: [],
};
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
class Editor extends BaseService { class Editor extends BaseService {
public state: StoreState = reactive({ public state: StoreState = reactive({
root: null, root: null,
@ -65,29 +104,7 @@ class Editor extends BaseService {
constructor() { constructor() {
super( super(
[ canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
{ name: 'getLayout', isAsync: true },
{ name: 'select', isAsync: true },
{ name: 'doAdd', isAsync: true },
{ name: 'add', isAsync: true },
{ name: 'doRemove', isAsync: true },
{ name: 'remove', isAsync: true },
{ name: 'doUpdate', isAsync: true },
{ name: 'update', isAsync: true },
{ name: 'sort', isAsync: true },
{ name: 'copy', isAsync: true },
{ name: 'paste', isAsync: true },
{ name: 'doPaste', isAsync: true },
{ name: 'doAlignCenter', isAsync: true },
{ name: 'alignCenter', isAsync: true },
{ name: 'moveLayer', isAsync: true },
{ name: 'moveToContainer', isAsync: true },
{ name: 'move', isAsync: true },
{ name: 'undo', isAsync: true },
{ name: 'redo', isAsync: true },
{ name: 'highlight', isAsync: true },
{ name: 'dragTo', isAsync: true },
],
// 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用 // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
['select', 'update', 'moveLayer'], ['select', 'update', 'moveLayer'],
); );
@ -696,7 +713,7 @@ class Editor extends BaseService {
public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> { public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> {
propsService.clearRelateId(); propsService.clearRelateId();
const pasteConfigs = await beforePaste(position, cloneDeep(config)); const pasteConfigs = beforePaste(position, cloneDeep(config));
return pasteConfigs; return pasteConfigs;
} }
@ -985,6 +1002,10 @@ class Editor extends BaseService {
this.get('modifiedNodeIds').clear(); this.get('modifiedNodeIds').clear();
} }
public usePlugin(options: AsyncHookPlugin<AsyncMethodName, Editor>): void {
super.usePlugin(options);
}
private addModifiedNodeId(id: Id) { private addModifiedNodeId(id: Id) {
if (!this.isHistoryStateChange) { if (!this.isHistoryStateChange) {
this.get('modifiedNodeIds').set(id, id); this.get('modifiedNodeIds').set(id, id);

View File

@ -18,6 +18,7 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { cloneDeep, mergeWith } from 'lodash-es'; import { cloneDeep, mergeWith } from 'lodash-es';
import { Writable } from 'type-fest';
import { DepTargetType } from '@tmagic/dep'; import { DepTargetType } from '@tmagic/dep';
import type { FormConfig } from '@tmagic/form'; import type { FormConfig } from '@tmagic/form';
@ -26,11 +27,26 @@ import { getValueByKeyPath, guid, setValueByKeyPath, toLine } from '@tmagic/util
import depService from '@editor/services/dep'; import depService from '@editor/services/dep';
import editorService from '@editor/services/editor'; import editorService from '@editor/services/editor';
import type { PropsState } from '@editor/type'; import type { AsyncHookPlugin, PropsState, SyncHookPlugin } from '@editor/type';
import { fillConfig } from '@editor/utils/props'; import { fillConfig } from '@editor/utils/props';
import BaseService from './BaseService'; import BaseService from './BaseService';
const canUsePluginMethods = {
async: [
'setPropsConfig',
'getPropsConfig',
'setPropsValue',
'getPropsValue',
'fillConfig',
'getDefaultPropsValue',
] as const,
sync: ['createId', 'setNewItemId'] as const,
};
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
class Props extends BaseService { class Props extends BaseService {
private state = reactive<PropsState>({ private state = reactive<PropsState>({
propsConfigMap: {}, propsConfigMap: {},
@ -40,14 +56,8 @@ class Props extends BaseService {
constructor() { constructor() {
super([ super([
{ name: 'setPropsConfig', isAsync: true }, ...canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
{ name: 'getPropsConfig', isAsync: true }, ...canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })),
{ name: 'setPropsValue', isAsync: true },
{ name: 'getPropsValue', isAsync: true },
{ name: 'createId', isAsync: false },
{ name: 'setNewItemId', isAsync: true },
{ name: 'fillConfig', isAsync: true },
{ name: 'getDefaultPropsValue', isAsync: true },
]); ]);
} }
@ -62,11 +72,6 @@ class Props extends BaseService {
return fillConfig(config, typeof labelWidth !== 'function' ? labelWidth : '80px'); return fillConfig(config, typeof labelWidth !== 'function' ? labelWidth : '80px');
} }
/**
*
* @param type
* @param config
*/
public async setPropsConfig(type: string, config: FormConfig) { public async setPropsConfig(type: string, config: FormConfig) {
this.state.propsConfigMap[type] = await this.fillConfig(Array.isArray(config) ? config : [config]); this.state.propsConfigMap[type] = await this.fillConfig(Array.isArray(config) ? config : [config]);
} }
@ -115,16 +120,14 @@ class Props extends BaseService {
return value; return value;
} }
const [id, defaultPropsValue, data] = await Promise.all([ const id = this.createId(type);
this.createId(type), const defaultPropsValue = this.getDefaultPropsValue(type);
this.getDefaultPropsValue(type), const data = this.setNewItemId(
this.setNewItemId( cloneDeep({
cloneDeep({ type,
type, ...defaultValue,
...defaultValue, } as any),
} as any), );
),
]);
return { return {
id, id,
@ -133,7 +136,7 @@ class Props extends BaseService {
}; };
} }
public async createId(type: string | number): Promise<string> { public createId(type: string | number): string {
return `${type}_${guid()}`; return `${type}_${guid()}`;
} }
@ -144,16 +147,16 @@ class Props extends BaseService {
* @param {Boolean} force ID * @param {Boolean} force ID
*/ */
/* eslint no-param-reassign: ["error", { "props": false }] */ /* eslint no-param-reassign: ["error", { "props": false }] */
public async setNewItemId(config: MNode, force = true) { public setNewItemId(config: MNode, force = true) {
if (force || editorService.getNodeById(config.id)) { if (force || editorService.getNodeById(config.id)) {
const newId = await this.createId(config.type || 'component'); const newId = this.createId(config.type || 'component');
this.setRelateId(config.id, newId); this.setRelateId(config.id, newId);
config.id = newId; config.id = newId;
} }
if (config.items && Array.isArray(config.items)) { if (config.items && Array.isArray(config.items)) {
for (const item of config.items) { for (const item of config.items) {
await this.setNewItemId(item); this.setNewItemId(item);
} }
} }
@ -165,7 +168,7 @@ class Props extends BaseService {
* @param type * @param type
* @returns Object * @returns Object
*/ */
public async getDefaultPropsValue(type: string) { public getDefaultPropsValue(type: string) {
return ['page', 'container'].includes(type) return ['page', 'container'].includes(type)
? { ? {
type, type,
@ -227,6 +230,10 @@ class Props extends BaseService {
this.removeAllPlugins(); this.removeAllPlugins();
} }
public usePlugin(options: AsyncHookPlugin<AsyncMethodName, Props> & SyncHookPlugin<SyncMethodName, Props>): void {
super.usePlugin(options);
}
/** /**
* setNewItemId前后映射关系 * setNewItemId前后映射关系
* @param oldId ID * @param oldId ID

View File

@ -1,11 +1,19 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import type { Writable } from 'type-fest';
import StageCore from '@tmagic/stage'; import StageCore from '@tmagic/stage';
import { useStage } from '@editor/hooks/use-stage'; import { useStage } from '@editor/hooks/use-stage';
import BaseService from '@editor/services//BaseService'; import BaseService from '@editor/services//BaseService';
import editorService from '@editor/services//editor'; import editorService from '@editor/services//editor';
import type { StageOptions, StageOverlayState } from '@editor/type'; import type { StageOptions, StageOverlayState, SyncHookPlugin } from '@editor/type';
const canUsePluginMethods = {
async: [],
sync: ['openOverlay', 'closeOverlay', 'updateOverlay', 'createStage'] as const,
};
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
class StageOverlay extends BaseService { class StageOverlay extends BaseService {
private state: StageOverlayState = reactive({ private state: StageOverlayState = reactive({
@ -20,12 +28,7 @@ class StageOverlay extends BaseService {
}); });
constructor() { constructor() {
super([ super(canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })));
{ name: 'openOverlay', isAsync: false },
{ name: 'closeOverlay', isAsync: false },
{ name: 'updateOverlay', isAsync: false },
{ name: 'createStage', isAsync: false },
]);
this.get('wrapDiv').classList.add('tmagic-editor-sub-stage-wrap'); this.get('wrapDiv').classList.add('tmagic-editor-sub-stage-wrap');
} }
@ -111,6 +114,10 @@ class StageOverlay extends BaseService {
}); });
} }
public usePlugin(options: SyncHookPlugin<SyncMethodName, StageOverlay>): void {
super.usePlugin(options);
}
private createContentEl() { private createContentEl() {
const sourceEl = this.get('sourceEl'); const sourceEl = this.get('sourceEl');
if (!sourceEl) return; if (!sourceEl) return;

View File

@ -1,5 +1,7 @@
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
import type { Writable } from 'type-fest';
import type { SyncHookPlugin } from '@editor/type';
import { getConfig } from '@editor/utils/config'; import { getConfig } from '@editor/utils/config';
import BaseService from './BaseService'; import BaseService from './BaseService';
@ -17,6 +19,13 @@ export enum Protocol {
BOOLEAN = 'boolean', BOOLEAN = 'boolean',
} }
const canUsePluginMethods = {
async: [],
sync: ['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem'] as const,
};
type SyncMethodName = Writable<(typeof canUsePluginMethods)['sync']>;
/** /**
* *
*/ */
@ -25,14 +34,7 @@ export class WebStorage extends BaseService {
private namespace = 'tmagic'; private namespace = 'tmagic';
constructor() { constructor() {
super([ super(canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })));
{ name: 'getStorage', isAsync: false },
{ name: 'getNamespace', isAsync: false },
{ name: 'clear', isAsync: false },
{ name: 'getItem', isAsync: false },
{ name: 'removeItem', isAsync: false },
{ name: 'setItem', isAsync: false },
]);
} }
/** /**
@ -123,6 +125,10 @@ export class WebStorage extends BaseService {
this.removeAllPlugins(); this.removeAllPlugins();
} }
public usePlugin(options: SyncHookPlugin<SyncMethodName, WebStorage>): void {
super.usePlugin(options);
}
private getValueAndProtocol(value: string | null) { private getValueAndProtocol(value: string | null) {
let protocol = ''; let protocol = '';

View File

@ -17,11 +17,12 @@
*/ */
import { reactive } from 'vue'; import { reactive } from 'vue';
import type { Writable } from 'type-fest';
import { convertToNumber } from '@tmagic/utils'; import { convertToNumber } from '@tmagic/utils';
import editorService from '@editor/services/editor'; import editorService from '@editor/services/editor';
import type { StageRect, UiState } from '@editor/type'; import type { AsyncHookPlugin, StageRect, UiState } from '@editor/type';
import BaseService from './BaseService'; import BaseService from './BaseService';
@ -50,12 +51,16 @@ const state = reactive<UiState>({
hideSlideBar: false, hideSlideBar: false,
}); });
const canUsePluginMethods = {
async: ['zoom', 'calcZoom'] as const,
sync: [] as const,
};
type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
class Ui extends BaseService { class Ui extends BaseService {
constructor() { constructor() {
super([ super(canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })));
{ name: 'zoom', isAsync: true },
{ name: 'calcZoom', isAsync: true },
]);
} }
public set<K extends keyof UiState, T extends UiState[K]>(name: K, value: T) { public set<K extends keyof UiState, T extends UiState[K]>(name: K, value: T) {
@ -134,6 +139,10 @@ class Ui extends BaseService {
this.removeAllPlugins(); this.removeAllPlugins();
} }
public usePlugin(options: AsyncHookPlugin<AsyncMethodName, Ui>): void {
super.usePlugin(options);
}
private async setStageRect(value: StageRect) { private async setStageRect(value: StageRect) {
state.stageRect = { state.stageRect = {
...state.stageRect, ...state.stageRect,

View File

@ -17,6 +17,7 @@
*/ */
import type { Component } from 'vue'; import type { Component } from 'vue';
import type { PascalCasedProperties } from 'type-fest';
import type { ColumnConfig, FilterFunction, FormConfig, FormItem } from '@tmagic/form'; import type { ColumnConfig, FilterFunction, FormConfig, FormItem } from '@tmagic/form';
import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema'; import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
@ -670,3 +671,35 @@ export interface TreeNodeData {
items?: TreeNodeData[]; items?: TreeNodeData[];
[key: string]: any; [key: string]: any;
} }
export type AsyncBeforeHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
[K in Value[number]]?: (...args: Parameters<C[K]>) => Promise<Parameters<C[K]>>;
};
export type AsyncAfterHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
[K in Value[number]]?: (result: Awaited<ReturnType<C[K]>>, ...args: Parameters<C[K]>) => ReturnType<C[K]>;
};
export type SyncBeforeHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
[K in Value[number]]?: (...args: Parameters<C[K]>) => Parameters<C[K]>;
};
export type SyncAfterHook<Value extends Array<string>, C extends Record<Value[number], (...args: any) => any>> = {
[K in Value[number]]?: (result: ReturnType<C[K]>, ...args: Parameters<C[K]>) => ReturnType<C[K]>;
};
export type AddPrefixToObject<T, P extends string> = {
[K in keyof T as K extends string ? `${P}${K}` : never]: T[K];
};
export type AsyncHookPlugin<
T extends Array<string>,
C extends Record<T[number], (...args: any) => any>,
> = AddPrefixToObject<PascalCasedProperties<AsyncBeforeHook<T, C>>, 'before'> &
AddPrefixToObject<PascalCasedProperties<AsyncAfterHook<T, C>>, 'after'>;
export type SyncHookPlugin<
T extends Array<string>,
C extends Record<T[number], (...args: any) => any>,
> = AddPrefixToObject<PascalCasedProperties<SyncBeforeHook<T, C>>, 'before'> &
AddPrefixToObject<PascalCasedProperties<SyncAfterHook<T, C>>, 'after'>;

View File

@ -15,55 +15,53 @@ import { generatePageNameByApp, getInitPositionStyle } from '@editor/utils/edito
* @param config () * @param config ()
* @returns * @returns
*/ */
export const beforePaste = async (position: PastePosition, config: MNode[]): Promise<MNode[]> => { export const beforePaste = (position: PastePosition, config: MNode[]): MNode[] => {
if (!config[0]?.style) return config; if (!config[0]?.style) return config;
const curNode = editorService.get('node'); const curNode = editorService.get('node');
// 将数组中第一个元素的坐标作为参照点 // 将数组中第一个元素的坐标作为参照点
const { left: referenceLeft, top: referenceTop } = config[0].style; const { left: referenceLeft, top: referenceTop } = config[0].style;
// 坐标校准后的粘贴数据 // 坐标校准后的粘贴数据
const pasteConfigs: MNode[] = await Promise.all( const pasteConfigs: MNode[] = config.map((configItem: MNode): MNode => {
config.map(async (configItem: MNode): Promise<MNode> => { // 解构 position 对象,从 position 删除 offsetX、offsetY字段
// 解构 position 对象,从 position 删除 offsetX、offsetY字段 const { offsetX = 0, offsetY = 0, ...positionClone } = position;
const { offsetX = 0, offsetY = 0, ...positionClone } = position; let pastePosition = positionClone;
let pastePosition = positionClone;
if (!isEmpty(pastePosition) && curNode?.items) { if (!isEmpty(pastePosition) && curNode?.items) {
// 如果没有传入粘贴坐标则可能为键盘操作,不再转换 // 如果没有传入粘贴坐标则可能为键盘操作,不再转换
// 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标 // 如果粘贴时选中了容器,则将元素粘贴到容器内,坐标需要转换为相对于容器的坐标
pastePosition = getPositionInContainer(pastePosition, curNode.id); pastePosition = getPositionInContainer(pastePosition, curNode.id);
}
// 将所有待粘贴元素坐标相对于多选第一个元素坐标重新计算,以保证多选粘贴后元素间距不变
if (pastePosition.left && configItem.style?.left) {
pastePosition.left = configItem.style.left - referenceLeft + pastePosition.left;
}
if (pastePosition.top && configItem.style?.top) {
pastePosition.top = configItem.style?.top - referenceTop + pastePosition.top;
}
const pasteConfig = propsService.setNewItemId(configItem, false);
if (pasteConfig.style) {
const { left, top } = pasteConfig.style;
// 判断能转换为数字时,做粘贴偏移量计算
if (typeof left === 'number' || (!!left && !isNaN(Number(left)))) {
pasteConfig.style.left = Number(left) + offsetX;
}
if (typeof top === 'number' || (!!top && !isNaN(Number(top)))) {
pasteConfig.style.top = Number(top) + offsetY;
} }
// 将所有待粘贴元素坐标相对于多选第一个元素坐标重新计算,以保证多选粘贴后元素间距不变 pasteConfig.style = {
if (pastePosition.left && configItem.style?.left) { ...pasteConfig.style,
pastePosition.left = configItem.style.left - referenceLeft + pastePosition.left; ...pastePosition,
} };
if (pastePosition.top && configItem.style?.top) { }
pastePosition.top = configItem.style?.top - referenceTop + pastePosition.top; const root = editorService.get('root');
} if ((isPage(pasteConfig) || isPageFragment(pasteConfig)) && root) {
const pasteConfig = await propsService.setNewItemId(configItem, false); pasteConfig.name = generatePageNameByApp(root, isPage(pasteConfig) ? NodeType.PAGE : NodeType.PAGE_FRAGMENT);
}
if (pasteConfig.style) { return pasteConfig as MNode;
const { left, top } = pasteConfig.style; });
// 判断能转换为数字时,做粘贴偏移量计算
if (typeof left === 'number' || (!!left && !isNaN(Number(left)))) {
pasteConfig.style.left = Number(left) + offsetX;
}
if (typeof top === 'number' || (!!top && !isNaN(Number(top)))) {
pasteConfig.style.top = Number(top) + offsetY;
}
pasteConfig.style = {
...pasteConfig.style,
...pastePosition,
};
}
const root = editorService.get('root');
if ((isPage(pasteConfig) || isPageFragment(pasteConfig)) && root) {
pasteConfig.name = generatePageNameByApp(root, isPage(pasteConfig) ? NodeType.PAGE : NodeType.PAGE_FRAGMENT);
}
return pasteConfig as MNode;
}),
);
return pasteConfigs; return pasteConfigs;
}; };

View File

@ -328,7 +328,7 @@ export const displayTabConfig: TabPaneConfig = {
* @param config * @param config
* @returns Object * @returns Object
*/ */
export const fillConfig = (config: FormConfig = [], labelWidth = '80px') => [ export const fillConfig = (config: FormConfig = [], labelWidth = '80px'): FormConfig => [
{ {
type: 'tab', type: 'tab',
labelWidth, labelWidth,

View File

@ -11,17 +11,17 @@ test('createId', async () => {
}); });
describe('setNewItemId', () => { describe('setNewItemId', () => {
test('普通', async () => { test('普通', () => {
const config = { const config = {
id: 1, id: 1,
type: 'text', type: 'text',
}; };
// 将组件与组件的子元素配置中的id都设置成一个新的ID // 将组件与组件的子元素配置中的id都设置成一个新的ID
await props.setNewItemId(config); props.setNewItemId(config);
expect(config.id === 1).toBeFalsy(); expect(config.id === 1).toBeFalsy();
}); });
test('items', async () => { test('items', () => {
const config = { const config = {
id: 1, id: 1,
type: NodeType.PAGE, type: NodeType.PAGE,
@ -32,7 +32,7 @@ describe('setNewItemId', () => {
}, },
], ],
}; };
await props.setNewItemId(config); props.setNewItemId(config);
expect(config.id === 1).toBeFalsy(); expect(config.id === 1).toBeFalsy();
expect(config.items[0].id === 2).toBeFalsy(); expect(config.items[0].id === 2).toBeFalsy();
}); });

View File

@ -227,7 +227,7 @@ asyncLoadJs(`${VITE_ENTRY_PATH}/ds-value/index.umd.cjs`).then(() => {
save(); save();
editorService.usePlugin({ editorService.usePlugin({
beforeDoAdd: (config: MNode, parent?: MContainer | null) => { beforeDoAdd: async (config: MNode, parent: MContainer) => {
if (config.type === 'overlay') { if (config.type === 'overlay') {
config.style = { config.style = {
...config.style, ...config.style,
@ -235,7 +235,7 @@ editorService.usePlugin({
top: 0, top: 0,
}; };
return [config, editorService.get('page')]; return [config, editorService.get('page') as MContainer];
} }
return [config, parent]; return [config, parent];

11
pnpm-lock.yaml generated
View File

@ -377,6 +377,9 @@ importers:
tsc-alias: tsc-alias:
specifier: ^1.8.5 specifier: ^1.8.5
version: 1.8.5 version: 1.8.5
type-fest:
specifier: ^4.10.3
version: 4.11.1
typescript: typescript:
specifier: ^5.0.4 specifier: ^5.0.4
version: 5.0.4 version: 5.0.4
@ -9552,7 +9555,7 @@ packages:
dependencies: dependencies:
find-up: 6.3.0 find-up: 6.3.0
read-pkg: 8.1.0 read-pkg: 8.1.0
type-fest: 4.6.0 type-fest: 4.11.1
dev: true dev: true
/read-pkg-up@7.0.1: /read-pkg-up@7.0.1:
@ -9581,7 +9584,7 @@ packages:
'@types/normalize-package-data': 2.4.1 '@types/normalize-package-data': 2.4.1
normalize-package-data: 6.0.0 normalize-package-data: 6.0.0
parse-json: 7.1.1 parse-json: 7.1.1
type-fest: 4.6.0 type-fest: 4.11.1
dev: true dev: true
/readable-stream@3.6.2: /readable-stream@3.6.2:
@ -10491,8 +10494,8 @@ packages:
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
dev: true dev: true
/type-fest@4.6.0: /type-fest@4.11.1:
resolution: {integrity: sha512-rLjWJzQFOq4xw7MgJrCZ6T1jIOvvYElXT12r+y0CC6u67hegDHaxcPqb2fZHOGlqxugGQPNB1EnTezjBetkwkw==} resolution: {integrity: sha512-MFMf6VkEVZAETidGGSYW2B1MjXbGX+sWIywn2QPEaJ3j08V+MwVRHMXtf2noB8ENJaD0LIun9wh5Z6OPNf1QzQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
dev: true dev: true

View File

@ -21,7 +21,7 @@ export const useRuntime = ({
fillConfig = (config) => config, fillConfig = (config) => config,
}: { }: {
plugins?: Plugin[]; plugins?: Plugin[];
fillConfig?: (config: FormConfig) => FormConfig; fillConfig?: (config: FormConfig, mForm: any) => FormConfig;
} = {}) => { } = {}) => {
const render = (stage: StageCore) => { const render = (stage: StageCore) => {
injectStyle(stage.renderer.getDocument()!, cssStyle); injectStyle(stage.renderer.getDocument()!, cssStyle);
@ -60,24 +60,24 @@ export const useRuntime = ({
}; };
propsService.usePlugin({ propsService.usePlugin({
afterFillConfig(config: FormConfig, itemConfig: FormConfig) { async afterFillConfig(config: FormConfig, itemConfig: FormConfig, labelWidth = '80px') {
return [ return [
{ {
type: 'tab', type: 'tab',
items: [ items: [
{ {
title: '属性', title: '属性',
labelWidth: '80px', labelWidth,
items: [...commonConfig, ...itemConfig], items: [...commonConfig, ...itemConfig],
}, },
], ],
}, },
]; ] as FormConfig;
}, },
}); });
editorService.usePlugin({ editorService.usePlugin({
afterGetLayout() { async afterGetLayout() {
return Layout.RELATIVE; return Layout.RELATIVE;
}, },
}); });