diff --git a/packages/abc/index.less b/packages/abc/index.less index d8311c2a9..51c89e49f 100644 --- a/packages/abc/index.less +++ b/packages/abc/index.less @@ -18,3 +18,4 @@ @import './loading/style/index.less'; @import './onboarding/style/index.less'; @import './pdf/style/index.less'; +@import './price/style/index.less'; diff --git a/packages/abc/price/demo/basic.md b/packages/abc/price/demo/basic.md new file mode 100644 index 000000000..06febc386 --- /dev/null +++ b/packages/abc/price/demo/basic.md @@ -0,0 +1,69 @@ +--- +order: 1 +title: + zh-CN: 基础样例 + en-US: Basic Usage +--- + +## zh-CN + +最简单的用法。 + +## en-US + +Simplest of usage. + +```ts +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-demo', + template: ` +

{{ type }}

+ +
+ +
+ `, +}) +export class DemoComponent implements OnInit { + types = ['url', 'email', 'tel', 'cn', 'vcard']; + value = ''; + type = ''; + change(type: string): void { + this.type = type; + switch (type) { + case 'url': + this.value = 'https://ng-alain.com/'; + break; + case 'email': + this.value = 'mailto:cipchk@qq.com'; + break; + case 'tel': + this.value = 'tel:15900000000'; + break; + case 'cn': + this.value = '你好🇨🇳'; + break; + case 'vcard': + this.value = `BEGIN:VCARD +VERSION:4.0 +N:色;卡;;Mr.; +FN:卡色 +ORG:NG-ALAIN +TITLE:NG-ALAIN +PHOTO;MEDIATYPE=image/svg:https://ng-alain.com/assets/img/logo-color.svg +TEL;TYPE=work,voice;VALUE=uri:tel:15900000000 +ADR;TYPE=WORK;PREF=1;LABEL="中国上海":;;上海;中国 +EMAIL:cipchk@qq.com +x-qq:94458893 +END:VCARD`; + break; + } + } + + ngOnInit(): void { + this.change('url'); + } +} +``` diff --git a/packages/abc/price/demo/design.md b/packages/abc/price/demo/design.md new file mode 100644 index 000000000..caf015a33 --- /dev/null +++ b/packages/abc/price/demo/design.md @@ -0,0 +1,124 @@ +--- +order: 2 +title: + zh-CN: 设计器 + en-US: Designer +--- + +## zh-CN + +利用 `change` 可以回调二维码 dataURL 值。 + +## en-US + +Get QR code (dataURL value) via `change`. + +```ts +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-demo', + template: ` +
+
+ +
+
+ + + +
+
+ +
+
+ +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + px + + + + px + +
+
+
+ `, +}) +export class DemoComponent { + value = 'https://ng-alain.com/'; + background = '#ffffff'; + backgroundAlpha = 1.0; + foreground = '#000000'; + foregroundAlpha = 1.0; + level = 'L'; + mime = 'image/png'; + padding = 10; + size = 220; + + change(dataURL: string): void { + console.log(dataURL); + } +} +``` diff --git a/packages/abc/price/index.en-US.md b/packages/abc/price/index.en-US.md new file mode 100644 index 000000000..84b2c279e --- /dev/null +++ b/packages/abc/price/index.en-US.md @@ -0,0 +1,37 @@ +--- +type: Basic +order: 3 +title: qr +subtitle: QR +cols: 1 +module: import { QRModule } from '@delon/abc/qr'; +--- + +Generate a QR code based on [qrious](https://github.com/neocotic/qrious). + + +Qr libary is lazy loading by default,you can change the default CDN path (or use the local path) via [Global Configuration](/docs/global-config). By default: `https://cdn.bootcdn.net/ajax/libs/qrious/4.0.2/qrious.min.js`. Or install dependence via `npm i --save qrious`, and import script path in `angular.json`. + +## API + +### qr + +| Property | Description | Type | Default | Global Config | +|----------|-------------|------|---------|---------------| +| `[value]` | Value encoded within the QR code | `string` | - | | +| `[background]` | Background colour of the QR code | `string` | `white` | ✅ | +| `[backgroundAlpha]` | Background alpha of the QR code | `number` | `1` | ✅ | +| `[foreground]` | Foreground colour of the QR code | `string` | `white` | ✅ | +| `[foregroundAlpha]` | Foreground alpha of the QR code | `number` | `1` | ✅ | +| `[level]` | Error correction level of the QR code | `'L','M','Q','H'` | `'L'` | ✅ | +| `[mime]` | MIME type used to render the image for the QR code | `string` | `image/png` | ✅ | +| `[padding]` | Padding for the QR code (pixels) | `number` | `10` | ✅ | +| `[size]` | Size of the QR code (pixels) | `number` | `220` | ✅ | +| `[delay]` | Delayed rendering, unit: ms | `number` | `0` | ✅ | +| `(change)` | change event | `EventEmitter` | - | | + +## FAQ + +### Custom LOGO + +Refer to [#100](https://github.com/neocotic/qrious/issues/100#issuecomment-308249343). diff --git a/packages/abc/price/index.ts b/packages/abc/price/index.ts new file mode 100644 index 000000000..4aaf8f92e --- /dev/null +++ b/packages/abc/price/index.ts @@ -0,0 +1 @@ +export * from './public_api'; diff --git a/packages/abc/price/index.zh-CN.md b/packages/abc/price/index.zh-CN.md new file mode 100644 index 000000000..cb6ec87c7 --- /dev/null +++ b/packages/abc/price/index.zh-CN.md @@ -0,0 +1,36 @@ +--- +type: Basic +order: 3 +title: qr +subtitle: 二维码 +cols: 1 +module: import { QRModule } from '@delon/abc/qr'; +--- + +基于 [qrious](https://github.com/neocotic/qrious) 生成二维码。 + +默认二维码的操作并不是刚需的原因,因此采用一种延迟加载脚本的形式,可以通过[全局配置](/docs/global-config)配置来改变默认 CDN 路径(或使用本地路径),默认情况下使用 `https://cdn.bootcdn.net/ajax/libs/qrious/4.0.2/qrious.min.js`。或安装 `npm i --save qrious` 依赖包并在 `angular.json` 的 `scripts` 引用 `"node_modules/qrious/dist/qrious.min.js"`。 + +## API + +### qr + +| 成员 | 说明 | 类型 | 默认值 | 全局配置 | +|----|----|----|-----|------| +| `[value]` | 值 | `string` | - | | +| `[background]` | 背景 | `string` | `white` | ✅ | +| `[backgroundAlpha]` | 背景透明级别,范围:`0-1` 之间 | `number` | `1` | ✅ | +| `[foreground]` | 前景 | `string` | `white` | ✅ | +| `[foregroundAlpha]` | 前景透明级别,范围:`0-1` 之间 | `number` | `1` | ✅ | +| `[level]` | 误差校正级别 | `'L','M','Q','H'` | `'L'` | ✅ | +| `[mime]` | 二维码输出图片MIME类型 | `string` | `image/png` | ✅ | +| `[padding]` | 内边距(单位:px) | `number` | `10` | ✅ | +| `[size]` | 大小(单位:px) | `number` | `220` | ✅ | +| `[delay]` | 延迟渲染,单位:毫秒 | `number` | `0` | ✅ | +| `(change)` | 变更时回调,返回二维码dataURL值 | `EventEmitter` | - | | + +## 常见问题 + +### 自定义LOGO + +参考 [#100](https://github.com/neocotic/qrious/issues/100#issuecomment-308249343) 的写法。 diff --git a/packages/abc/price/package.json b/packages/abc/price/package.json new file mode 100644 index 000000000..055656f8a --- /dev/null +++ b/packages/abc/price/package.json @@ -0,0 +1,11 @@ +{ + "ngPackage": { + "lib": { + "flatModuleFile": "price", + "entryFile": "public_api.ts", + "umdModuleIds": { + "@delon/util": "delon.util" + } + } + } +} diff --git a/packages/abc/price/price.component.ts b/packages/abc/price/price.component.ts new file mode 100644 index 000000000..38dffe686 --- /dev/null +++ b/packages/abc/price/price.component.ts @@ -0,0 +1,67 @@ +import { ChangeDetectionStrategy, Component, forwardRef, Input, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { BooleanInput, InputBoolean, toNumber } from '@delon/util/decorator'; +import type { NzSizeLDSType, OnChangeType, OnTouchedType } from 'ng-zorro-antd/core/types'; + +@Component({ + selector: 'price', + exportAs: 'price', + template: ` + + `, + host: { + '[class.price]': `true`, + }, + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PriceComponent), + multi: true, + }, + ], + preserveWhitespaces: false, + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, +}) +export class PriceComponent implements ControlValueAccessor { + static ngAcceptInputType_disabled: BooleanInput; + static ngAcceptInputType_autoFocus: BooleanInput; + + onChange: OnChangeType = () => {}; + onTouched: OnTouchedType = () => {}; + value: number | null = null; + + @Input() nzId: string | null = null; + @Input() size: NzSizeLDSType = 'default'; + @Input() min: number = -Infinity; + @Input() max: number = Infinity; + @Input() placeHolder = ''; + @Input() step = 1; + @Input() @InputBoolean() disabled = false; + @Input() @InputBoolean() autoFocus = false; + + handlValue(val: number): void { + this.onChange(val); + } + + writeValue(value: number): void { + this.value = toNumber(value, null); + } + registerOnChange(fn: OnChangeType): void { + this.onChange = fn; + } + registerOnTouched(fn: OnTouchedType): void { + this.onTouched = fn; + } +} diff --git a/packages/abc/price/price.module.ts b/packages/abc/price/price.module.ts new file mode 100644 index 000000000..3e33ca947 --- /dev/null +++ b/packages/abc/price/price.module.ts @@ -0,0 +1,14 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { PriceComponent } from './price.component'; +import { FormsModule } from '@angular/forms'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; + +const COMPONENTS = [PriceComponent]; + +@NgModule({ + imports: [CommonModule, FormsModule, NzInputNumberModule], + declarations: COMPONENTS, + exports: COMPONENTS, +}) +export class PriceModule {} diff --git a/packages/abc/price/price.spec.ts b/packages/abc/price/price.spec.ts new file mode 100644 index 000000000..60d01f627 --- /dev/null +++ b/packages/abc/price/price.spec.ts @@ -0,0 +1,119 @@ +import { Component, DebugElement, ViewChild } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { createTestContext } from '@delon/testing'; +import { of } from 'rxjs'; +import { AlainConfigService, LazyService } from '../../util'; +import { PriceComponent } from './price.component'; +import { PriceModule } from './price.module'; + +describe('abc: qr', () => { + let fixture: ComponentFixture; + let dl: DebugElement; + let context: TestComponent; + const win: any = window; + + class MockQRious { + set(): jasmine.Spy { + return jasmine.createSpy('set'); + } + toDataURL(): jasmine.Spy { + return jasmine.createSpy('toDataURL'); + } + } + + function createModule(): void { + TestBed.configureTestingModule({ + imports: [PriceModule], + declarations: [TestComponent], + }); + } + + function mockQRious(): void { + win.QRious = MockQRious; + } + + afterEach(() => { + // if (context.comp && context.comp.ngOnDestroy) context.comp.ngOnDestroy(); + delete win.QRious; + }); + + describe('', () => { + beforeEach(fakeAsync(() => { + createModule(); + mockQRious(); + ({ fixture, dl, context } = createTestContext(TestComponent)); + fixture.detectChanges(); + spyOn(context, 'change'); + tick(100); + })); + + function getDataURL(): string { + return (dl.query(By.css('img')).nativeElement as HTMLImageElement).src; + } + + it('should be generate a qr', () => { + expect(getDataURL().length).toBeGreaterThan(1); + }); + + it('should be support unicode value', () => { + context.value = 'test'; + fixture.detectChanges(); + expect(context.change).toHaveBeenCalled(); + context.value = `中国🇨🇳`; + fixture.detectChanges(); + expect(context.change).toHaveBeenCalled(); + }); + }); + + it('should be lazy load libary', () => { + createModule(); + const cogSrv = TestBed.inject(AlainConfigService); + const lazySrv = TestBed.inject(LazyService); + spyOn(cogSrv, 'merge').and.returnValue({ lib: '1.js' }); + spyOnProperty(lazySrv, 'change').and.returnValue(of([{ path: '1.js', status: 'ok' }])); + ({ fixture, dl, context } = createTestContext(TestComponent)); + fixture.detectChanges(); + mockQRious(); + spyOn(context, 'change'); + context.value = '11'; + fixture.detectChanges(); + expect(context.change).toHaveBeenCalled(); + }); +}); + +@Component({ + template: ` + + `, +}) +class TestComponent { + @ViewChild('comp', { static: true }) + comp: PriceComponent; + + value = 'https://ng-alain.com/'; + background = 'white'; + // tslint:disable-next-line:number-literal-format + backgroundAlpha = 1.0; + foreground = 'black'; + // tslint:disable-next-line:number-literal-format + foregroundAlpha = 1.0; + level = 'L'; + mime = 'image/png'; + padding = 10; + size = 220; + + change(): void {} +} diff --git a/packages/abc/price/public_api.ts b/packages/abc/price/public_api.ts new file mode 100644 index 000000000..76c86d8c0 --- /dev/null +++ b/packages/abc/price/public_api.ts @@ -0,0 +1,2 @@ +export * from './price.component'; +export * from './price.module'; diff --git a/packages/abc/price/style/index.less b/packages/abc/price/style/index.less new file mode 100644 index 000000000..03e8c592c --- /dev/null +++ b/packages/abc/price/style/index.less @@ -0,0 +1,6 @@ +@import '../../../theme/theme-default.less'; +@price-prefix: ~'.price'; + +@{price-prefix} { + display: inline-block; +} diff --git a/src/app/core/code/files/delon-abc.module.ts b/src/app/core/code/files/delon-abc.module.ts index a312d2242..8f0bc9905 100644 --- a/src/app/core/code/files/delon-abc.module.ts +++ b/src/app/core/code/files/delon-abc.module.ts @@ -32,6 +32,7 @@ import { OnboardingModule } from '@delon/abc/onboarding'; import { LetModule } from '@delon/abc/let'; import { AutoFocusModule } from '@delon/abc/auto-focus'; import { PdfModule } from '@delon/abc/pdf'; +import { PriceModule } from '@delon/abc/price'; const MODULES = [ ErrorCollectModule, @@ -65,6 +66,7 @@ const MODULES = [ LetModule, AutoFocusModule, PdfModule, + PriceModule, ]; @NgModule({ exports: MODULES }) diff --git a/src/app/shared/shared-delon.module.ts b/src/app/shared/shared-delon.module.ts index 49f390121..20e738070 100644 --- a/src/app/shared/shared-delon.module.ts +++ b/src/app/shared/shared-delon.module.ts @@ -16,6 +16,7 @@ import { NoticeIconModule } from '@delon/abc/notice-icon'; import { OnboardingModule } from '@delon/abc/onboarding'; import { PageHeaderModule } from '@delon/abc/page-header'; import { PdfModule } from '@delon/abc/pdf'; +import { PriceModule } from '@delon/abc/price'; import { QRModule } from '@delon/abc/qr'; import { QuickMenuModule } from '@delon/abc/quick-menu'; import { ResultModule } from '@delon/abc/result'; @@ -94,5 +95,6 @@ export const SHARED_DELON_MODULES = [ FormatPipeModule, FilterPipeModule, AutoFocusModule, - LetModule + LetModule, + PriceModule ]; diff --git a/src/dev/demo.component.ts b/src/dev/demo.component.ts index 290370be7..9501191cf 100644 --- a/src/dev/demo.component.ts +++ b/src/dev/demo.component.ts @@ -1,39 +1,9 @@ import { Component } from '@angular/core'; -import { dateTimePickerUtil } from '@delon/util/date-time'; - @Component({ selector: 'app-demo', - template: ` -

value: {{ value | _date }}

- -
- -
- -
-

values: {{ values }}

- - ` + template: ` = {{ value | json }}` }) export class DemoComponent { - value: Date; - values: Date[]; - disabledDate = dateTimePickerUtil.disabledBeforeDate(); - disabledDateTime = dateTimePickerUtil.disabledBeforeTime({ offsetSeconds: 60 * 5 }); - // disabledDate = dateTimePickerUtil.disabledAfterDate(); - // disabledDateTime = dateTimePickerUtil.disabledAfterTime(); + value: number | null = null; }