From 574e03f685e44d08fb43207723af79a08ee0a22f Mon Sep 17 00:00:00 2001 From: roymondchen Date: Thu, 4 Aug 2022 18:44:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E5=AE=8C=E5=96=84storageServic?= =?UTF-8?q?e=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增namespace,setItem/getItem时自动key自动加上namespace;setItem可以指定协议;getItem时根据协议自动解析 fix #224 --- packages/editor/src/services/editor.ts | 24 ++-- packages/editor/src/services/storage.ts | 108 +++++++++++++++--- .../editor/tests/unit/services/editor.spec.ts | 4 +- 3 files changed, 100 insertions(+), 36 deletions(-) diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index ee97706a..27f7d93b 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -18,7 +18,6 @@ import { reactive, toRaw } from 'vue'; import { cloneDeep, mergeWith, uniq } from 'lodash-es'; -import serialize from 'serialize-javascript'; import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema'; import { NodeType } from '@tmagic/schema'; @@ -26,7 +25,7 @@ import StageCore from '@tmagic/stage'; import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils'; import historyService, { StepValue } from '@editor/services/history'; -import storageService from '@editor/services/storage'; +import storageService, { Protocol } from '@editor/services/storage'; import type { AddMNode, EditorNodeInfo, PastePosition, StoreState } from '@editor/type'; import { LayerOffset, Layout } from '@editor/type'; import { @@ -483,7 +482,9 @@ class Editor extends BaseService { * @returns 组件节点配置 */ public async copy(config: MNode | MNode[]): Promise { - await storageService.setItem(COPY_STORAGE_KEY, serialize(Array.isArray(config) ? config : [config])); + await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], { + protocol: Protocol.OBJECT, + }); } /** @@ -492,19 +493,10 @@ class Editor extends BaseService { * @returns 添加后的组件节点配置 */ public async paste(position: PastePosition = {}): Promise { - const configStr = await storageService.getItem(COPY_STORAGE_KEY); - // eslint-disable-next-line prefer-const - let config: any = {}; - if (!configStr) { - return; - } - try { - // eslint-disable-next-line no-eval - eval(`config = ${configStr}`); - } catch (e) { - console.error(e); - return; - } + const config = await storageService.getItem(COPY_STORAGE_KEY); + + if (!config) return; + const pasteConfigs = await beforePaste(position, config); return await this.multiAdd(pasteConfigs); diff --git a/packages/editor/src/services/storage.ts b/packages/editor/src/services/storage.ts index 0edc3e56..a2c907d6 100644 --- a/packages/editor/src/services/storage.ts +++ b/packages/editor/src/services/storage.ts @@ -1,39 +1,80 @@ +import serialize from 'serialize-javascript'; + import BaseService from './BaseService'; +interface Options { + namespace?: string; + protocol?: Protocol; +} + +export enum Protocol { + OBJECT = 'object', + JSON = 'json', + STRING = 'string', + NUMBER = 'number', + BOOLEAN = 'boolean', +} + /** * 数据存储服务 */ export class WebStorage extends BaseService { + private storage: Storage = globalThis.localStorage; + private namespace = 'tmagic'; + constructor() { - super(['getStorage', 'clear', 'getItem', 'removeItem', 'setItem']); + super(['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem']); } /** * 获取数据存储对象,可以通过 * const storageService = new StorageService(); - * storageService.use({ + * storageService.usePlugin({ * // 替换存储对象为 sessionStorage - * async getStorage(): Promise { - * return window.sessionStorage; + * async afterGetStorage(): Promise { + * return window.sessionStorage; * }, * }); */ public async getStorage(): Promise { - return globalThis.localStorage; + return this.storage; } + + public async getNamespace(): Promise { + return this.namespace; + } + /** - * 清理,支持storageService.use + * 清理,支持storageService.usePlugin */ public async clear(): Promise { const storage = await this.getStorage(); storage.clear(); } /** - * 获取存储项,支持storageService.use + * 获取存储项,支持storageService.usePlugin */ - public async getItem(key: string): Promise { - const storage = await this.getStorage(); - return storage.getItem(key); + public async getItem(key: string, options: Options = {}): Promise { + const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + const { protocol = options.protocol, item } = this.getValueAndProtocol( + storage.getItem(`${options.namespace || namespace}:${key}`), + ); + + if (item === null) return null; + + switch (protocol) { + case Protocol.OBJECT: + // eslint-disable-next-line no-eval + return eval(`(${item})`); + case Protocol.JSON: + return JSON.parse(item); + case Protocol.NUMBER: + return Number(item); + case Protocol.BOOLEAN: + return Boolean(item); + default: + return item; + } } /** * 获取指定索引位置的key @@ -42,20 +83,51 @@ export class WebStorage extends BaseService { const storage = await this.getStorage(); return storage.key(index); } + /** - * 移除存储项,支持storageService.use + * 移除存储项,支持storageService.usePlugin */ - public async removeItem(key: string): Promise { - const storage = await this.getStorage(); - storage.removeItem(key); + public async removeItem(key: string, options: Options = {}): Promise { + const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + storage.removeItem(`${options.namespace || namespace}:${key}`); } + /** - * 设置存储项,支持storageService.use + * 设置存储项,支持storageService.usePlugin */ - public async setItem(key: string, value: string): Promise { - const storage = await this.getStorage(); - storage.setItem(key, value); + public async setItem(key: string, value: any, options: Options = {}): Promise { + const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]); + let item = value; + const protocol = options.protocol ? `${options.protocol}:` : ''; + if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) { + item = `${protocol}${value}`; + } else { + item = `${protocol}${serialize(value)}`; + } + storage.setItem(`${options.namespace || namespace}:${key}`, item); + } + + private getValueAndProtocol(value: string | null) { + let protocol = ''; + + if (value === null) { + return { + item: value, + protocol, + }; + } + + const item = value.replace(new RegExp(`^(${Object.values(Protocol).join('|')})(:)(.+)`), (_$0, $1, _$2, $3) => { + protocol = $1; + return $3; + }); + + return { + protocol, + item, + }; } } + export type StorageService = WebStorage; export default new WebStorage(); diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index 5d9fe1dc..0c234d21 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -354,7 +354,7 @@ describe('copy', () => { const node = editorService.getNodeById(NodeId.NODE_ID2); await editorService.copy(node!); const str = await storageService.getItem(COPY_STORAGE_KEY); - expect(str).toBe(JSON.stringify([node])); + expect(str).toHaveLength(1); }); }); @@ -372,7 +372,7 @@ describe('paste', () => { }); test('空', async () => { - globalThis.localStorage.clear(); + await storageService.clear(); const newNode = await editorService.paste({ left: 0, top: 0 }); expect(newNode).toBeUndefined(); });