diff --git a/packages/abc/onboarding/demo/basic.md b/packages/abc/onboarding/demo/basic.md index 30d21950b2..170ed6357b 100644 --- a/packages/abc/onboarding/demo/basic.md +++ b/packages/abc/onboarding/demo/basic.md @@ -15,7 +15,8 @@ Simplest of usage. ```ts import { Component } from '@angular/core'; -import { OnboardingService } from '@delon/abc/onboarding'; + +import { OnboardingConfig, OnboardingService } from '@delon/abc/onboarding'; import { _HttpClient } from '@delon/theme'; import { NzMessageService } from 'ng-zorro-antd/message'; @@ -28,27 +29,49 @@ import { NzMessageService } from 'ng-zorro-antd/message'; + - + onboarding.json - `, + ` }) export class DemoComponent { - constructor(private srv: OnboardingService, private msg: NzMessageService, private http: _HttpClient) {} + private def: OnboardingConfig = { + items: [ + { + selectors: '.test1-1', + content: 'The user guidance is to help users better understand and use the product', + width: 300 + }, + { + selectors: '.test1-2', + title: 'Test2', + content: 'The user guidance is to help users better understand and use the product' + }, + { + selectors: '.test1-3', + title: 'Test3', + content: 'The user guidance is to help users better understand and use the product' + } + ] + }; + constructor( + private srv: OnboardingService, + private msg: NzMessageService, + private http: _HttpClient + ) {} handleClick(): void { this.msg.info(`click`); } start(): void { - this.srv.start({ - items: [ - { selectors: '.test1-1', content: 'The user guidance is to help users better understand and use the product', width: 300 }, - { selectors: '.test1-2', title: 'Test2', content: 'The user guidance is to help users better understand and use the product' }, - { selectors: '.test1-3', title: 'Test3', content: 'The user guidance is to help users better understand and use the product' }, - ], - }); + this.srv.start({ ...this.def }); } viaHttp(): void { @@ -57,5 +80,9 @@ export class DemoComponent { this.srv.start(res); }); } + + startOnce(): void { + this.srv.start({ ...this.def, key: 'obs-once' }); + } } ``` diff --git a/packages/abc/onboarding/index.en-US.md b/packages/abc/onboarding/index.en-US.md index 32bcda5ba3..deeedfe21e 100644 --- a/packages/abc/onboarding/index.en-US.md +++ b/packages/abc/onboarding/index.en-US.md @@ -25,6 +25,8 @@ The components only support the use of `OnboardingService` service to build. | Property | Description | Type | Default | |----------|-------------|------|---------| +| `[key]` | Storage identification Key, The default is `localStorage` local storage, allowing the use of `ONBOARDING_STORE_TOKEN` to change the storage method | `string` | - | +| `[keyVersion]` | Current version | `unknown` | - | | `[items]` | Onboarding items | `OnboardingItem[]` | `[]` | | `[mask]` | Whether to show mask or not | `boolean` | `true` | | `[maskClosable]` | Clicking on the mask (area outside the onboarding) to close the onboarding or not | `boolean` | `true` | diff --git a/packages/abc/onboarding/index.zh-CN.md b/packages/abc/onboarding/index.zh-CN.md index 4098b5e354..98977770a4 100644 --- a/packages/abc/onboarding/index.zh-CN.md +++ b/packages/abc/onboarding/index.zh-CN.md @@ -25,6 +25,8 @@ module: import { OnboardingModule } from '@delon/abc/onboarding'; | 成员 | 说明 | 类型 | 默认值 | |----|----|----|-----| +| `[key]` | 存储标识Key;默认 `localStorage` 本地存储,允许使用 `ONBOARDING_STORE_TOKEN` 变更存储方式 | `string` | - | +| `[keyVersion]` | 当前版本 | `unknown` | - | | `[items]` | 引导项列表 | `OnboardingItem[]` | `[]` | | `[mask]` | 是否展示遮罩 | `boolean` | `true` | | `[maskClosable]` | 点击蒙层是否允许关闭 | `boolean` | `true` | diff --git a/packages/abc/onboarding/onboarding.service.ts b/packages/abc/onboarding/onboarding.service.ts index d1f9bbbca3..b401721b3b 100644 --- a/packages/abc/onboarding/onboarding.service.ts +++ b/packages/abc/onboarding/onboarding.service.ts @@ -18,13 +18,14 @@ import { AlainConfigService } from '@delon/util/config'; import type { NzSafeAny } from 'ng-zorro-antd/core/types'; import { OnboardingComponent } from './onboarding.component'; +import { ONBOARDING_STORE_TOKEN, OnBoardingKeyStore } from './onboarding.storage'; import { OnboardingConfig, OnboardingItem, OnboardingOpType } from './onboarding.types'; @Injectable() export class OnboardingService implements OnDestroy { private compRef!: ComponentRef; private op$!: Subscription; - private config!: OnboardingConfig; + private config?: OnboardingConfig; private active = 0; private running$: Subscription | null = null; private _running = false; @@ -49,6 +50,7 @@ export class OnboardingService implements OnDestroy { private router: Router, @Inject(DOCUMENT) private doc: NzSafeAny, private configSrv: AlainConfigService, + @Inject(ONBOARDING_STORE_TOKEN) private keyStoreSrv: OnBoardingKeyStore, @Optional() private directionality: Directionality ) {} @@ -96,6 +98,10 @@ export class OnboardingService implements OnDestroy { } private destroy(): void { + const storeKey = this.config?.key; + if (storeKey != null) { + this.keyStoreSrv.set(storeKey, this.config?.keyVersion); + } this.cancelRunning(); if (this.compRef) { this.appRef.detachView(this.compRef.hostView); @@ -105,7 +111,7 @@ export class OnboardingService implements OnDestroy { } private showItem(isStart: boolean = false): void { - const items = this.config.items!; + const items = this.config?.items!; const item = { position: 'bottomLeft', before: of(true), @@ -142,17 +148,23 @@ export class OnboardingService implements OnDestroy { * 开启新的用户引导流程 */ start(config: OnboardingConfig): void { - if (this.running) { - return; - } - this.destroy(); - this.config = { + const cog: OnboardingConfig = { + keyVersion: '', items: [], mask: true, maskClosable: true, showTotal: false, ...config }; + const storeKey = cog?.key; + if (storeKey != null && this.keyStoreSrv.get(storeKey) === cog.keyVersion) { + return; + } + if (this.running) { + return; + } + this.destroy(); + this.config = cog; this.active = 0; this.type = null; this.attach(); @@ -165,7 +177,7 @@ export class OnboardingService implements OnDestroy { * 下一步 */ next(): void { - if (this._running || this.active + 1 >= this.config.items!.length) { + if (this._running || this.active + 1 >= this.config!.items!.length) { this.done(); return; } diff --git a/packages/abc/onboarding/onboarding.spec.ts b/packages/abc/onboarding/onboarding.spec.ts index 8044fcd989..22e9aa560e 100644 --- a/packages/abc/onboarding/onboarding.spec.ts +++ b/packages/abc/onboarding/onboarding.spec.ts @@ -10,6 +10,7 @@ import { NzSafeAny } from 'ng-zorro-antd/core/types'; import { OnboardingModule } from './onboarding.module'; import { OnboardingService } from './onboarding.service'; +import { ONBOARDING_STORE_TOKEN } from './onboarding.storage'; import { OnboardingConfig, OnboardingOpType } from './onboarding.types'; describe('abc: onboarding', () => { @@ -42,6 +43,20 @@ describe('abc: onboarding', () => { page.start().checkActive().click('next').checkDone(false).click('done').checkDone(); })); + it('#key', fakeAsync(() => { + const storeSrv = TestBed.inject(ONBOARDING_STORE_TOKEN); + let storeKeyVersion: unknown = ''; + spyOn(storeSrv, 'get').and.callFake(() => { + return storeKeyVersion; + }); + spyOn(storeSrv, 'set').and.callFake((_, value) => { + storeKeyVersion = value; + }); + page.start({ key: 'a', keyVersion: '1' }).checkActive().click('next').checkDone(false).click('done').checkDone(); + page.start({ key: 'a', keyVersion: '1' }); + expect(page.el == null); + })); + it('#skip', fakeAsync(() => { page.start().checkActive().click('skip').checkDone(); })); diff --git a/packages/abc/onboarding/onboarding.storage.ts b/packages/abc/onboarding/onboarding.storage.ts new file mode 100644 index 0000000000..857915bfa1 --- /dev/null +++ b/packages/abc/onboarding/onboarding.storage.ts @@ -0,0 +1,26 @@ +import { InjectionToken } from '@angular/core'; + +export interface OnBoardingKeyStore { + get(key: string): unknown; + + set(key: string, version: unknown): void; +} + +export const ONBOARDING_STORE_TOKEN = new InjectionToken('ONBOARDING_STORE_TOKEN', { + providedIn: 'root', + factory: ONBOARDING_STORE_TOKEN_FACTORY +}); + +export function ONBOARDING_STORE_TOKEN_FACTORY(): OnBoardingKeyStore { + return new LocalStorageStore(); +} + +export class LocalStorageStore implements OnBoardingKeyStore { + get(key: string): unknown { + return localStorage.getItem(key); + } + + set(key: string, version: unknown): void { + localStorage.setItem(key, `${version}`); + } +} diff --git a/packages/abc/onboarding/onboarding.types.ts b/packages/abc/onboarding/onboarding.types.ts index 723f603c3b..4d9e0db218 100644 --- a/packages/abc/onboarding/onboarding.types.ts +++ b/packages/abc/onboarding/onboarding.types.ts @@ -7,6 +7,14 @@ import type { NzSafeAny } from 'ng-zorro-antd/core/types'; export type OnboardingOpType = 'next' | 'prev' | 'skip' | 'done'; export interface OnboardingConfig { + /** + * Storage identification Key, The default is `localStorage` local storage, allowing the use of `ONBOARDING_STORE_TOKEN` to change the storage method + */ + key?: string; + /** + * Current version + */ + keyVersion?: unknown; /** * Onboarding items */ diff --git a/packages/abc/onboarding/public_api.ts b/packages/abc/onboarding/public_api.ts index 68bb531ecc..fa2269a8f0 100644 --- a/packages/abc/onboarding/public_api.ts +++ b/packages/abc/onboarding/public_api.ts @@ -1,5 +1,6 @@ export * from './onboarding.service'; export * from './onboarding.types'; export * from './onboarding.component'; +export * from './onboarding.storage'; export * from './onboarding.module';