diff --git a/docs/cli.md b/docs/cli.md
index 2e9139fefd..8c6c4cb773 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -151,3 +151,34 @@ npm run el:bundle.windows
可将 Angular 打包成一个 Exe 应用程序。
> 脚手架对 Electron 的实现来自 [angular-electron-seed](https://github.com/sean-perkins/angular-electron-seed);有关更多细节可参考。
+
+## [page] 命令
+
+@delon/cli 扩展了 `ng generate page` (简化:`ng g page`) 命令用于生成业务组件页。
+
+### 参数
+
+`page` 命令是由 `ng g component` 基础向下构建。
+
+**命令格式**
+
+```bash
+ng g page [page name] -t=view --modal
+```
+
+| Alias | Arguments | Default | Summary |
+| --------- | --------- | ------- | ------- |
+| `-t` | `--type` | `list` | 指定页面类型,值包括:`list`、`view`、`edit` |
+| - | `--modal` | `true` | 是否模态框,限:`view`、`edit` 时有效 |
+
+例如:
+
+```bash
+# 生成日志列表页
+ng g page log
+
+cd log
+
+# 生成日志详情页
+ng g page view -t=view
+```
diff --git a/src/core/cli/collection.json b/src/core/cli/collection.json
index 188a1f08ef..bbfe92a77a 100644
--- a/src/core/cli/collection.json
+++ b/src/core/cli/collection.json
@@ -70,6 +70,11 @@
"factory": "./app-shell",
"description": "Create an app shell.",
"schema": "./app-shell/schema.json"
+ },
+ "page": {
+ "factory": "./page",
+ "description": "Create a page component.",
+ "schema": "./page/schema.json"
}
}
}
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.html b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.html
new file mode 100644
index 0000000000..d775f47ca3
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.html
@@ -0,0 +1,4 @@
+
+
Page Name
+
+
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.spec.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.spec.ts
new file mode 100644
index 0000000000..1bef5549eb
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.spec.ts
@@ -0,0 +1,24 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
+
+describe('<%= classify(name) %>Component', () => {
+ let component: <%= classify(name) %>Component;
+ let fixture: ComponentFixture<<%= classify(name) %>Component>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ <%= classify(name) %>Component ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(<%= classify(name) %>Component);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.ts
new file mode 100644
index 0000000000..f685263951
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-edit__.component.ts
@@ -0,0 +1,8 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class <%= classify(name) %>Service {
+
+ constructor() { }
+
+}
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.html b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.html
new file mode 100644
index 0000000000..105ae5adef
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.spec.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.spec.ts
new file mode 100644
index 0000000000..1bef5549eb
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.spec.ts
@@ -0,0 +1,24 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
+
+describe('<%= classify(name) %>Component', () => {
+ let component: <%= classify(name) %>Component;
+ let fixture: ComponentFixture<<%= classify(name) %>Component>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ <%= classify(name) %>Component ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(<%= classify(name) %>Component);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.ts
new file mode 100644
index 0000000000..497987c4c2
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-list__.component.ts
@@ -0,0 +1,30 @@
+import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
+import { _HttpClient } from '@delon/theme';
+import { SimpleTableColumn, SimpleTableComponent } from '@delon/abc';
+
+@Component({
+ selector: '<%= selector %>',<% if(inlineTemplate) { %>
+ template: `
+
+
+
+
+ `,<% } else { %>
+ templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %><% } else { %>
+ styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
+ encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
+ changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
+})
+export class <%= classify(name) %>Component implements OnInit {
+
+ params: any = {};
+ url = '/';
+ columns: SimpleTableColumn[] = [
+ { title: '编号', index: 'id' }
+ ];
+
+ constructor(private http: _HttpClient) { }
+
+ ngOnInit() { }
+
+}
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.html b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.html
new file mode 100644
index 0000000000..9d3394703b
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.html
@@ -0,0 +1,19 @@
+<% if(modal) { %>
+
+ 10000
+
+<% } else { %>
+
+
+
+ 10000
+
+
+
+ 10000
+
+<% } %>
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.spec.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.spec.ts
new file mode 100644
index 0000000000..1bef5549eb
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.spec.ts
@@ -0,0 +1,24 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { <%= classify(name) %>Component } from './<%= dasherize(name) %>.component';
+
+describe('<%= classify(name) %>Component', () => {
+ let component: <%= classify(name) %>Component;
+ let fixture: ComponentFixture<<%= classify(name) %>Component>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ <%= classify(name) %>Component ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(<%= classify(name) %>Component);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.ts b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.ts
new file mode 100644
index 0000000000..63b705e551
--- /dev/null
+++ b/src/core/cli/page/files/__path__/__name@dasherize@if-flat__/__name@dasherize@if-view__.component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit<% if(!!viewEncapsulation) { %>, ViewEncapsulation<% }%><% if(changeDetection !== 'Default') { %>, ChangeDetectionStrategy<% }%> } from '@angular/core';
+import { NzModalSubject } from 'ng-zorro-antd';
+import { _HttpClient } from '@delon/theme';
+
+@Component({
+ selector: '<%= selector %>',<% if(inlineTemplate) { %>
+ template: `<% if(modal) { %>
+
+
+ 10000
+
+ <% } else { %>
+
+
+
+ 10000
+
+
+
+ 10000
+
+ <% } %>
+ `,<% } else { %>
+ templateUrl: './<%= dasherize(name) %>.component.html',<% } if(inlineStyle) { %><% } else { %>
+ styleUrls: ['./<%= dasherize(name) %>.component.<%= styleext %>']<% } %><% if(!!viewEncapsulation) { %>,
+ encapsulation: ViewEncapsulation.<%= viewEncapsulation %><% } if (changeDetection !== 'Default') { %>,
+ changeDetection: ChangeDetectionStrategy.<%= changeDetection %><% } %>
+})
+export class <%= classify(name) %>Component implements OnInit {
+ private readonly URI = '/';
+ i: any;
+
+ constructor(public http: _HttpClient, private subject: NzModalSubject) { }
+
+ ngOnInit() {
+ this.http.get(this.URI + this.i.id).subscribe((res: any) => this.i = res.d);
+ }
+
+ close() {
+ this.subject.destroy();
+ }
+}
diff --git a/src/core/cli/page/index.d.ts b/src/core/cli/page/index.d.ts
new file mode 100644
index 0000000000..51cb2e9bac
--- /dev/null
+++ b/src/core/cli/page/index.d.ts
@@ -0,0 +1,3 @@
+import { Rule } from '@angular-devkit/schematics';
+import { Schema as ServiceOptions } from './schema';
+export default function (options: ServiceOptions): Rule;
diff --git a/src/core/cli/page/index.js b/src/core/cli/page/index.js
new file mode 100644
index 0000000000..5978620719
--- /dev/null
+++ b/src/core/cli/page/index.js
@@ -0,0 +1,101 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+const core_1 = require("@angular-devkit/core");
+const schematics_1 = require("@angular-devkit/schematics");
+require("rxjs/add/operator/merge");
+const ts = require("typescript");
+const stringUtils = require("../strings");
+const ast_utils_1 = require("../utility/ast-utils");
+const change_1 = require("../utility/change");
+const find_module_1 = require("../utility/find-module");
+function addDeclarationToNgModule(options) {
+ return (host) => {
+ if (options.skipImport || !options.module) {
+ return host;
+ }
+ const modulePath = options.module;
+ const text = host.read(modulePath);
+ if (text === null) {
+ throw new schematics_1.SchematicsException(`File ${modulePath} does not exist.`);
+ }
+ const sourceText = text.toString('utf-8');
+ const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
+ const componentPath = `/${options.sourceDir}/${options.path}/`
+ + (options.flat ? '' : stringUtils.dasherize(options.name) + '/')
+ + stringUtils.dasherize(options.name)
+ + '.component';
+ const relativePath = find_module_1.buildRelativePath(modulePath, componentPath);
+ const classifiedName = stringUtils.classify(`${options.name}Component`);
+ const declarationChanges = ast_utils_1.addDeclarationToModule(source, modulePath, classifiedName, relativePath);
+ const declarationRecorder = host.beginUpdate(modulePath);
+ for (const change of declarationChanges) {
+ if (change instanceof change_1.InsertChange) {
+ declarationRecorder.insertLeft(change.pos, change.toAdd);
+ }
+ }
+ host.commitUpdate(declarationRecorder);
+ if (options.export) {
+ // Need to refresh the AST because we overwrote the file in the host.
+ const text = host.read(modulePath);
+ if (text === null) {
+ throw new schematics_1.SchematicsException(`File ${modulePath} does not exist.`);
+ }
+ const sourceText = text.toString('utf-8');
+ const source = ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true);
+ const exportRecorder = host.beginUpdate(modulePath);
+ const exportChanges = ast_utils_1.addExportToModule(source, modulePath, stringUtils.classify(`${options.name}Component`), relativePath);
+ for (const change of exportChanges) {
+ if (change instanceof change_1.InsertChange) {
+ exportRecorder.insertLeft(change.pos, change.toAdd);
+ }
+ }
+ host.commitUpdate(exportRecorder);
+ }
+ return host;
+ };
+}
+function buildSelector(options) {
+ let selector = stringUtils.dasherize(options.name);
+ if (options.prefix) {
+ selector = `${options.prefix}-${selector}`;
+ }
+ return selector;
+}
+function default_1(options) {
+ const sourceDir = options.sourceDir;
+ if (!sourceDir) {
+ throw new schematics_1.SchematicsException(`sourceDir option is required.`);
+ }
+ return (host, context) => {
+ options.selector = options.selector || buildSelector(options);
+ options.path = options.path ? core_1.normalize(options.path) : options.path;
+ options.module = find_module_1.findModuleFromOptions(host, options);
+ const templateSource = schematics_1.apply(schematics_1.url('./files'), [
+ options.spec ? schematics_1.noop() : schematics_1.filter(path => !path.endsWith('.spec.ts')),
+ options.inlineStyle ? schematics_1.filter(path => !path.endsWith('.__styleext__')) : schematics_1.noop(),
+ options.inlineTemplate ? schematics_1.filter(path => !path.endsWith('.html')) : schematics_1.noop(),
+ schematics_1.filter(path => path.includes(`if-${options.type}`)),
+ schematics_1.template(Object.assign({}, stringUtils, {
+ 'if-flat': (s) => options.flat ? '' : s,
+ 'if-list': (s) => s,
+ 'if-edit': (s) => s,
+ 'if-view': (s) => s
+ }, options)),
+ schematics_1.move(sourceDir),
+ ]);
+ return schematics_1.chain([
+ schematics_1.branchAndMerge(schematics_1.chain([
+ addDeclarationToNgModule(options),
+ schematics_1.mergeWith(templateSource),
+ ])),
+ ])(host, context);
+ };
+}
+exports.default = default_1;
diff --git a/src/core/cli/page/schema.d.ts b/src/core/cli/page/schema.d.ts
new file mode 100644
index 0000000000..3a2bfa4593
--- /dev/null
+++ b/src/core/cli/page/schema.d.ts
@@ -0,0 +1,82 @@
+/**
+ * @license
+ * Copyright Google Inc. All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export interface Schema {
+ /**
+ * The path to create the component.
+ */
+ path?: string;
+ /**
+ * The path of the source directory.
+ */
+ sourceDir?: string;
+ /**
+ * The root of the application.
+ */
+ appRoot?: string;
+ /**
+ * The name of the component.
+ */
+ name: string;
+ /**
+ * Specifies if the style will be in the ts file.
+ */
+ inlineStyle?: boolean;
+ /**
+ * Specifies if the template will be in the ts file.
+ */
+ inlineTemplate?: boolean;
+ /**
+ * Specifies the view encapsulation strategy.
+ */
+ viewEncapsulation?: ('Emulated' | 'Native' | 'None');
+ /**
+ * Specifies the change detection strategy.
+ */
+ changeDetection?: ('Default' | 'OnPush');
+ /**
+ * The prefix to apply to generated selectors.
+ */
+ prefix?: string;
+ /**
+ * The file extension to be used for style files.
+ */
+ styleext?: string;
+ /**
+ * Specifies if a spec file is generated.
+ */
+ spec?: boolean;
+ /**
+ * Flag to indicate if a dir is created.
+ */
+ flat?: boolean;
+ /**
+ * Flag to skip the module import.
+ */
+ skipImport?: boolean;
+ /**
+ * The selector to use for the component.
+ */
+ selector?: string;
+ /**
+ * Allows specification of the declaring module.
+ */
+ module?: string;
+ /**
+ * Specifies if declaring module exports the component.
+ */
+ export?: boolean;
+ /**
+ * The page type
+ */
+ type?: ('list' | 'edit' | 'view');
+ /**
+ * Specifies if edit or view page using modal mode.
+ */
+ modal?: boolean;
+}
diff --git a/src/core/cli/page/schema.json b/src/core/cli/page/schema.json
new file mode 100644
index 0000000000..fcf5f62279
--- /dev/null
+++ b/src/core/cli/page/schema.json
@@ -0,0 +1,108 @@
+{
+ "$schema": "http://json-schema.org/schema",
+ "id": "SchematicsNgAlainPageComponent",
+ "title": "ng-alain page Options Schema",
+ "type": "object",
+ "properties": {
+ "path": {
+ "type": "string",
+ "description": "The path to create the component.",
+ "default": "app",
+ "visible": false
+ },
+ "sourceDir": {
+ "type": "string",
+ "description": "The path of the source directory.",
+ "default": "src",
+ "alias": "sd",
+ "visible": false
+ },
+ "appRoot": {
+ "type": "string",
+ "description": "The root of the application.",
+ "visible": false
+ },
+ "name": {
+ "type": "string",
+ "description": "The name of the component."
+ },
+ "inlineStyle": {
+ "description": "Specifies if the style will be in the ts file.",
+ "type": "boolean",
+ "default": true,
+ "alias": "is"
+ },
+ "inlineTemplate": {
+ "description": "Specifies if the template will be in the ts file.",
+ "type": "boolean",
+ "default": false,
+ "alias": "it"
+ },
+ "viewEncapsulation": {
+ "description": "Specifies the view encapsulation strategy.",
+ "enum": ["Emulated", "Native", "None"],
+ "type": "string",
+ "alias": "ve"
+ },
+ "changeDetection": {
+ "description": "Specifies the change detection strategy.",
+ "enum": ["Default", "OnPush"],
+ "type": "string",
+ "default": "Default",
+ "alias": "cd"
+ },
+ "prefix": {
+ "type": "string",
+ "description": "The prefix to apply to generated selectors.",
+ "default": "app",
+ "alias": "p"
+ },
+ "styleext": {
+ "description": "The file extension to be used for style files.",
+ "type": "string",
+ "default": "less"
+ },
+ "spec": {
+ "type": "boolean",
+ "description": "Specifies if a spec file is generated.",
+ "default": false
+ },
+ "flat": {
+ "type": "boolean",
+ "description": "Flag to indicate if a dir is created.",
+ "default": false
+ },
+ "skipImport": {
+ "type": "boolean",
+ "description": "Flag to skip the module import.",
+ "default": false
+ },
+ "selector": {
+ "type": "string",
+ "description": "The selector to use for the component."
+ },
+ "module": {
+ "type": "string",
+ "description": "Allows specification of the declaring module.",
+ "alias": "m"
+ },
+ "export": {
+ "type": "boolean",
+ "default": false,
+ "description": "Specifies if declaring module exports the component."
+ },
+ "type": {
+ "description": "The page type, includes: list, edit, view",
+ "enum": ["list", "edit", "view"],
+ "type": "string",
+ "default": "list",
+ "alias": "t"
+ },
+ "modal": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies if edit or view page using modal mode."
+ }
+ },
+ "required": ["name"]
+}
diff --git a/src/core/theme/styles/app/antd/_preserve-white-spaces.less b/src/core/theme/styles/app/antd/_preserve-white-spaces.less
index aa64a8bdf8..588c2a1230 100644
--- a/src/core/theme/styles/app/antd/_preserve-white-spaces.less
+++ b/src/core/theme/styles/app/antd/_preserve-white-spaces.less
@@ -4,7 +4,8 @@
// 表单
[nz-form] {
// 按钮
- .ant-btn + .ant-btn {
+ .ant-btn + .ant-btn,
+ .ant-btn + nz-popconfirm {
margin-left: @white-spacing;
}
}