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;
}