haogh 11 hónapja
commit
75ee270c34
100 módosított fájl, 5536 hozzáadás és 0 törlés
  1. 13 0
      .editorconfig
  2. 46 0
      .gitignore
  3. 27 0
      README.md
  4. 166 0
      angular.json
  5. 12 0
      browserslist
  6. 13 0
      build.bat
  7. 32 0
      e2e/protractor.conf.js
  8. 23 0
      e2e/src/app.e2e-spec.ts
  9. 11 0
      e2e/src/app.po.ts
  10. 13 0
      e2e/tsconfig.json
  11. 32 0
      modernizr-config.json
  12. 88 0
      package.json
  13. 7 0
      proxy.conf.json
  14. 3 0
      src/app/app.component.html
  15. 0 0
      src/app/app.component.scss
  16. 87 0
      src/app/app.component.ts
  17. 69 0
      src/app/app.module.ts
  18. 27 0
      src/app/core/core.module.ts
  19. 1 0
      src/app/core/frame/ajax/frame-ajax/frame-ajax.html
  20. 187 0
      src/app/core/frame/ajax/frame-ajax/frame-ajax.ts
  21. 64 0
      src/app/core/frame/pager/frame-pager/frame-pager.html
  22. 421 0
      src/app/core/frame/pager/frame-pager/frame-pager.ts
  23. 47 0
      src/app/core/frame/upload/frame-upload/frame-upload.html
  24. 210 0
      src/app/core/frame/upload/frame-upload/frame-upload.ts
  25. 7 0
      src/app/core/frame/valid/form-valid.html
  26. 57 0
      src/app/core/frame/valid/form-valid.ts
  27. 31 0
      src/app/core/menu/menu.service.ts
  28. 6 0
      src/app/core/module-import-guard.ts
  29. 3 0
      src/app/core/preloader/preloader.component.html
  30. 51 0
      src/app/core/preloader/preloader.js
  31. 71 0
      src/app/core/preloader/preloader.scss
  32. 198 0
      src/app/core/service/ajax.service.ts
  33. 57 0
      src/app/core/service/ajax.ts
  34. 30 0
      src/app/core/service/cookie.service.ts
  35. 239 0
      src/app/core/service/frame.service.ts
  36. 192 0
      src/app/core/service/func.service.ts
  37. 69 0
      src/app/core/service/modal.service.ts
  38. 141 0
      src/app/core/service/msg.service.ts
  39. 41 0
      src/app/core/service/pager.ts
  40. 49 0
      src/app/core/service/var.service.ts
  41. 74 0
      src/app/core/settings/settings.service.ts
  42. 72 0
      src/app/core/themes/themes.service.ts
  43. 32 0
      src/app/core/translator/translator.service.ts
  44. 2 0
      src/app/index.ts
  45. 1 0
      src/app/layout/footer/footer.component.html
  46. 0 0
      src/app/layout/footer/footer.component.scss
  47. 17 0
      src/app/layout/footer/footer.component.ts
  48. 140 0
      src/app/layout/header/header.component.h.html
  49. 117 0
      src/app/layout/header/header.component.html
  50. 0 0
      src/app/layout/header/header.component.scss
  51. 148 0
      src/app/layout/header/header.component.ts
  52. 9 0
      src/app/layout/header/navsearch/navsearch.component.html
  53. 0 0
      src/app/layout/header/navsearch/navsearch.component.scss
  54. 45 0
      src/app/layout/header/navsearch/navsearch.component.ts
  55. 15 0
      src/app/layout/layout.component.h.html
  56. 17 0
      src/app/layout/layout.component.html
  57. 0 0
      src/app/layout/layout.component.scss
  58. 15 0
      src/app/layout/layout.component.ts
  59. 47 0
      src/app/layout/layout.module.ts
  60. 272 0
      src/app/layout/offsidebar/offsidebar.component.html
  61. 0 0
      src/app/layout/offsidebar/offsidebar.component.scss
  62. 52 0
      src/app/layout/offsidebar/offsidebar.component.ts
  63. 113 0
      src/app/layout/sidebar/sidebar.component.html
  64. 0 0
      src/app/layout/sidebar/sidebar.component.scss
  65. 195 0
      src/app/layout/sidebar/sidebar.component.ts
  66. 14 0
      src/app/layout/sidebar/userblock/userblock.component.html
  67. 0 0
      src/app/layout/sidebar/userblock/userblock.component.scss
  68. 20 0
      src/app/layout/sidebar/userblock/userblock.component.spec.ts
  69. 26 0
      src/app/layout/sidebar/userblock/userblock.component.ts
  70. 16 0
      src/app/layout/sidebar/userblock/userblock.service.spec.ts
  71. 23 0
      src/app/layout/sidebar/userblock/userblock.service.ts
  72. 43 0
      src/app/routes/art/admit/admit-aspect/admit-aspect.component.html
  73. 0 0
      src/app/routes/art/admit/admit-aspect/admit-aspect.component.scss
  74. 19 0
      src/app/routes/art/admit/admit-aspect/admit-aspect.component.ts
  75. 39 0
      src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.html
  76. 0 0
      src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.scss
  77. 49 0
      src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.ts
  78. 63 0
      src/app/routes/art/admit/admit-province-score/admit-province-score.component.html
  79. 0 0
      src/app/routes/art/admit/admit-province-score/admit-province-score.component.scss
  80. 113 0
      src/app/routes/art/admit/admit-province-score/admit-province-score.component.ts
  81. 91 0
      src/app/routes/art/admit/admit-province/admit-province.component.html
  82. 0 0
      src/app/routes/art/admit/admit-province/admit-province.component.scss
  83. 78 0
      src/app/routes/art/admit/admit-province/admit-province.component.ts
  84. 32 0
      src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.html
  85. 0 0
      src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.scss
  86. 111 0
      src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.ts
  87. 27 0
      src/app/routes/art/admit/admit.art.module.ts
  88. 35 0
      src/app/routes/art/art.module.ts
  89. 32 0
      src/app/routes/art/conf/agent/agent.conf.module.ts
  90. 74 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.html
  91. 0 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.scss
  92. 113 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.ts
  93. 77 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.html
  94. 0 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.scss
  95. 52 0
      src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.ts
  96. 63 0
      src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.html
  97. 0 0
      src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.scss
  98. 70 0
      src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.ts
  99. 32 0
      src/app/routes/art/conf/agent/conf-agent-category/conf-agent-category.component.html
  100. 0 0
      src/app/routes/art/conf/agent/conf-agent-category/conf-agent-category.component.scss

+ 13 - 0
.editorconfig

@@ -0,0 +1,13 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false

+ 46 - 0
.gitignore

@@ -0,0 +1,46 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+/out-tsc
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events*.json
+speed-measure-plugin*.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+
+# System Files
+.DS_Store
+Thumbs.db

+ 27 - 0
README.md

@@ -0,0 +1,27 @@
+# Ng2angle
+
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.1.
+
+## Development server
+
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
+
+## Code scaffolding
+
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
+
+## Build
+
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
+
+## Running unit tests
+
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
+
+## Running end-to-end tests
+
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
+
+## Further help
+
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

+ 166 - 0
angular.json

@@ -0,0 +1,166 @@
+{
+  "$schema": "./node_modules/@angular/cl/lib/config/schema.json",
+  "version": 1,
+  "newProjectRoot": "projects",
+  "projects": {
+    "ArtWeb-V3": {
+      "projectType": "application",
+      "schematics": {
+        "@schematics/angular:component": {
+          "style": "scss"
+        }
+      },
+      "root": "",
+      "sourceRoot": "src",
+      "prefix": "app",
+      "architect": {
+        "build": {
+          "builder": "@angular-devkit/build-angular:browser",
+          "options": {
+            "allowedCommonJsDependencies": [
+              "lodash"
+            ],
+            "preserveSymlinks": true,
+            "outputPath": "dist",
+            "index": "src/frame.html",
+            "main": "src/main.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.app.json",
+            "aot": false,
+            "assets": [
+              "src/favicon.ico",
+              "src/assets",
+              "src/index.html"
+            ],
+            "styles": [
+              "src/app/core/preloader/preloader.scss",
+              "src/styles.scss",
+              "src/assets/common/laydate-5.0.9/theme/default/laydate.css"
+            ],
+            "scripts": [
+              "node_modules/jquery/dist/jquery.min.js",
+              "src/app/core/preloader/preloader.js",
+              "node_modules/chart.js/dist/Chart.bundle.js",
+              "node_modules/bootstrap/js/dist/util.js",
+              "node_modules/bootstrap/js/dist/modal.js",
+              "node_modules/bootstrap/js/dist/dropdown.js",
+              "node_modules/bootstrap/js/dist/tooltip.js",
+              "node_modules/summernote/dist/summernote.js",
+              "node_modules/moment/min/moment-with-locales.min.js",
+              "node_modules/echarts/dist/echarts.min.js",
+              "src/assets/common/laydate-5.0.9/laydate.js",
+              "src/assets/common/quagga.min.js",
+              "src/assets/common/renderer.js",
+              "src/assets/common/aliplayer/aliplayer.js"
+            ]
+          },
+          "configurations": {
+            "production": {
+              "fileReplacements": [
+                {
+                  "replace": "src/environments/environment.ts",
+                  "with": "src/environments/environment.prod.ts"
+                }
+              ],
+              "optimization": true,
+              "aot": true,
+              "outputHashing": "all",
+              "sourceMap": false,
+              "extractCss": true,
+              "namedChunks": false,
+              "extractLicenses": true,
+              "vendorChunk": false,
+              "buildOptimizer": true,
+              "budgets": [
+                {
+                  "type": "initial",
+                  "maximumWarning": "3mb",
+                  "maximumError": "5mb"
+                },
+                {
+                  "type": "anyComponentStyle",
+                  "maximumWarning": "6kb",
+                  "maximumError": "10kb"
+                }
+              ]
+            }
+          }
+        },
+        "serve": {
+          "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "browserTarget": "ArtWeb-V3:build"
+          },
+          "configurations": {
+            "production": {
+              "browserTarget": "ArtWeb-V3:build:production"
+            }
+          }
+        },
+        "extract-i18n": {
+          "builder": "@angular-devkit/build-angular:extract-i18n",
+          "options": {
+            "browserTarget": "ArtWeb-V3:build"
+          }
+        },
+        "test": {
+          "builder": "@angular-devkit/build-angular:karma",
+          "options": {
+            "main": "src/test.ts",
+            "polyfills": "src/polyfills.ts",
+            "tsConfig": "tsconfig.spec.json",
+            "karmaConfig": "karma.conf.js",
+            "assets": [
+              "src/favicon.ico",
+              "src/assets"
+            ],
+            "styles": [
+              "src/app/core/preloader/preloader.scss",
+              "src/styles.scss"
+            ],
+            "scripts": [
+              "node_modules/jquery/dist/jquery.js",
+              "src/app/core/preloader/preloader.js",
+              "node_modules/chart.js/dist/Chart.bundle.js",
+              "node_modules/bootstrap/js/dist/util.js",
+              "node_modules/bootstrap/js/dist/modal.js",
+              "node_modules/bootstrap/js/dist/dropdown.js",
+              "node_modules/bootstrap/js/dist/tooltip.js",
+              "node_modules/summernote/dist/summernote.js",
+              "node_modules/moment/min/moment-with-locales.min.js"
+            ]
+          }
+        },
+        "lint": {
+          "builder": "@angular-devkit/build-angular:tslint",
+          "options": {
+            "tsConfig": [
+              "tsconfig.app.json",
+              "tsconfig.spec.json",
+              "e2e/tsconfig.json"
+            ],
+            "exclude": [
+              "**/node_modules/**"
+            ]
+          }
+        },
+        "e2e": {
+          "builder": "@angular-devkit/build-angular:protractor",
+          "options": {
+            "protractorConfig": "e2e/protractor.conf.js",
+            "devServerTarget": "ArtWeb-V3:serve"
+          },
+          "configurations": {
+            "production": {
+              "devServerTarget": "ArtWeb-V3:serve:production"
+            }
+          }
+        }
+      }
+    }
+  },
+  "defaultProject": "ArtWeb-V3",
+  "cli": {
+    "analytics": false
+  }
+}

+ 12 - 0
browserslist

@@ -0,0 +1,12 @@
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+
+# You can see what browsers were selected by your queries by running:
+#   npx browserslist
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+IE 11

+ 13 - 0
build.bat

@@ -0,0 +1,13 @@
+@echo off
+set HOUR=%time:~0,2%
+:: 当时钟小于等于9时,前面有个空格,需要个位数的时候前面补上一个0, LEQ表示小于等于  
+if %HOUR% LEQ 9 set HOUR=0%time:~1,1%
+
+@set curtime=%date:~0,4%%date:~5,2%%date:~8,2%%HOUR%%time:~3,2%%time:~6,2%
+
+call ng build --prod --buildOptimizer=true --base-href /a/  
+call cd dist
+zip  -9qrx=assets/*  ../dist%curtime%.zip . 
+cd .. 
+echo  Dist filename:dist%curtime%.zip
+pause

+ 32 - 0
e2e/protractor.conf.js

@@ -0,0 +1,32 @@
+// @ts-check
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+/**
+ * @type { import("protractor").Config }
+ */
+exports.config = {
+  allScriptsTimeout: 11000,
+  specs: [
+    './src/**/*.e2e-spec.ts'
+  ],
+  capabilities: {
+    'browserName': 'chrome'
+  },
+  directConnect: true,
+  baseUrl: 'http://localhost:4200/',
+  framework: 'jasmine',
+  jasmineNodeOpts: {
+    showColors: true,
+    defaultTimeoutInterval: 30000,
+    print: function() {}
+  },
+  onPrepare() {
+    require('ts-node').register({
+      project: require('path').join(__dirname, './tsconfig.json')
+    });
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+  }
+};

+ 23 - 0
e2e/src/app.e2e-spec.ts

@@ -0,0 +1,23 @@
+import { AppPage } from './app.po';
+import { browser, logging } from 'protractor';
+
+describe('ng2angle App', () => {
+  let page: AppPage;
+
+  beforeEach(() => {
+    page = new AppPage();
+  });
+
+  it('should display Angle in h1 tag', () => {
+    page.navigateTo();
+    expect(page.getParagraphText()).toEqual('Angle');
+  });
+
+  afterEach(async () => {
+    // Assert that there are no errors emitted from the browser
+    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
+    expect(logs).not.toContain(jasmine.objectContaining({
+      level: logging.Level.SEVERE,
+    } as logging.Entry));
+  });
+});

+ 11 - 0
e2e/src/app.po.ts

@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+    navigateTo() {
+        return browser.get(browser.baseUrl) as Promise<any>;
+    }
+
+    getParagraphText() {
+        return element(by.css('app-root h1')).getText() as Promise<string>;
+    }
+}

+ 13 - 0
e2e/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "extends": "../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../out-tsc/e2e",
+    "module": "commonjs",
+    "target": "es5",
+    "types": [
+      "jasmine",
+      "jasminewd2",
+      "node"
+    ]
+  }
+}

+ 32 - 0
modernizr-config.json

@@ -0,0 +1,32 @@
+{
+    "classPrefix": "",
+    "options": [
+        "setClasses"
+    ],
+    "feature-detects": [
+        "css/backgroundposition-shorthand",
+        "css/backgroundposition-xy",
+        "css/backgroundrepeat",
+        "css/backgroundsizecover",
+        "css/borderradius",
+        "css/animations",
+        "css/calc",
+        "css/transforms",
+        "css/transforms3d",
+        "css/transformstylepreserve3d",
+        "css/transitions",
+        "css/flexboxtweener",
+        "css/fontface",
+        "svg",
+        "svg/asimg",
+        "svg/clippaths",
+        "svg/filters",
+        "svg/foreignobject",
+        "svg/inline",
+        "svg/smil",
+        "storage/localstorage",
+        "storage/sessionstorage",
+        "storage/websqldatabase",
+        "css/multiplebgs"
+    ]
+}

+ 88 - 0
package.json

@@ -0,0 +1,88 @@
+{
+  "name": "ArtWeb",
+  "version": "0.0.0",
+  "description": "ArtWeb - V3",
+  "author": "@YeFeng",
+  "license": "www.hmsoft.cn",
+  "private": true,
+  "scripts": {
+    "ng": "ng",
+    "start": "ng serve --proxy-config proxy.conf.json",
+    "build": "ng build --prod",
+    "test": "ng test",
+    "lint": "ng lint",
+    "modernizr": "modernizr -c modernizr-config.json -d src/modernizr.js",
+    "e2e": "ng e2e"
+  },
+  "dependencies": {
+    "@angular/animations": "10.0.8",
+    "@angular/common": "10.0.8",
+    "@angular/compiler": "10.0.8",
+    "@angular/core": "10.0.8",
+    "@angular/forms": "10.0.8",
+    "@angular/platform-browser": "10.0.8",
+    "@angular/platform-browser-dynamic": "10.0.8",
+    "@angular/router": "10.0.8",
+    "@circlon/angular-tree-component": "10.0.0",
+    "@fortawesome/fontawesome-free": "5.14.0",
+    "@ngx-translate/core": "13.0.0",
+    "@ngx-translate/http-loader": "6.0.0",
+    "@popperjs/core": "2.4.4",
+    "@types/echarts": "^4.8.0",
+    "@types/jquery": "^3.5.1",
+    "angular-material-picker": "^0.7.7",
+    "bootstrap": "4.5.2",
+    "chart.js": "2.9.3",
+    "classlist.js": "1.1.20150312",
+    "core-js": "3.6.5",
+    "echarts": "^4.9.0",
+  "enhanced-resolve": "4.3.0",
+    "intl": "1.2.5",
+    "jquery": "3.5.1",
+    "jquery-slimscroll": "1.3.8",
+    "jquery.browser": "0.1.0",
+    "lodash": "4.17.20",
+    "modernizr": "3.11.3",
+    "moment": "2.27.0",
+    "ng2-charts": "2.4.0",
+    "ng2-file-upload": "1.4.0",
+    "ngx-bootstrap": "5.6.1",
+    "ngx-color-picker": "10.0.0",
+    "ngx-cookie-service": "^10.0.1",
+    "ngx-custom-validators": "9.1.0",
+    "ngx-echarts": "^5.1.2",
+    "ngx-img-cropper": "10.0.0",
+    "ngx-infinite-scroll": "9.0.0",
+    "ngx-loading": "^8.0.0",
+    "ngx-select-ex": "4.0.0",
+    "ngx-toastr": "13.0.0",
+    "ngx-viewer": "^1.0.8",
+    "rxjs": "6.6.2",
+    "rxjs-compat": "6.6.2",
+    "screenfull": "5.0.2",
+    "simple-line-icons": "2.5.5",
+    "source-sans-pro": "3.6.0",
+    "spinkit": "2.0.1",
+    "summernote": "0.8.18",
+    "sweetalert": "2.1.2",
+    "ts-helpers": "1.1.2",
+    "tslib": "2.0.1",
+    "viewerjs": "^1.11.3",
+    "weather-icons": "1.3.2",
+    "web-animations-js": "2.3.2",
+    "zone.js": "0.11.1"
+  },
+  "devDependencies": {
+    "@angular-devkit/build-angular": "0.1000.8",
+    "@angular/cli": "10.0.8",
+    "@angular/compiler-cli": "10.0.8",
+    "@angular/language-service": "10.0.14",
+    "@types/node": "14.6.2",
+    "@types/lodash": "4.14.161",
+    "codelyzer": "6.0.0",
+    "loaders.css": "0.1.2",
+    "ts-node": "9.0.0",
+    "tslint": "6.1.3",
+    "typescript": "3.9.7"
+  }
+}

+ 7 - 0
proxy.conf.json

@@ -0,0 +1,7 @@
+{
+  "/":{
+    "target":"http://127.0.0.1:8080"
+  }
+}
+
+

+ 3 - 0
src/app/app.component.html

@@ -0,0 +1,3 @@
+<router-outlet></router-outlet>
+<div toastContainer></div>
+<ngx-loading [show]="varService['SHOW_LOADING']" [config]="loadConfig"></ngx-loading>

+ 0 - 0
src/app/app.component.scss


+ 87 - 0
src/app/app.component.ts

@@ -0,0 +1,87 @@
+import {Component, HostBinding, Injector, OnInit, Type, ViewChild} from '@angular/core';
+
+import {SettingsService} from './core/settings/settings.service';
+import {ToastContainerDirective, ToastrService} from "ngx-toastr";
+import {ngxLoadingAnimationTypes} from "ngx-loading";
+import {VarService} from "./core/service/var.service";
+import {DomSanitizer} from "@angular/platform-browser";
+import {HttpClient} from "@angular/common/http";
+import {CookieService} from "ngx-cookie-service";
+
+@Component({
+  selector: 'app-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
+})
+export class AppComponent implements OnInit {
+
+  @HostBinding('class.layout-fixed') get isFixed() {
+    return this.settings.getLayoutSetting('isFixed');
+  };
+
+  @HostBinding('class.aside-collapsed') get isCollapsed() {
+    return this.settings.getLayoutSetting('isCollapsed');
+  };
+
+  @HostBinding('class.layout-boxed') get isBoxed() {
+    return this.settings.getLayoutSetting('isBoxed');
+  };
+
+  @HostBinding('class.layout-fs') get useFullLayout() {
+    return this.settings.getLayoutSetting('useFullLayout');
+  };
+
+  @HostBinding('class.hidden-footer') get hiddenFooter() {
+    return this.settings.getLayoutSetting('hiddenFooter');
+  };
+
+  @HostBinding('class.layout-h') get horizontal() {
+    return this.settings.getLayoutSetting('horizontal');
+  };
+
+  @HostBinding('class.aside-float') get isFloat() {
+    return this.settings.getLayoutSetting('isFloat');
+  };
+
+  @HostBinding('class.offsidebar-open') get offsidebarOpen() {
+    return this.settings.getLayoutSetting('offsidebarOpen');
+  };
+
+  @HostBinding('class.aside-toggled') get asideToggled() {
+    return this.settings.getLayoutSetting('asideToggled');
+  };
+
+  @HostBinding('class.aside-collapsed-text') get isCollapsedText() {
+    return this.settings.getLayoutSetting('isCollapsedText');
+  };
+
+  varService;
+  loadConfig = {
+    animationType: ngxLoadingAnimationTypes.circle,
+    primaryColour: '#dd0031',
+    secondaryColour: '#ffffff',
+    backdropBorderRadius: '8px'
+  }
+  @ViewChild(ToastContainerDirective, {static: true}) toastContainer: ToastContainerDirective;
+
+  constructor(public settings: SettingsService, private toastrService: ToastrService, private  injector: Injector,
+              private sanitizer: DomSanitizer,private http: HttpClient,private cookie: CookieService) {
+    // 设置全局变量
+    this.varService = VarService;
+    VarService.INJECTOR = injector;
+    VarService.DOM_SANITIZER = sanitizer;
+    VarService.HTTP_CLIENT = http;
+    VarService.COOKIE_SERVICE = cookie;
+  }
+
+  ngOnInit() {
+    this.toastrService.overlayContainer = this.toastContainer;
+    VarService.TOASTR = this.toastrService;
+    // prevent empty links to reload the page
+    document.addEventListener('click', e => {
+      const target = e.target as HTMLElement;
+      if (target.tagName === 'A' && ['', '#'].indexOf(target.getAttribute('href')) > -1)
+        e.preventDefault();
+    });
+  }
+}

+ 69 - 0
src/app/app.module.ts

@@ -0,0 +1,69 @@
+import {BrowserModule} from '@angular/platform-browser';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; // this is needed!
+import {Injector, LOCALE_ID, NgModule} from '@angular/core';
+import {HttpClientModule, HttpClient} from '@angular/common/http';
+import {TranslateService, TranslateModule, TranslateLoader} from '@ngx-translate/core';
+import {TranslateHttpLoader} from '@ngx-translate/http-loader';
+import {AppComponent} from './app.component';
+import {CoreModule} from './core/core.module';
+import {LayoutModule} from './layout/layout.module';
+import {SharedModule} from './shared/shared.module';
+import {RoutesModule} from './routes/routes.module';
+import {HashLocationStrategy, LocationStrategy, registerLocaleData} from "@angular/common";
+import localeZh from '@angular/common/locales/zh';
+import * as $ from 'jquery';
+import {environment} from "../environments/environment";
+import {FrameService} from "./core/service/frame.service";
+import {ToastrService} from "ngx-toastr";
+import {MsgService} from "./core/service/msg.service";
+import {NgxLoadingModule} from "ngx-loading";
+
+export function createTranslateLoader(http: HttpClient) {
+  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
+}
+
+@NgModule({
+  declarations: [
+    AppComponent
+  ],
+  imports: [
+    HttpClientModule,
+    BrowserAnimationsModule, // required for ng2-tag-input
+    CoreModule,
+    LayoutModule,
+    SharedModule.forRoot(),
+    RoutesModule,
+    TranslateModule.forRoot({
+      loader: {
+        provide: TranslateLoader,
+        useFactory: (createTranslateLoader),
+        deps: [HttpClient]
+      }
+    }),
+    NgxLoadingModule
+  ],
+  providers: [ {provide: LOCALE_ID, useValue: 'zh-CN'}],
+  bootstrap: [AppComponent]
+})
+export class AppModule {
+  /***********************
+   * 系统初始化,
+   * 1.所有静态变量设置
+   * 2.系统登录,获取系统参数、资源、权限、字典等
+   */
+  constructor(private injector: Injector, private http: HttpClient) {
+    // 中文环境
+    registerLocaleData(localeZh);
+
+    http.get(`${environment.webRoot}/frame/optr/init.htm`).toPromise().then((data) => {
+      if (data['success'] === false) {
+        window.location.href = './assets/login/login.html';
+        return;
+      }
+      FrameService.initializeAfterLoin(injector, data['map']);
+    }) .catch((res)=>{
+      alert('Service is not running!');
+      window.location.href = './assets/login/login.html';
+    });
+  }
+}

+ 27 - 0
src/app/core/core.module.ts

@@ -0,0 +1,27 @@
+import { NgModule, Optional, SkipSelf } from '@angular/core';
+
+import { SettingsService } from './settings/settings.service';
+import { ThemesService } from './themes/themes.service';
+import { TranslatorService } from './translator/translator.service';
+import { MenuService } from './menu/menu.service';
+
+import { throwIfAlreadyLoaded } from './module-import-guard';
+
+@NgModule({
+    imports: [
+    ],
+    providers: [
+        SettingsService,
+        ThemesService,
+        TranslatorService,
+        MenuService
+    ],
+    declarations: [],
+    exports: [
+    ]
+})
+export class CoreModule {
+    constructor( @Optional() @SkipSelf() parentModule: CoreModule) {
+        throwIfAlreadyLoaded(parentModule, 'CoreModule');
+    }
+}

+ 1 - 0
src/app/core/frame/ajax/frame-ajax/frame-ajax.html

@@ -0,0 +1 @@
+<ng-container></ng-container>

+ 187 - 0
src/app/core/frame/ajax/frame-ajax/frame-ajax.ts

@@ -0,0 +1,187 @@
+import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
+import {BsModalService} from "ngx-bootstrap/modal";
+import {AjaxService} from "../../../service/ajax.service";
+import {Pager} from "../../../service/pager";
+
+@Component({
+  selector: 'frame-ajax',
+  templateUrl: './frame-ajax.html'
+})
+export class FrameAjax implements OnInit, OnDestroy {
+
+  /*******************
+   * 请求地址
+   */
+  @Input()
+  url: string;
+  /**********************
+   * 请求是否显示操作等待界面
+   */
+  @Input()
+  requestWithLoading = false;
+
+  /*********************
+   * 返回数据类型
+   */
+  @Input()
+  ajaxDataType = 'entity';
+
+  /********************
+   * 是否自动请求数据
+   */
+  @Input()
+  autoRequest = true;
+
+  /*****************
+   * 请求的数据
+   */
+  @Input()
+  requestData: {};
+  /******************
+   * 请求参数信息
+   */
+  @Input()
+  requestParam: any;
+
+  /****************
+   * 请求后回调参数
+   */
+  @Input()
+  callback: Function;
+  /****************
+   * 轮询请求间隔时长,单位秒
+   */
+  @Input()
+  intervalSeconds: number = 0;
+
+
+  /****************
+   * 数据加载完毕
+   */
+  @Output()
+  ajaxRequestComplete = new EventEmitter<Pager>();
+
+  // 定时器,用于轮询时使用
+  private intervalId: any;
+  // 请求回来的数据
+  private responseData: any;
+  // 请求回来的map
+  private responseMap: any;
+  // 请求回来的数组
+  private responseArray: any;
+  // 请求返回的实体
+  private responseEntity: any;
+
+  // bsService用于打开弹出框
+  constructor(private bsService: BsModalService) {
+    this.responseData = {};
+    this.responseMap = {};
+    this.responseArray = [];
+    this.responseEntity = {};
+  }
+
+  ngOnInit(): void {
+    if (this.autoRequest) {
+      setTimeout(this.request.bind(this), 5);
+    }
+    // 判断是否是不间断请求
+    if (this.intervalSeconds > 0)
+      this.intervalId = setInterval(this.request.bind(this), this.intervalSeconds * 1000);
+  }
+
+  /**********************
+   * Ajax请求
+   */
+  request(url?: string, data?: {}, callback?: Function, param?: {}, options?: {}) {
+    // 实际请求的url
+    const vUrl = url || this.url;
+    // 实际请求的数据
+    const vData = Object.assign({}, this.requestData, data);
+    // 实际的回调函数
+    const vCallback = callback || this.callback;
+    const vParam = Object.assign({}, this.requestParam, param);
+    if (vParam.showMsg === undefined)
+      vParam.showMsg = false;
+
+    if (vParam.RequestDataType === undefined)
+      vParam.RequestDataType = this.ajaxDataType;
+    let fn = function (response) {
+      if (vParam.RequestDataType === 'entity') {
+        this.responseEntity = response.entity;
+      } else if (vParam.RequestDataType === 'map') {
+        this.responseMap = response.map;
+      } else if (vParam.RequestDataType === 'array') {
+        this.responseArray = response.array;
+      } else {
+        this.responseData = response;
+      }
+
+      if (vCallback)
+        vCallback.call(undefined, response);
+      this.ajaxRequestComplete.emit(response);
+    };
+    vParam.callback = fn.bind(this);
+    if(vUrl) {
+      AjaxService.ajaxRequest(vUrl, vData, vParam, options);
+    }
+
+  }
+
+  /****************
+   * Ajax请求返回Map对象
+   */
+  public getMap() {
+    return this.responseMap;
+  }
+
+  /****************
+   * Ajax请求返回Array对象
+   */
+  public getArray() {
+    return this.responseArray;
+  }
+
+  /****************
+   * Ajax请求返回Entity对象
+   */
+  public getEntity() {
+    return this.responseEntity;
+  }
+
+  /****************
+   * Ajax请求返回
+   */
+  public getData() {
+    return this.responseData;
+  }
+
+
+  /******************
+   * 销毁前清理定时器
+   */
+  ngOnDestroy(): void {
+    clearTimeout(this.intervalId);
+  }
+
+    /****************
+   * 重新加载
+   */
+  reload(data?) {
+    this.request(undefined, data);
+  }
+
+  public optrRecord(url: string, data: {}, param?: { successMsg?: string, successMsgType?: string, confirmMsg?: string, callback?: Function, showMsg?: boolean,}, options?: { RequestWithLoading?: boolean }) {
+    param = param || {};
+    let callback = param.callback || function () {
+    };
+    let fn = function (response) {
+      callback.call(undefined, response);
+      this.reload();
+      this.ajaxRequestComplete.emit(response);
+    };
+    param.callback = fn.bind(this);
+    AjaxService.ajaxRequest(url, data, param, options);
+
+  }
+
+}

+ 64 - 0
src/app/core/frame/pager/frame-pager/frame-pager.html

@@ -0,0 +1,64 @@
+<div class="row wd-wide mb-2">
+  <div class="col-xl-3 col-md-3">
+    <div class="input-group" *ngIf="showSearch">
+      <input class="form-control ml-2" [style]="searchStyle" [class]="searchCss" type="text"
+             [(ngModel)]="query" [readonly]="dataLoading"
+             (keyup)="goSearch($event)" [placeholder]="'pagination.search' | translate"/>
+      <div class="input-group-append">
+        <button class="btn btn-outline-secondary bg-white" (click)="goSearch(undefined)" [disabled]="dataLoading">
+          <em class="fa fas fa-search"></em>
+        </button>
+      </div>
+    </div>
+  </div>
+
+  <div class="col-lg-9 col-md-9 ">
+    <div class="row float-right">
+      <div class="mt-2">
+        <div class="row">
+          <div class="input-group">{{'pagination.eachpage' | translate}} <input type="text" [(ngModel)]="pagerLimit" [readonly]="!allowChangePagerLimit"
+                                             [style.width]="getPageLimitWidth()" (keyup)="changePageLimit($event)"
+                                             style="text-align:center; margin-left:2px; margin-top: -2px; border:0px; background-color: transparent">{{'pagination.record' | translate}}&nbsp;
+            {{'pagination.di' | translate}}<input type="text" [(ngModel)]="pageIndex" (keyup)="changePageIndex($event)"
+                    [style.width]="getPageIndexWidth()" [readonly]="!allowChangePagerCurrent"
+                    style="text-align:center;margin-left:2px; margin-top: -2px; border:0px; background-color: transparent">{{'pagination.page' | translate}},
+            {{'pagination.total' | translate}}{{pageTotal}}{{'pagination.page' | translate}}({{recordTotal}}{{'pagination.records' | translate}})
+          </div>
+        </div>
+      </div>
+      <div class="mt-1 ml-4 align-items-center justify-content-center" *ngIf="dataLoading">
+        <div class="semi-circle-spin" style="width:20px;height:20px;">
+          <div style="background-color: black"></div>
+        </div>
+      </div>
+      <ul [class]="dataLoading === true?'pagination ml-2':'pagination ml-4'" *ngIf="showPagination && !dataLoading">
+        <li class="pagination-first page-item" *ngIf="showFirstPage">
+          <a class="page-link cursor-hand" (click)="goFirst()">
+            <em class="fa fas fa-fast-backward"></em>
+          </a>
+        </li>
+        <li class="pagination-prev page-item" *ngIf="hasPrevGroup">
+          <a class="page-link cursor-hand" (click)="goPrevGroup()">
+            <em class="fa fa-angle-double-left"></em>
+          </a>
+        </li>
+        <ng-container *ngFor="let pg of pageArray">
+          <li [class.active]="pg.active" [class.disabled]="disabled && !pg.active" *ngIf="pg.data" class="pagination-page page-item">
+            <span class="page-link cursor-hand" [innerHTML]="pg.number" (click)="goPage(pg)"></span>
+          </li>
+        </ng-container>
+        <li class="pagination-next page-item" *ngIf="hasNextGroup">
+          <a class="page-link cursor-hand" (click)="goNextGroup()">
+            <em class="fa fa-angle-double-right"></em>
+          </a>
+        </li>
+        <li class="pagination-last page-item" *ngIf="showLastPage">
+          <a class="page-link cursor-hand" (click)="goLast()">
+            <em class="fa fas fa-fast-forward"></em>
+          </a>
+        </li>
+      </ul>
+    </div>
+  </div>
+
+</div>

+ 421 - 0
src/app/core/frame/pager/frame-pager/frame-pager.ts

@@ -0,0 +1,421 @@
+import {Component, Input, OnDestroy, OnInit, Output, EventEmitter} from '@angular/core';
+import {CookieService} from '../../../service/cookie.service';
+import {FrameService} from '../../../service/frame.service';
+import {MsgService} from '../../../service/msg.service';
+import {AjaxService} from '../../../service/ajax.service';
+import {Pager} from '../../../service/pager';
+import {Ajax} from '../../../service/ajax';
+
+@Component({
+  selector: 'frame-pager',
+  templateUrl: './frame-pager.html'
+})
+export class FramePager implements OnInit, OnDestroy {
+  /**********************
+   * 请求地址
+   */
+  @Input()
+  url: string;
+  /**********************
+   * 请求参数
+   */
+  @Input()
+  pagerParams: {};
+  /*************************
+   * 每个多少条数据
+   */
+  @Input()
+  pagerLimit: number = 20;
+  /**********************
+   * 一组显示几页
+   */
+  @Input()
+  pagerLength = 10;
+  /**********************
+   * 是否自动去请求数据
+   */
+  @Input()
+  autoRequest: boolean = true;
+  /*********************
+   * 是否需要定时加在数据
+   */
+  @Input()
+  loopRequestTicket: number = 0;
+  /**********
+   * 请求数据时是否显示加载等待界面
+   */
+  @Input()
+  requestWithLoading: boolean = false;
+  /*********************
+   * 是否显示查询框
+   */
+  @Input()
+  showSearch = true;
+  /*******************
+   * 是否显示分页组件
+   */
+  @Input()
+  showPagination = true;
+  /********************
+   * 是否允许修改每个大小
+   */
+  @Input()
+  allowChangePagerLimit = true;
+  /**********************
+   * 是否允许修改当前页面
+   */
+  @Input()
+  allowChangePagerCurrent = true;
+  /********************
+   * 查询框样式
+   */
+  @Input()
+  searchCss: string;
+  /********************
+   * 查询框样式
+   */
+  @Input()
+  searchStyle = {'width': '60px'};
+  /****************
+   * 数据加载完毕
+   */
+  @Output()
+  pageAjaxComplete = new EventEmitter<Pager>();
+  /********************
+   * 操作完毕
+   */
+  @Output()
+  optrAjaxComplete = new EventEmitter<Ajax>();
+
+  // 内部定时器,用于定时刷新时使用
+  intervalId: any;
+  // 所有分页数据
+  pageArray: any;
+  // 当前分页数据
+  pageRecords: any;
+  // 开始的记录条数
+  pageStart = 1;
+  // 当前第几页
+  pageIndex = 1;
+  // 总的页码
+  pageTotal = 1;
+  // 总记录数
+  recordTotal = 0;
+  // 返回的参数
+  responseMap;
+
+
+  // 总组数
+  totalGroup = 0;
+  // 当前在第几组
+  currentGroup = 0;
+  // 是否有下一组页面
+  hasNextGroup = false;
+  // 是否有前一组页面
+  hasPrevGroup = false;
+  // 是否显示第一个页
+  showFirstPage = false;
+  // 是否显示最后一页
+  showLastPage = false;
+
+  // 是否正在加载数据
+  dataLoading = false;
+  // 查询值
+  query: string;
+  // 排序字段
+  orderColumn = '';
+  // 排序方式
+  orderType = '';
+  disabled = false;
+
+
+  static cookieName_PageLimit = 'Frame.PagerLimit';
+
+  constructor() {
+    this.pageArray = [], this.pageRecords = [];
+  }
+
+  ngOnInit(): void {
+    // 取每页多少条记录
+    this.pagerLimit = this.pagerLimit || CookieService.get(FramePager.cookieName_PageLimit) ||
+      parseInt(FrameService.getParamValue('FramePageLimit', 20), 10);
+    // 初始化分页的页面数
+    for (let i = 0; i < this.pagerLength; i++) {
+      this.pageArray.push({active: false, number: i + 1, data: []});
+    }
+    if (this.autoRequest)
+      setTimeout(this.requestData.bind(this), 0);
+
+  }
+
+  /**************************
+   * 请求数据
+   */
+  requestData(params?: {}) {
+    if (this.url === '' || this.url === undefined) {
+      MsgService.errorAlert('未设置请求地址,请检查!');
+      return;
+    }
+    const rData = Object.assign({}, this.pagerParams, params);
+    rData['limit'] = this.pagerLimit;
+    rData['start'] = this.pageStart - 1;
+    if (this.query && this.query.trim().length > 0)
+      rData['query'] = this.query;
+    if (this.orderColumn !== '')
+      rData['order'] = this.orderColumn;
+    if (this.orderType !== '')
+      rData['type'] = this.orderType;
+    this.dataLoading = true;
+    AjaxService.requestPage(this.url, rData, (page) => {
+        page.index = page.index + 1;
+        this.pageIndex = page.index;
+        // 总记录数
+        this.recordTotal = page.total;
+        // 记录
+        this.pageRecords = page.records;
+        // 总页数
+        this.pageTotal = page.count;
+        // 参数信息
+
+        this.responseMap = page.param;
+        // 当前页面
+        const pIndex = page.index;
+        //
+        // 计算在第几组
+        this.totalGroup = Math.ceil(page.count / this.pagerLength);
+        // 当前分组
+        this.currentGroup = Math.ceil(page.index / this.pagerLength);
+
+        // 如果是第一分组,不显示
+        if (this.currentGroup === 1) {
+          this.hasPrevGroup = false;
+          this.showFirstPage = false;
+        } else {
+          this.hasPrevGroup = true;
+          this.showFirstPage = true;
+        }
+
+        // 判断是否显示最后一组
+        if (this.currentGroup >= this.totalGroup) {
+          this.hasNextGroup = false;
+          this.showLastPage = false;
+        } else {
+          this.hasNextGroup = true;
+          this.showLastPage = true;
+        }
+
+        for (let i = 0; i < this.pagerLength; i++) {
+          const pageNumber = (this.currentGroup - 1) * this.pagerLength + 1 + i;
+          this.pageArray[i] = {
+            active: pageNumber === pIndex ? true : false,
+            number: pageNumber,
+            data: pageNumber <= this.pageTotal ? true : false
+          };
+        }
+        this.dataLoading = false;
+        // 判断是否需要间隔获取数据
+        if (this.loopRequestTicket > 0 && this.intervalId === undefined) {
+          this.intervalId = setInterval(this.requestData.bind(this), this.loopRequestTicket * 1000);
+        }
+        this.pageAjaxComplete.emit(page);
+        // console.log(page);
+      }, {
+        'RequestWithLoading': this.requestWithLoading,
+        'ErrorCallback': () => {
+          this.dataLoading = false;
+          // 判断是否需要间隔获取数据
+          if (this.loopRequestTicket > 0 && this.intervalId === undefined) {
+            this.intervalId = setInterval(this.reload.bind(this), this.loopRequestTicket * 1000);
+          }
+        }
+      }
+    );
+  }
+
+  /***************
+   * 重新请求数据
+   */
+  public reload(param?: any) {
+    this.requestData(param);
+  }
+
+  /********************
+   * 修改每页条数
+   */
+  changePageLimit(event) {
+    if (event.keyCode === 13) {
+      const temp = parseInt(`${this.pageIndex}`, 10);
+      if (isNaN(temp) || temp < 1) {
+        this.pageIndex = 1;
+      } else {
+        this.pageIndex = temp;
+      }
+      this.pageStart = 1;
+      this.requestData();
+      CookieService.set(FramePager.cookieName_PageLimit, `${this.pagerLimit}`);
+    }
+  }
+
+  /******************
+   * 修改当前页面
+   */
+  changePageIndex(event) {
+    if (event.keyCode === 13) {
+      const temp = parseInt(this.pageIndex + '', 10);
+      if (isNaN(temp) || temp < 1) {
+        this.pageIndex = 1;
+      } else if (this.pageIndex > this.pageIndex) {
+        this.pageIndex = temp;
+      } else {
+        this.pageIndex = temp;
+      }
+      this.pageStart = (this.pageIndex - 1) * this.pagerLimit + 1;
+      this.requestData();
+    }
+  }
+
+  /*******************
+   * 敲回车或者点击查询按钮
+   */
+  goSearch(event) {
+    if (event) {
+      if (event.keyCode !== 13) {
+        return;
+      }
+    }
+    this.pageStart = 1;
+    this.requestData();
+  }
+
+  /****************
+   * 点击页面跳转
+   */
+  goPage(page) {
+    // 开始页面
+    this.pageStart = (page.number - 1) * this.pagerLimit + 1;
+    this.requestData();
+  }
+
+  /********************
+   * 跳转到第一页
+   */
+  goFirst() {
+    this.pageStart = 1;
+    this.requestData();
+  }
+
+  /******************
+   * 跳转到最后一页
+   */
+  goLast() {
+    this.pageStart = (this.pageTotal - 1) * this.pagerLimit + 1;
+    this.requestData();
+  }
+
+  /*******************
+   * 跳转到上一组
+   */
+  goPrevGroup() {
+    this.pageStart = ((this.currentGroup - 2) * this.pagerLength) * this.pagerLimit + 1;
+    this.requestData();
+  }
+
+  /*******************
+   * 跳转到下一组
+   */
+  goNextGroup() {
+    this.pageStart = (this.currentGroup * this.pagerLength) * this.pagerLimit + 1;
+    this.requestData();
+  }
+
+  /***************
+   * 排序
+   */
+  public sort(column) {
+    if (column === this.orderColumn) {
+      if (this.orderType === 'ASC') {
+        this.orderType = 'DESC';
+      } else {
+        this.orderType = 'ASC';
+      }
+    } else {
+      this.orderColumn = column;
+      this.orderType = 'ASC';
+    }
+    this.pageStart = 1;
+    this.requestData();
+  }
+
+  getPageLimitWidth() {
+    return (8 + (this.pagerLimit + '').length * 6) + 'px';
+  }
+
+  getPageIndexWidth() {
+    return (8 + (this.pageIndex + '').length * 6) + 'px';
+  }
+
+  /***************
+   * 获取完数据后回调函数
+   * @param response
+   * @private
+   */
+
+  /********************
+   * ajax请求后自动刷新page数据
+   */
+  public optrRecord(url: string, data: {}, param?: { successMsg?: string, successMsgType?: string, confirmMsg?: string, callback?: Function }, options?: { RequestWithLoading?: boolean }) {
+    param = param || {};
+    let callback = param.callback || function () {
+    };
+    let fn = function (response) {
+      callback.call(undefined, response);
+      this.reload();
+      this.optrAjaxComplete.emit(response);
+    };
+    param.callback = fn.bind(this);
+    AjaxService.ajaxRequest(url, data, param, options);
+  }
+
+  /********************
+   * 获取本页数据
+   */
+  public getRecords() {
+    return this.pageRecords;
+  }
+
+  public getResponseMap() {
+    return this.responseMap;
+  }
+  /*********************
+   * 销毁时清理定时器
+   */
+  ngOnDestroy(): void {
+    clearTimeout(this.intervalId);
+  }
+  
+  /*********************
+   *清除数据
+   */
+  public clear(): void {
+    this.pageRecords = [];
+    this.pageStart = 1;
+    // 当前第几页
+    this.pageIndex = 1;
+    // 总的页码
+    this.pageTotal = 1;
+    // 总记录数
+    this.recordTotal = 0;
+    // 是否有下一组页面
+    this.hasNextGroup = false;
+    // 是否有前一组页面
+    this.hasPrevGroup = false;
+    // 是否显示第一个页
+    this.showFirstPage = false;
+    // 是否显示最后一页
+    this.showLastPage = false;
+    // 总组数
+    this.totalGroup = 0;
+    this.currentGroup = 0;
+}
+
+}

+ 47 - 0
src/app/core/frame/upload/frame-upload/frame-upload.html

@@ -0,0 +1,47 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">{{uploadTitle}}</h4>
+  <div [hidden]="true">
+    <input #elMultipleUpload id="elMultFile" type="file" ng2FileSelect [uploader]="fileUploader" multiple/>
+    <input #elSimpleUpload id="elSimpleFile" type="file" ng2FileSelect [uploader]="fileUploader"/>
+  </div>
+</div>
+<div class="modal-body">
+  <table class="table table-borderless">
+    <tbody>
+    <tr *ngFor="let item of fileUploader.queue">
+      <td><strong>{{ item?.file?.name }}</strong></td>
+      <td *ngIf="fileUploader.options.isHTML5" nowrap>{{ item?.file?.size / 1024 / 1024 | number:'.2' }} MB</td>
+      <td *ngIf="fileUploader.options.isHTML5">
+        <div class="progress" style="margin-bottom: 0; width:100px;">
+          <div class="progress-bar" role="progressbar" [ngStyle]="{ 'width': fileUploader.progress + '%' }"></div>
+        </div>
+      </td>
+      <td class="text-center">
+        <span *ngIf="item.isSuccess"><i class="fa fa-ok"></i></span>
+        <span *ngIf="item.isCancel"><i class="icon-close mr"></i></span>
+        <span *ngIf="item.isError"><i class="fas fa-times"></i></span>
+      </td>
+    </tr>
+    </tbody>
+  </table>
+</div>
+
+<div class="modal-footer justify-content-between" *ngIf="!fileUploader.isUploading">
+  <div>
+    <button *ngIf="templateFile" class="btn btn-primary" type="button" (click)="FuncService.fileDownload('frame/template/download.htm', {file_name: templateFile})">
+      下载模版
+    </button>
+  </div>
+  <div>
+    <button class="btn btn-primary" type="button" *ngIf="fileUploader.queue.length === 0" (click)="chooseFile()">
+      选择上传文件
+    </button>
+    <button class="btn btn-primary" type="button" *ngIf="fileUploader.queue.length > 0" (click)="chooseFile()">
+      重新选择文件
+    </button>
+    <button class="btn btn-success ml-3" type="button" *ngIf="fileUploader.queue.length > 0 && !uploadComplete" (click)="fileUploader.uploadAll()">
+      上传文件
+    </button>
+    <button class="btn btn-secondary ml-3" type="button" (click)="closeWindow()">关闭窗口</button>
+  </div>
+</div>

+ 210 - 0
src/app/core/frame/upload/frame-upload/frame-upload.ts

@@ -0,0 +1,210 @@
+import {Component, ElementRef, Input, OnInit, Output, ViewChild, EventEmitter} from '@angular/core';
+import {FileItem, FileUploader, ParsedResponseHeaders} from "ng2-file-upload";
+import {BsModalRef, BsModalService, ModalOptions} from "ngx-bootstrap/modal";
+import {FrameDetailComponent} from "../../../../routes/frame/core/detail/frame.detail";
+import {environment} from "../../../../../environments/environment";
+import {VarService} from "../../../service/var.service";
+import {MsgService} from "../../../service/msg.service";
+import {AjaxService} from "../../../service/ajax.service";
+import {ModalService} from "../../../service/modal.service";
+import {FrameThreadRunningComponent} from "../../../../routes/frame/thread/frame-thread-running/frame-thread-running.component";
+
+@Component({
+  selector: 'frame-upload',
+  templateUrl: './frame-upload.html'
+})
+export class FrameUpload extends FrameDetailComponent implements OnInit {
+  /******************
+   * 上传地址
+   */
+  uploadUrl: string;
+  /******************
+   * 标题
+   */
+  uploadTitle: string = '文件上传';
+  /*****************
+   * 是否允许上传多个文件
+   */
+  multiple = false;
+  /*************************
+   * 上传前提示信息
+   */
+  confirmMsg: string;
+  /************************
+   * 上传后提示信息
+   */
+  successMsg: string;
+  /************************
+   * 允许的文件类型
+   */
+  allowedFileType: [string];
+  /***********************
+   * 最大文件限制
+   */
+  maxFileSize: number;
+  /**********************
+   * 文件上传参数
+   */
+  uploadParams = {};
+  /**********
+   * 模版文件
+   */
+  templateFile: string;
+  /**********
+   * 成功回调
+   */
+  uploadCallback: Function;
+  /******************
+   * 是否自动关闭
+   */
+  autoClose = false;
+
+  /*******************
+   * 弹出主窗口
+   */
+  modalService:any;
+
+  // 上传是否失败
+  uploadFail = false;
+  // 上传是否完成
+  uploadComplete = false;
+  // 线程id
+  threadId: any;
+
+  // 上传核心组件
+  fileUploader: FileUploader;
+  @ViewChild('elSimpleUpload', {static: false}) elSimpleUpload: ElementRef;
+  @ViewChild('elMultipleUpload', {static: false}) elMultipleUpload: ElementRef;
+
+  constructor(bsModalRef: BsModalRef) {
+    super(bsModalRef);
+  }
+
+  ngOnInit() {
+    this.fileUploader = new FileUploader({
+      url: environment.uploadRoot + this.uploadUrl,
+      allowedFileType: this.allowedFileType,
+      method: 'post',
+      maxFileSize: this.maxFileSize * 1024,
+      isHTML5: true,
+      autoUpload: false, // 不自动上传
+      // removeAfterUpload: true 不自动移除
+      // headers: [{ name: 'Access-Control-Allow-Origin', value: '*'}]
+    });
+    this.fileUploader.onBuildItemForm = this.onBuildItemForm.bind(this);
+    this.fileUploader.onCompleteItem = this.onCompleteItem.bind(this);
+    this.fileUploader.onCompleteAll = this.onCompleteAll.bind(this);
+  }
+
+  /***********************
+   * 上传之前传递参数
+   */
+  private onBuildItemForm(fileItem: FileItem, form: any) {
+    fileItem.withCredentials = false;
+    const data = Object.assign({}, this.uploadParams, this.uploadParams);
+    for (const v in data) {
+      form.append(v, data[v]);
+    }
+  }
+
+  /***************************
+   * 单个文件完成上传
+   */
+  private onCompleteItem(item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) {
+    if (status === 404) {
+      MsgService.errorAlert(`无法访问上传地址,请联系系统管理员!`);
+      return;
+    }
+
+    if (status !== 200) {
+      // 非正常返回,直接提示错误信息、
+      MsgService.errorAlert(`文件上传出现异常,请稍后再试!`);
+      return;
+    }
+    if (response === undefined || response === '') {
+      return;
+    }
+    const ajax = JSON.parse(response);
+    if (ajax.success === undefined || ajax.success === false) {
+      MsgService.errorAlert(ajax.errorMsg);
+      this.uploadFail = true;
+    } else {
+      // 判断是否是后台线程
+      if (ajax.threadId) {
+        this.threadId = ajax.threadId;
+        // 如果返回的json有threadId,表示是一个后台线程
+        // ModalService.showThreadWindow(this.modalService,ajax.entity,{
+        //   callback:this.uploadCallback
+        // });
+        const modal = Object.assign({}, {
+          animated: true,
+          keyboard: false,
+          backdrop: true,
+          ignoreBackdropClick: true,
+          initialState:  {},
+          class: 'modal-5p'
+        }, {});
+        modal.initialState['ResponseEntity'] = ajax.entity;
+        modal.initialState['threadParam'] = {
+          callback:this.uploadCallback
+        };
+        this.modalService.show(FrameThreadRunningComponent, modal);
+        this.closeWindow();
+      } else {
+        if (this.uploadCallback)
+          this.uploadCallback.call(undefined, ajax);
+        if (this.autoClose)
+          this.closeWindow();
+      }
+      this.uploadFail = false;
+    }
+
+
+  }
+
+  private onCompleteAll() {
+    if (this.multiple === true) {
+      this.elMultipleUpload.nativeElement.value = '';
+    } else {
+      this.elSimpleUpload.nativeElement.value = '';
+    }
+    this.uploadComplete = true;
+
+    // 只有非线程的时候弹出提示信息
+    if (!this.uploadFail && this.successMsg !== undefined && this.threadId === undefined) {
+      MsgService.successAlert(this.successMsg);
+    }
+
+  }
+
+  chooseFile() {
+    this.fileUploader.clearQueue();
+    this.uploadComplete = false;
+    if (this.multiple === true) {
+      this.elMultipleUpload.nativeElement.click();
+    } else {
+      this.elSimpleUpload.nativeElement.click();
+    }
+  }
+
+  /******************
+   * 上传文件
+   */
+  uploadFiles(data?: any) {
+    const fn = () => {
+      this.uploadParams = data;
+      if (this.multiple === true) {
+        this.elMultipleUpload.nativeElement.click();
+      } else {
+        this.elSimpleUpload.nativeElement.click();
+      }
+    };
+    if (this.confirmMsg !== undefined) {
+      MsgService.confirmAlert(this.confirmMsg, fn);
+    } else {
+      fn.apply(undefined);
+    }
+  }
+
+
+}

+ 7 - 0
src/app/core/frame/valid/form-valid.html

@@ -0,0 +1,7 @@
+<span class="text-danger"
+      *ngIf="form?.controls[fieldName]?.hasError(errorCode)
+      && (form?.controls[fieldName]?.dirty || form?.controls[fieldName]?.touched)">
+  <span *ngIf="errorMsg">{{errorMsg}}</span>
+</span>
+
+

+ 57 - 0
src/app/core/frame/valid/form-valid.ts

@@ -0,0 +1,57 @@
+import {Component, Input, OnInit} from '@angular/core';
+import {FormGroup} from "@angular/forms";
+
+@Component({
+  selector: 'form-valid',
+  templateUrl: './form-valid.html'
+})
+export class FormValid implements OnInit {
+
+  @Input()
+  form: FormGroup;
+  @Input()
+  fieldName: string;
+  @Input()
+  errorCode = 'required';
+  @Input()
+  errorMsg: string;
+
+
+  private code: string;
+
+  constructor() {
+
+  }
+
+  ngOnInit(): void {
+    this.code = (this.errorCode.toLowerCase());
+    if (this.errorMsg === undefined) {
+      if (this.code === 'required') {
+        this.errorMsg = '此处为必填项';
+      } else if (this.code === 'minlength') {
+        this.errorMsg = '字符太短';
+      } else if (this.code === 'maxlength') {
+        this.errorMsg = '字符过长';
+      } else if (this.code === 'max') {
+        this.errorMsg = '数字过大';
+      } else if (this.code === 'min') {
+        this.errorMsg = '数字太小';
+      } else if (this.code === 'datetime') {
+        this.errorMsg = '请使用 yyyy-MM-dd HH:mi:ss格式';
+      } else if (this.code === 'date') {
+        this.errorMsg = '请使用 yyyy-MM-dd 格式';
+      } else if (this.code === 'ename') {
+        this.errorMsg = '只能使用a-z,A-Z,0-9和下划线';
+      } else if (this.code === 'mobile') {
+        this.errorMsg = '请输入正确的手机号';
+      } else if (this.code === 'password') {
+        this.errorMsg = '两次密码不一致';
+      }else
+        this.errorMsg = '数据格式错误';
+
+
+    }
+  }
+
+
+}

+ 31 - 0
src/app/core/menu/menu.service.ts

@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class MenuService {
+
+    menuItems: Array<any>;
+
+    constructor() {
+        this.menuItems = [];
+    }
+
+    addMenu(items: Array<{
+        text: string,
+        heading?: boolean,
+        link?: string,     // internal route links
+        elink?: string,    // used only for external links
+        target?: string,   // anchor target="_blank|_self|_parent|_top|framename"
+        icon?: string,
+        alert?: string,
+        submenu?: Array<any>
+    }>) {
+        items.forEach((item) => {
+            this.menuItems.push(item);
+        });
+    }
+
+    getMenu() {
+        return this.menuItems;
+    }
+
+}

+ 6 - 0
src/app/core/module-import-guard.ts

@@ -0,0 +1,6 @@
+// https://angular.io/styleguide#!#04-12
+export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) {
+  if (parentModule) {
+    throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`);
+  }
+}

+ 3 - 0
src/app/core/preloader/preloader.component.html

@@ -0,0 +1,3 @@
+<div class="preloader-progress">
+  <div class="preloader-progress-bar" [style.width]="loadCounter+'%'"></div>
+</div>

+ 51 - 0
src/app/core/preloader/preloader.js

@@ -0,0 +1,51 @@
+(function(global) {
+
+    var counter = 0, timeout;
+    var preloader = document.querySelector('.preloader');
+    var progressBar = document.querySelector('.preloader-progress-bar');
+    var body = document.querySelector('body');
+
+    // if preloader not present => abort
+    if (!preloader) return;
+
+    // disables scrollbar
+    body.style.overflow = 'hidden';
+
+    timeout = setTimeout(startCounter, 20);
+
+    // main.ts call this function once the app is boostrapped
+    global.appBootstrap = function() {
+        setTimeout(endCounter, 1000);
+    };
+
+    function startCounter() {
+        var remaining = 100 - counter;
+        counter = counter + (0.015 * Math.pow(1 - Math.sqrt(remaining), 2));
+
+        if (progressBar) progressBar.style.width = Math.round(counter) + '%';
+
+        timeout = setTimeout(startCounter, 20);
+    }
+
+    function endCounter() {
+
+        clearTimeout(timeout);
+
+        if (progressBar) progressBar.style.width = '100%';
+
+        setTimeout(function() {
+            // animate preloader hiding
+            removePreloader();
+            // retore scrollbar
+            body.style.overflow = '';
+        }, 300);
+    }
+
+    function removePreloader() {
+        preloader.addEventListener('transitionend', function() {
+            preloader.className = 'preloader-hidden';
+        });
+        preloader.className += ' preloader-hidden-add preloader-hidden-add-active';
+    };
+
+})(window);

+ 71 - 0
src/app/core/preloader/preloader.scss

@@ -0,0 +1,71 @@
+/* ========================================================================
+   Component: preloader
+ ========================================================================== */
+@import "../../shared/styles/bootstrap/_functions.scss";
+@import "../../shared/styles/bootstrap/_variables.scss";
+@import "../../shared/styles/bootstrap/_mixins";
+@import "../../shared/styles/app/variables";
+
+$preloader-bg: $info;
+$preloader-wd: 100px;
+$preloader-hg: 30px;
+
+/*@noflip*/
+.preloader {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+    background-color: $preloader-bg;
+    background-image: -webkit-linear-gradient(90deg, $preloader-bg 10%, darken($preloader-bg, 5%) 90%); /* Chrome 10+, Saf5.1+ */
+    background-image: -moz-linear-gradient(90deg, $preloader-bg 10%, darken($preloader-bg, 5%) 90%); /* FF3.6+ */
+    background-image: -ms-linear-gradient(90deg, $preloader-bg 10%, darken($preloader-bg, 5%) 90%); /* IE10 */
+    background-image: -o-linear-gradient(90deg, $preloader-bg 10%, darken($preloader-bg, 5%) 90%); /* Opera 11.10+ */
+    background-image: linear-gradient(90deg, $preloader-bg 10%, darken($preloader-bg, 5%) 90%); /* W3C */
+    z-index: 9999;
+    transition: opacity .65s;
+}
+
+/*@noflip*/
+.preloader-progress {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    right: 0;
+    width: $preloader-wd;
+    height: $preloader-hg;
+    margin: auto;
+    overflow: auto;
+    background-image: url(../../../assets/img/preloader/preloader.empty.png);
+    background-size: $preloader-wd $preloader-hg;
+}
+
+/*@noflip*/
+.preloader-progress-bar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    min-width: 10px;
+    background-image: url(../../../assets/img/preloader/preloader.full.png);
+    background-size: $preloader-wd $preloader-hg;
+}
+.preloader-hidden {
+    display: none;
+}
+// ngAnimate behavior
+.preloader-hidden-add {
+    opacity: 1;
+    display: block;
+
+    .preloader-progress {
+        transition: transform .4s ease;
+        transform: scale(0);
+    }
+}
+.preloader-hidden-add-active {
+    opacity: 0;
+}

+ 198 - 0
src/app/core/service/ajax.service.ts

@@ -0,0 +1,198 @@
+import {HttpHeaders, HttpParams} from "@angular/common/http";
+import {VarService} from "./var.service";
+import {MsgService} from "./msg.service";
+import {environment} from "../../../environments/environment";
+import {timeout} from "rxjs/operators";
+import {FrameService} from "./frame.service";
+
+/***************************
+ * 提供 Ajax 静态服务
+ */
+export class AjaxService {
+  /**************************
+   * 所有的请求通用头部
+   */
+  private static HEADERS = new HttpHeaders()
+    .set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8')
+    .set('Accept', 'application/json, text/javascript, */*; q=0.01')
+    .set('X-Requested-With', 'XMLHttpRequest');
+
+  /************************
+   * Ajax请求
+   */
+  public static request(url: string, data = {}, callback: Function = () => {
+  }, param = {}) {
+    // 统一记上sessionId
+    data['FrameSessionId'] = VarService.SESSION_ID;
+    for (const key in data) {
+      // 判断属性里是否有 undefined 的,有直接移除
+      if (data[key] === 'undefined' || data[key] === undefined || data[key] === null) {
+        delete data[key];
+      } 
+      /**
+      else if (Array.isArray(data[key])) {
+        // 处理数组,如果是数组,判断数组类型
+        if (data[key].length === 0)
+          delete data[key];
+        else {
+          const tmp = data[key][0];
+          if (typeof tmp !== 'string' && typeof tmp === 'number') {
+            // 复杂数组,统一用json格式提交
+            data[key] = JSON.stringify(data[key]);
+          }
+        }
+      }
+      */
+    }
+    // 是否显示操作等待界面
+    if (param['RequestWithLoading']) {
+      MsgService.showLoading();
+    }
+
+
+    VarService.HTTP_CLIENT.post(environment.webRoot + url, new HttpParams({
+      fromObject: data
+    }), {headers: AjaxService.HEADERS}).pipe(timeout(FrameService.ParamMap['AjaxTimeout'] | 60000))
+      .subscribe(
+        (response: any) => {
+          // 根据不同参数,返回不同的对象
+          if (response.success) {
+            let responseObj = response;
+            const requestType = param['RequestType'];
+            if (requestType === 'Entity') {
+              responseObj = response.entity;
+            } else if (requestType === 'Array') {
+              responseObj = response.array;
+            } else if (requestType === 'Page') {
+              responseObj = response.page;
+            } else if (requestType === 'Map') {
+              responseObj = response.map;
+            }
+            if (callback) {
+              callback.call(undefined, responseObj);
+            }
+          } else {
+            if (response['login'] === false) {
+              MsgService.errorAlert(response.errorMsg, () => {
+                window.location.href = './assets/login/login.html';
+              });
+              return;
+            }
+            const msgType = param['MsgType'] || 'Toaster';
+            const msgFlag = param['MsgFlag'] || true;
+            const errorCallback = param['ErrorCallback'];
+            // 业务错误
+            // 是否需要提示错误信息
+            if (errorCallback) {
+              errorCallback.call(undefined, response);
+            }
+            if (msgFlag) {
+              if (msgType === 'Toaster') {
+                MsgService.errorToastr(response.errorMsg);
+              } else if (msgType === 'Swal') {
+                MsgService.errorAlert(response.errorMsg);
+              } else if (msgType === 'Alert') {
+                alert(response.errorMsg);
+              }
+            }
+          }
+        }, () => {
+          if (param['RequestWithLoading']) {
+            MsgService.hideLoading();
+          }
+          const errorCallback = param['ErrorCallback'];
+          // 业务错误
+          // 是否需要提示错误信息
+          if (errorCallback) {
+            errorCallback.call(undefined);
+          }
+          // 未能正常请求,包括路径不存在 404错误,等其他非业务错误
+          MsgService.errorAlert('网络错误,请稍后重试!');
+        }, () => {
+          if (param['RequestWithLoading']) {
+            MsgService.hideLoading();
+          }
+        });
+  }
+
+  /**************************
+   * ajax 获取一个对象
+   */
+  public static requestEntity(url: string, data = {}, callback: Function = () => {
+  }, param = {}) {
+    const obj = Object.assign({}, param, {RequestType: 'Entity'});
+    return AjaxService.request(url, data, callback, obj);
+  }
+
+  /**************************
+   * ajax 获取一个数组
+   */
+  public static requestArray(url: string, data = {}, callback: Function = () => {
+  }, param = {}) {
+    const obj = Object.assign({}, param, {RequestType: 'Array'});
+    return AjaxService.request(url, data, callback, obj);
+  }
+
+  /**************************
+   * ajax 获取一个map对象
+   */
+  public static requestMap(url: string, data = {}, callback: Function = () => {
+  }, param = {}) {
+    const obj = Object.assign({}, param, {RequestType: 'Map'});
+    return AjaxService.request(url, data, callback, obj);
+  }
+
+  /**************************
+   * ajax 获取一个page对象
+   */
+  public static requestPage(url: string, data = {}, callback: Function = () => {
+  }, param = {}) {
+    const obj = Object.assign({}, param, {RequestType: 'Page'});
+    return AjaxService.request(url, data, callback, obj);
+  }
+
+
+  /***************************
+   * 通用ajax请求
+   * @param url
+   * @param data
+   * @param param
+   * @param options
+   */
+  public static ajaxRequest(url: string, data: {}, param?: { successMsg?: string, successMsgType?: string, confirmMsg?: string, callback?: Function, showMsg?: boolean }, options?: { RequestWithLoading?: boolean }) {
+    if (options === undefined)
+      options = {};
+    if (options.RequestWithLoading === undefined)
+      options.RequestWithLoading = true;
+    const confirmMsg = param ? param.confirmMsg : undefined;
+    const showMsg = param ? param.showMsg : true;
+
+    const fn = () => {
+      AjaxService.request(url, data, (response) => {
+        const successMsg = param ? (param.successMsg ? param.successMsg : '操作成功') : '操作成功';
+        if (successMsg && showMsg) {
+          const successMsgType = param ? (param.successMsgType ? param.successMsgType : 'Toaster') : 'Toaster';
+          if (successMsgType === 'Toaster') {
+            MsgService.successToastr(successMsg);
+          } else if (successMsgType === 'Swal') {
+            MsgService.successAlert(successMsg);
+          } else {
+            alert(successMsg);
+          }
+        }
+        const callback = param ? param.callback : undefined;
+        if (callback) {
+          callback.call(undefined, response);
+        }
+      }, options);
+    };
+
+    if (confirmMsg) {
+      MsgService.confirmAlert(confirmMsg, fn);
+    } else {
+      fn.call(undefined);
+    }
+
+  }
+
+}

+ 57 - 0
src/app/core/service/ajax.ts

@@ -0,0 +1,57 @@
+/*****************************
+ *  请求结果Ajax对象
+ */
+import {Pager} from "./pager";
+
+export class Ajax {
+  /********************
+   * 是否成功登陆
+   */
+  login: boolean = false;
+
+  /********************
+   * 请求是否成功
+   */
+  success: boolean;
+
+  /********************
+   * 单一对象
+   */
+  entity: any;
+
+  /*******************
+   * 数组对象
+   */
+  array: [any];
+
+  /***********************
+   * map对象
+   */
+  map: {};
+
+  /****************
+   * 分页对象
+   */
+  page: Pager;
+
+  /******************
+   * 错误原因
+   */
+  errorMsg: string;
+
+  /*****************
+   * 错误代码
+   */
+  errorCode: number;
+
+  /*********************
+   * 跳转的url,如果这里有值,优先处理
+   */
+  url: string;
+
+  /********************
+   * 线程id,是否是一个后台线程,如果是,该信息为线程的编号
+   */
+  threadId: number;
+
+}

+ 30 - 0
src/app/core/service/cookie.service.ts

@@ -0,0 +1,30 @@
+/*************************
+ * Cookies服务
+ */
+import {VarService} from "./var.service";
+
+export class CookieService {
+
+  /**********************
+   * 获取 cookie值,如果没有,返回default值
+   */
+  public static get(key: string, value?: any): any {
+    if (VarService.COOKIE_SERVICE.check(key))
+      return VarService.COOKIE_SERVICE.get(key);
+    return value;
+  }
+
+  /***************************
+   * 设置cookies值
+   */
+  public static set(key: string, value: any): void {
+    VarService.COOKIE_SERVICE.set(key, value);
+  }
+
+  /************************
+   * 删除一个cookie
+   */
+  public static del(key: string): void {
+    VarService.COOKIE_SERVICE.delete(key);
+  }
+}

+ 239 - 0
src/app/core/service/frame.service.ts

@@ -0,0 +1,239 @@
+import {Injector} from "@angular/core";
+import {MenuService} from "../menu/menu.service";
+import {VarService} from "./var.service";
+
+/**********************
+ * 登陆初始化从服务器获取的信息
+ */
+export class FrameService {
+  // 初始化时返回的的参数
+  public static LoginInitMap: any;
+  // 系统字典
+  public static DictMap: any = {};
+  // 系统参数
+  public static ParamMap: any = {};
+  // 操作员
+  public static FrameOptr: any;
+  // 部门信息
+  public static FrameDept: any;
+  // 操作员资源信息
+  public static ResMap: any = {};
+  // 操作员资源信息
+  public static ResArray: [any];
+
+  /*************************
+   *
+   * 系统登录后初始化信息
+   */
+  public static initializeAfterLoin(injector: Injector, map: any) {
+    FrameService.LoginInitMap = map;
+    // 系统字典
+    for (const v of map['FrameDictArray']) {
+      if (!FrameService.DictMap[v['dict_name']]) {
+        FrameService.DictMap[v['dict_name']] = new Array();
+      }
+      FrameService.DictMap[v['dict_name']].push(v);
+    }
+
+    // 系统参数
+    for (const v of map['FrameParamArray']) {
+      FrameService.ParamMap[v['param_name']] = v;
+    }
+    // 当前登录的用户
+    FrameService.FrameOptr = map['FrameOptr'];
+    FrameService.FrameDept = map['FrameDept'];
+    if (FrameService.FrameDept === undefined) {
+      FrameService.FrameDept = {dept_id: 0, dept_level: 0, dept_name: '顶级部门'};
+    }
+    FrameService.FrameOptr.dept_name = FrameService.FrameDept.dept_name;
+    if (FrameService.FrameOptr['optr_image'] === undefined) {
+      // 如果没有图片,设置默认图片
+      FrameService.FrameOptr['optr_image'] = `assets/img/user/01.jpg`;
+    }
+    //用户登录首页
+    FrameService.FrameOptr.home_page = map['HomePage'];
+
+    // 操作员可用资源
+    const resArray = new Array();
+    // 所有权限
+    for (const v of map['FrameResArray']) {
+      FrameService.ResMap['R_' + v['res_id']] = v;
+      if (v['res_level'] === 1) {
+        FrameService.findChildRes(map['FrameResArray'], v);
+        resArray.push(v);
+      }
+    }
+    FrameService.ResArray = map['FrameResArray'];
+
+    // 所有菜单
+    const aMenu = new Array();
+    for (const v of resArray) {
+      if (v['childArray'] === undefined) {
+        continue;
+      }
+      // 添加顶层菜单
+      aMenu.push({
+        text: v['res_name'],
+        heading: true,
+        icon: v['res_css']
+      });
+      // 二级菜单
+      for (const c of v['childArray']) {
+        if (c['childArray'] && c['childArray'].length > 0) {
+          const dArray = new Array();
+          for (const d of c['childArray']) {
+            // 三级菜单
+            let submenu = [];
+            if (d['childArray'] && d['childArray'].length > 0) {
+              for (const f of d['childArray']) {
+                submenu.push({
+                  text: f['res_name'],
+                  link: f['res_url'],
+                  icon: f['res_css'],
+                });
+              }
+            }
+            dArray.push({
+              text: d['res_name'],
+              link: d['res_url'],
+              icon: d['res_css'],
+              submenu: submenu.length === 0 ? undefined : submenu
+            });
+          }
+          aMenu.push({
+            text: c['res_name'],
+            link: c['res_url'],
+            icon: c['res_css'],
+            submenu: dArray
+          });
+        } else {
+          aMenu.push({
+            text: c['res_name'],
+            link: c['res_url'],
+            icon: c['res_css']
+          });
+        }
+      }
+
+    }
+
+    // 设置标题
+    if (FrameService.ParamMap['FrameName']) {
+      document.title = FrameService.ParamMap['FrameName']['param_value'];
+    }
+
+    // 设置sessionId
+    VarService.SESSION_ID = map['FrameSessionId'];
+
+    // 添加菜单
+    const mService = injector.get<MenuService>(MenuService);
+    mService.addMenu(aMenu);
+  }
+
+  /********************
+   * 查找某个资源下的所有资源
+   */
+  public static findChildRes(resArray, res) {
+    const array = new Array()
+    for (const v of resArray) {
+      if (v['res_id'] !== res['res_id'] && v['res_pid'] === res['res_id'] && v['res_type'] !== 'Fun') {
+        FrameService.findChildRes(resArray, v);
+        array.push(v);
+        res.childArray = array;
+      }
+    }
+  }
+
+  /**********************
+   * 将某个res下的所有资源转为数组
+   */
+  public static makeResArray(res, array) {
+    if (res.childArray && res.childArray.length > 0) {
+      for (const c of res.childArray) {
+        array.push(c);
+        FrameService.makeResArray(c, array);
+      }
+    }
+  }
+
+  /**************************
+   * 获取某一个字典所有的字典项
+   * @param {string} dict_name  字典名称
+   * @returns {[any]} 项目数组
+   */
+  public static getFrameDictArray(dict_name: string): [any] {
+    return FrameService.DictMap[dict_name];
+  }
+
+  /***********************
+   * 获取字典项
+   * @param {string} dict_name 字典名称
+   * @param {string} dict_value 字典唯一值
+   * @returns {any} 对应的字典项
+   */
+  public static getFrameDict(dict_name: string, dict_value: string) {
+    if (dict_name !== undefined && FrameService.DictMap[dict_name] !== undefined) {
+      for (const v of FrameService.DictMap[dict_name]) {
+        if (v.dict_value === (dict_value + '')) {
+          return v;
+        }
+      }
+    }
+    return undefined;
+  }
+
+  /***********************
+   * 获取参数值
+   * @param {string} dict_name 字典名称
+   * @param {string} dict_value 字典唯一值
+   * @returns {any} 对应的字典项
+   */
+  public static getParamValue(param_name: string, default_value?: any) {
+    if (param_name !== undefined && FrameService.ParamMap[param_name] !== undefined) {
+      return FrameService.ParamMap[param_name].param_value;
+    }
+    if (default_value) {
+      return default_value + '';
+    }
+    return default_value;
+  }
+
+  /******************
+   * 获取字典的中文描述
+   * @param {string} dict_name  字典名称
+   * @param {string} dict_value 字典唯一值
+   * @returns {any} 对应的描述信息
+   */
+  public static getFrameDictText(dict_name: string, dict_value: string) {
+    const dict = FrameService.getFrameDict(dict_name, dict_value);
+    if (dict !== undefined) {
+      return dict.dict_text;
+    }
+    return dict_value;
+  }
+
+  /************************
+   * 验证是否有权限
+   * @param res 资源编号或者别名
+   * @returns {boolean} 是否有对应的权限
+   */
+  public static validateRole(res: any): boolean {
+    if (FrameService.FrameOptr === undefined) {
+      return false;
+    }
+    // 超级管理员
+    if (res === 0 || res === '0' || FrameService.FrameOptr.optr_id === 0) {
+      return true;
+    }
+
+    // 一般权限判断
+    for (const r of FrameService.ResArray) {
+      if (res === '' + r.res_id || res === r.res_alias) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+}

+ 192 - 0
src/app/core/service/func.service.ts

@@ -0,0 +1,192 @@
+import {formatCurrency} from '@angular/common';
+import {environment} from "../../../environments/environment";
+/***************
+ * 公用函数处理
+ */
+export class FuncService {
+  /********************
+   * 格式化中文货币
+   */
+  public static formatCurrency(v) {
+    return formatCurrency(v, 'zh', '¥');
+  }
+
+  /********************
+   * 将转字符串转化为日期 yyyy-MM-dd  -> Date
+   */
+  public static shortStringToDate(date?) {
+    if (date === undefined)
+      date = new Date();
+    if (date instanceof Date) {
+      return date;
+    }
+    return new Date(Date.parse(FuncService.dateToShortString(date).replace(/-/g, '/')));
+  }
+
+
+  public static longStringToDate(date) {
+    if (date instanceof Date) {
+      return date;
+    }
+    if (date === undefined || date.length !== 19) {
+      return undefined;
+    }
+    return new Date(Date.parse(FuncService.dateToLongString(date).replace(/-/g, '/')));
+  }
+
+  /***********************
+   * 将日期变量进行格式化
+   * @param date 日期变量
+   * @param fmt 字符串格式
+   */
+  public static dateToString(date, fmt) {
+    const o = {
+      'M+': date.getMonth() + 1,
+      'd+': date.getDate(),
+      'h+': date.getHours(),
+      'm+': date.getMinutes(),
+      's+': date.getSeconds(),
+      'q+': Math.floor((date.getMonth() + 3) / 3),
+      'S': date.getMilliseconds()
+    };
+    if (/(y+)/.test(fmt)) {
+      fmt = fmt.replace(RegExp.$1, (date.getFullYear() + ``).substr(4 - RegExp.$1.length));
+    }
+    for (let k in o) {
+      if (new RegExp(`(` + k + `)`).test(fmt)) {
+        fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : ((`00` + o[k]).substr((`` + o[k]).length)));
+      }
+    }
+    return fmt;
+  }
+
+  /*********************
+   * 日期转换成长格式字符串 Date -> yyyy-MM-dd HH:mm:ss
+   */
+  public static dateToLongString(date?) {
+    if (date === undefined)
+      date = new Date();
+    if (date instanceof Date) {
+      return FuncService.dateToString(date, 'yyyy-MM-dd hh:mm:ss');
+    }
+    return '';
+  }
+
+  /*********************
+   * 日期转换成段字符串 Date -> yyyy-MM-dd
+   */
+  public static dateToShortString(date?) {
+    if (date === undefined) {
+      date = new Date();
+    }
+    if (date instanceof Date) {
+      date = FuncService.dateToLongString(date);
+    }
+    if (date.length > 10)
+      return date.substr(0, 10);
+    return date;
+  }
+
+  /***************************
+   * 日期转换成 具体时间格式 Date -> HH:mm:ss
+   */
+  public static dateToTimeString(date?) {
+    if (date === undefined) {
+      date = new Date();
+    }
+    if (date instanceof Date) {
+      date = FuncService.dateToLongString(date);
+    }
+    if (date.length > 10) {
+      return date.substr(11);
+    }
+    return date;
+  }
+
+
+  /***************************
+   * 日期转换成 小时分格式 Date -> HH:mm
+   */
+  public static dateToMinuteString(date?) {
+    if (date === undefined) {
+      date = new Date();
+    }
+    if (date instanceof Date) {
+      date = FuncService.dateToLongString(date);
+    }
+    if (date.length > 10 && date.length >= 15) {
+      return date.substr(11, 5);
+    }
+    return date;
+  }
+
+  /*************************
+   * 两个日期 Date,Date -> HH:mm - HH:mm
+   */
+  public static dateToBetweenMinutes(start, end) {
+    return FuncService.dateToMinuteString(start) + ' - ' + FuncService.dateToMinuteString(end);
+  }
+
+  /*********************
+   * 数组排序
+   * @param array 需要排序的数组
+   * @param attr_name 用来排序的字段
+   */
+  public static sortArray(array, attr_name, order_attr?: any) {
+    for (let i = 0; i < array.length - 1; i++) {
+      for (let j = 0; j < array.length - 1 - i; j++) {
+        if (array[j][attr_name] > array[j + 1][attr_name]) {
+          const temp = array[j];
+          array[j] = array[j + 1];
+          array[j + 1] = temp;
+        }
+      }
+      if (order_attr !== undefined) {
+        array[order_attr] = i + 1;
+      }
+    }
+  }
+
+  /***********************
+   * 文件下载 iframe方式,不需要考虑弹窗,但长时间等待可能会导致用户不知道下载线程正在处理
+   */
+  public static fileDownload(url: string, param?: any) {
+    let html = '';
+    for (const name in param) {
+      if (typeof (param[name]) !== 'function' && name !== '$$hashKey') {
+        const value = param[name];
+        if (value !== null && value !== undefined) {
+          html = html + (html === '' ? '?' : '&') + `${name}=${value}`;
+        }
+      }
+    }
+    const elemIF = document.createElement('iframe');
+    elemIF.src = environment.uploadRoot + url + html;
+    elemIF.style.display = 'none';
+    document.body.appendChild(elemIF);
+    setTimeout(() => {
+      document.body.removeChild(elemIF);
+    }, 1000);
+  }
+
+  /*********************
+   * 弹窗方式,用户可知道线程正在运行,但会受到浏览器拦截,需要手动设置
+   */
+  public static ajaxDownload(url: string, param?: any) {
+    let html = '<form action="' + environment.uploadRoot + url + '" method="post" target="_blank">';
+    for (const name in param) {
+      if (typeof (param[name]) !== 'function' && name !== '$$hashKey') {
+        const value = param[name];
+        if (value !== null && value !== undefined) {
+          html = html + '<input type="hidden" name="' + name + '" value="' + value + '"/>';
+        }
+
+      }
+    }
+    html = html + '</form>';
+    $(html).appendTo('body').submit().remove();
+  }
+
+
+
+}

+ 69 - 0
src/app/core/service/modal.service.ts

@@ -0,0 +1,69 @@
+import {BsModalRef, BsModalService, ModalOptions} from "ngx-bootstrap/modal";
+import {AjaxService} from "./ajax.service";
+import {FrameUpload} from "../frame/upload/frame-upload/frame-upload";
+import {FrameThreadRunningComponent} from "../../routes/frame/thread/frame-thread-running/frame-thread-running.component";
+
+export class ModalService {
+
+  /*************************
+   * 显示线程窗口
+   * 返回弹出窗口句柄
+   */
+  public static showUploadWindow(modalService: BsModalService, url: string, uploadParam?: any, modalParam?: any, options?: ModalOptions): BsModalRef {
+    if (!modalParam)
+      modalParam = {};
+    let bsModalRef;
+    const modal = Object.assign({}, {
+      animated: true,
+      keyboard: false,
+      backdrop: true,
+      ignoreBackdropClick: true,
+      initialState: {},
+      class: 'modal-5p'
+    }, options);
+    modalParam['uploadParams'] = uploadParam || {};
+    modal.initialState = modalParam;
+    modal.initialState['uploadUrl'] = url;
+    modal.initialState['modalService'] = modalService;
+    // 如果需要去服务器请求数据
+    if (modalParam['entityUrl']) {
+      AjaxService.requestEntity(modalParam['entityUrl'], modalParam['entityParam'], (entity) => {
+        modal.initialState['ResponseEntity'] = entity;
+        bsModalRef = modalService.show(FrameUpload, modal);
+      });
+    } else if (modalParam['mapUrl']) {
+      AjaxService.requestMap(modalParam['mapUrl'], modalParam['mapParam'], (map) => {
+        modal.initialState['ResponseMap'] = map;
+        bsModalRef = modalService.show(FrameUpload, modal);
+      }, {RequestWithLoading: true});
+    } else if (modalParam['arrayUrl']) {
+      AjaxService.requestArray(modalParam['arrayUrl'], modalParam['arrayParam'], (array) => {
+        modal.initialState['ResponseArray'] = array;
+        bsModalRef = modalService.show(FrameUpload, modal);
+      }, {RequestWithLoading: true});
+    } else {
+      bsModalRef = modalService.show(FrameUpload, modal);
+    }
+    return bsModalRef;
+  }
+
+
+  /*************************
+   * 显示线程窗口
+   * 返回弹出窗口句柄
+   */
+  public static showThreadWindow(modalService: BsModalService, frameThread: any, threadParam?: any, options?: ModalOptions): BsModalRef {
+    const modal = Object.assign({}, {
+      animated: true,
+      keyboard: false,
+      backdrop: true,
+      ignoreBackdropClick: true,
+      initialState:  {},
+      class: 'modal-5p'
+    }, options);
+    modal.initialState['ResponseEntity'] = frameThread;
+    modal.initialState['threadParam'] = threadParam;
+    return modalService.show(FrameThreadRunningComponent, modal);
+  }
+
+}

+ 141 - 0
src/app/core/service/msg.service.ts

@@ -0,0 +1,141 @@
+import {TranslateService} from '@ngx-translate/core';
+import {VarService} from "./var.service";
+
+
+// 加载弹框服务
+const swal = require('sweetalert');
+
+/*********************
+ * 提供全局消息服务
+ */
+export class MsgService {
+
+  /***********************
+   * 右下角操作 成功提示
+   */
+  public static successToastr(msg: string) {
+    VarService.TOASTR.success(msg);
+  }
+
+  /***********************
+   * 右下角操作 警告提示
+   */
+  public static warningToastr(msg: string) {
+    VarService.TOASTR.warning(msg);
+  }
+
+  /***********************
+   * 右下角操作 错误提示
+   */
+  public static errorToastr(msg: string) {
+    VarService.TOASTR.error(msg);
+  }
+
+  /********************
+   * 显示Loading
+   */
+  public static showLoading() {
+    VarService.SHOW_LOADING = true;
+  }
+
+  /********************
+   * 隐藏Loading
+   */
+  public static hideLoading() {
+    VarService.SHOW_LOADING = false;
+  }
+
+  /**************************
+   * 单一输入框,用于简单的参数输入
+   */
+  public static input(msg: string, callback: Function) {
+    swal(msg, {
+      content: "input",
+    }).then((value) => {
+      callback.call(undefined, value);
+    });
+  }
+
+  /***********************
+   * 弹框确认操作
+   */
+  public static confirmAlert(msg: string, callback: Function) {
+    swal(msg, {
+      buttons: {
+        cancel: MsgService.translateText(`msg.button.cancel`),
+        catch: {
+          text: MsgService.translateText(`msg.button.submit`),
+          value: 'catch',
+        }
+      },
+    })
+      .then((value) => {
+        switch (value) {
+          case 'catch':
+            swal.close();
+            callback();
+            break;
+        }
+      });
+  }
+
+  /******************************
+   * 弹框 错误提示
+   */
+  public static errorAlert(msg: string, callback?: Function) {
+    MsgService.curstomAlert('error', '', msg, callback);
+  }
+
+  /******************************
+   * 弹框 正确提示
+   */
+  public static successAlert(msg: string, callback?: Function) {
+    MsgService.curstomAlert('success', '', msg, callback);
+  }
+
+  /******************************
+   * 弹框 提醒提示
+   */
+  public static warningAlert(msg: string, callback?: Function) {
+    MsgService.curstomAlert('warning', '', msg, callback);
+  }
+
+  /******************************
+   * 弹框 正常提示
+   */
+  public static infoAlert(msg: string, callback?: Function) {
+    MsgService.curstomAlert('info', '', msg, callback);
+  }
+
+  /******************************
+   * 自定义弹框提示
+   */
+  public static curstomAlert(type: string, msg: string, title: string, callback?: Function) {
+    swal({
+      title: title,
+      text: msg,
+      icon: type,
+      button: {
+        text: MsgService.translateText(`msg.button.know`),
+        value: true,
+        visible: true,
+        closeModal: true,
+      }
+    }).then((value) => {
+      if (callback) {
+        callback();
+      }
+    });
+  }
+
+  /*************************
+   * 国际化
+   */
+  public static translateText(text: string) {
+    return VarService.INJECTOR.get(TranslateService).instant(text);
+  }
+
+
+
+
+}

+ 41 - 0
src/app/core/service/pager.ts

@@ -0,0 +1,41 @@
+
+/********************
+ * 分页对象
+ */
+
+export class Pager {
+  /*****************
+   * 开始位置
+   */
+  start: number;
+  /*****************
+   * 每页显示的条数
+   */
+  limit: number;
+
+  /********************
+   * 记录
+   */
+  records: [];
+
+  /**********************
+   * 总记录数
+   */
+  total: number;
+
+  /*********************
+   * 总页数
+   */
+  count: number;
+
+  /********************
+   *  当前页码
+   */
+  index: number;
+
+  /********************
+   * 其他参数信息
+   */
+  param: {}
+
+}

+ 49 - 0
src/app/core/service/var.service.ts

@@ -0,0 +1,49 @@
+import {Injector} from "@angular/core";
+import {ToastrService} from "ngx-toastr";
+import {DomSanitizer} from "@angular/platform-browser";
+import {HttpClient} from "@angular/common/http";
+import {CookieService} from "ngx-cookie-service";
+
+/***************
+ * 定义公用变量
+ */
+export class VarService {
+  /***********************
+   * 全局显示加载提示
+   */
+  public static SHOW_LOADING = false;
+
+  /************************
+   * 拦截器,用于获取一些内部类
+   */
+  public static INJECTOR: Injector;
+
+  /***************
+   * 全局右下角提示
+   */
+  public static TOASTR: ToastrService = null;
+  /**********************
+   * 页面html安全工具
+   */
+  public static DOM_SANITIZER: DomSanitizer;
+
+  /*************************
+   * httpclient客户端
+   */
+  public static HTTP_CLIENT: HttpClient;
+
+  /**************************
+   * 全局SessionId:
+   *
+   */
+  /**************************
+   * 全局SessionId:
+   *
+   */
+  public static SESSION_ID: string;
+  /**************************
+   * Cookies 服务
+   */
+  public static COOKIE_SERVICE: CookieService;
+
+}

+ 74 - 0
src/app/core/settings/settings.service.ts

@@ -0,0 +1,74 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class SettingsService {
+
+    private user: any;
+    private app: any;
+    private layout: any;
+
+    constructor() {
+        this.user = {
+            name: 'Guest',
+            job: '',
+            picture: 'assets/img/user/02.jpg'
+        };
+        this.app = {
+            name: 'HmSoft',
+            description: '',
+            year: ((new Date()).getFullYear())
+        };
+
+        // Layout Settings
+        // -----------------------------------
+        this.layout = {
+            isFixed: true,
+            isCollapsed: false,
+            isBoxed: false,
+            isRTL: false,
+            horizontal: false,
+            isFloat: false,
+            asideHover: false,
+            theme: null,
+            asideScrollbar: false,
+            isCollapsedText: false,
+            useFullLayout: false,
+            hiddenFooter: false,
+            offsidebarOpen: false,
+            asideToggled: false,
+            viewAnimation: 'ng-fadeInUp'
+        };
+
+    }
+
+    getAppSetting(name) {
+        return name ? this.app[name] : this.app;
+    }
+    getUserSetting(name) {
+        return name ? this.user[name] : this.user;
+    }
+    getLayoutSetting(name) {
+        return name ? this.layout[name] : this.layout;
+    }
+
+    setAppSetting(name, value) {
+        if (typeof this.app[name] !== 'undefined') {
+            this.app[name] = value;
+        }
+    }
+    setUserSetting(name, value) {
+        if (typeof this.user[name] !== 'undefined') {
+            this.user[name] = value;
+        }
+    }
+    setLayoutSetting(name, value) {
+        if (typeof this.layout[name] !== 'undefined') {
+            return this.layout[name] = value;
+        }
+    }
+
+    toggleLayoutSetting(name) {
+        return this.setLayoutSetting(name, !this.getLayoutSetting(name));
+    }
+
+}

+ 72 - 0
src/app/core/themes/themes.service.ts

@@ -0,0 +1,72 @@
+import { Injectable } from '@angular/core';
+
+const themeA = require('../../shared/styles/themes/theme-a.scss');
+const themeB = require('../../shared/styles/themes/theme-b.scss');
+const themeC = require('../../shared/styles/themes/theme-c.scss');
+const themeD = require('../../shared/styles/themes/theme-d.scss');
+const themeE = require('../../shared/styles/themes/theme-e.scss');
+const themeF = require('../../shared/styles/themes/theme-f.scss');
+const themeG = require('../../shared/styles/themes/theme-g.scss');
+const themeH = require('../../shared/styles/themes/theme-h.scss');
+
+/*******************
+ * 色系方案
+ */
+@Injectable()
+export class ThemesService {
+
+    styleTag: any;
+    defaultTheme: string = 'A';
+
+    constructor() {
+        this.createStyle();
+        this.setTheme(this.defaultTheme);
+    }
+
+    private createStyle() {
+        const head = document.head || document.getElementsByTagName('head')[0];
+        this.styleTag = document.createElement('style');
+        this.styleTag.type = 'text/css';
+        this.styleTag.id = 'appthemes';
+        head.appendChild(this.styleTag);
+    }
+
+    setTheme(name) {
+        switch (name) {
+            case 'A':
+                this.injectStylesheet(themeA);
+                break;
+            case 'B':
+                this.injectStylesheet(themeB);
+                break;
+            case 'C':
+                this.injectStylesheet(themeC);
+                break;
+            case 'D':
+                this.injectStylesheet(themeD);
+                break;
+            case 'E':
+                this.injectStylesheet(themeE);
+                break;
+            case 'F':
+                this.injectStylesheet(themeF);
+                break;
+            case 'G':
+                this.injectStylesheet(themeG);
+                break;
+            case 'H':
+                this.injectStylesheet(themeH);
+                break;
+        }
+    }
+
+    // since v9, content is available in 'default'
+    injectStylesheet(css) {
+        this.styleTag.innerHTML = css.default;
+    }
+
+    getDefaultTheme() {
+        return this.defaultTheme;
+    }
+
+}

+ 32 - 0
src/app/core/translator/translator.service.ts

@@ -0,0 +1,32 @@
+import {Injectable} from '@angular/core';
+import {TranslatePipe, TranslateService} from '@ngx-translate/core';
+
+/*********************
+ * 国际化
+ */
+@Injectable()
+export class TranslatorService {
+
+  private defaultLanguage: string = 'zh';
+
+  private availablelangs = [
+    {code: 'zh', text: '简体中文'},
+    {code: 'en', text: 'English'}
+  ];
+
+  constructor(public translate: TranslateService) {
+
+    if (!translate.getDefaultLang())
+      translate.setDefaultLang(this.defaultLanguage);
+    this.useLanguage();
+  }
+
+  useLanguage(lang: string = null) {
+    this.translate.use(lang || this.translate.getDefaultLang());
+  }
+
+  getAvailableLanguages() {
+    return this.availablelangs;
+  }
+
+}

+ 2 - 0
src/app/index.ts

@@ -0,0 +1,2 @@
+export * from './app.component';
+export * from './app.module';

+ 1 - 0
src/app/layout/footer/footer.component.html

@@ -0,0 +1 @@
+<span>&copy; {{settings.getAppSetting('year')}} - {{ settings.getAppSetting('name') }}</span>

+ 0 - 0
src/app/layout/footer/footer.component.scss


+ 17 - 0
src/app/layout/footer/footer.component.ts

@@ -0,0 +1,17 @@
+import { Component, OnInit } from '@angular/core';
+import { SettingsService } from '../../core/settings/settings.service';
+
+@Component({
+    selector: '[app-footer]',
+    templateUrl: './footer.component.html',
+    styleUrls: ['./footer.component.scss']
+})
+export class FooterComponent implements OnInit {
+
+    constructor(public settings: SettingsService) { }
+
+    ngOnInit() {
+
+    }
+
+}

+ 140 - 0
src/app/layout/header/header.component.h.html

@@ -0,0 +1,140 @@
+<!-- START Top Navbar-->
+<nav class="navbar topnavbar navbar-expand-lg navbar-light" role="navigation">
+    <!-- START navbar header-->
+    <div class="navbar-header">
+        <a class="navbar-brand" href="#/">
+            <div class="brand-logo">
+                <img class="img-fluid" src="assets/img/logo.png" alt="App Logo" />
+            </div>
+            <div class="brand-logo-collapsed">
+                <img class="img-fluid" src="assets/img/logo-single.png" alt="App Logo" />
+            </div>
+        </a>
+       <button class="navbar-toggler" type="button" (click)="navCollapsed = !navCollapsed; $event.stopPropagation()">
+          <span class="navbar-toggler-icon"></span>
+       </button>
+    </div>
+    <!-- END navbar header-->
+    <!-- START Nav wrapper-->
+    <div class="navbar-collapse collapse" [collapse]="navCollapsed">
+        <!-- Navbar Menu -->
+        <ul class="nav navbar-nav mr-auto flex-column flex-lg-row">
+            <ng-template ngFor let-item [ngForOf]="menuItems">
+                <!-- Single items -->
+                <li [routerLinkActive]="['active']" class="nav-item" *ngIf="!item.heading && !item.submenu">
+                    <a class="nav-link" [routerLink]="item.link" [title]="item.text">
+                        <span>{{(item.translate | translate) || item.text}}</span>
+                    </a>
+                </li>
+                <!-- Dropdown items -->
+                <li [routerLinkActive]="['active']" class="nav-item"
+                    [ngClass]="{'dropdown': item.submenu}" dropdown *ngIf="!item.heading && item.submenu">
+                    <a class="nav-link dropdown-toggle dropdown-toggle-nocaret" dropdownToggle>
+                        <span>{{(item.translate | translate) || item.text}}</span>
+                    </a>
+                    <!-- START Dropdown menu-->
+                    <div *dropdownMenu class="dropdown-menu">
+                        <a class="dropdown-item" [routerLinkActive]="['active']" *ngFor="let subitem of item.submenu" [routerLink]="subitem.link" [title]="subitem.text" >
+                            <span>{{(subitem.translate | translate) || subitem.text}}</span>
+                        </a>
+                    </div>
+                </li>
+            </ng-template>
+        </ul>
+        <!-- End Navbar Menu-->
+        <!-- START Right Navbar-->
+        <ul class="navbar-nav flex-row">
+            <!-- START lock screen-->
+            <li class="nav-item">
+                <a class="nav-link" title="Lock screen" [routerLink]="'/lock'">
+                    <em class="icon-lock"></em>
+                </a>
+            </li>
+            <!-- END lock screen-->
+            <!-- Search icon-->
+            <li class="nav-item">
+                <a class="nav-link" (click)="openNavSearch($event)">
+                    <em class="icon-magnifier"></em>
+                </a>
+            </li>
+            <!-- Fullscreen (only desktops)-->
+            <li class="nav-item d-none d-md-block">
+                <a class="nav-link" #fsbutton (click)="toggleFullScreen($event)">
+                    <em class="fa fa-expand"></em>
+                </a>
+            </li>
+            <!-- START Alert menu-->
+            <li class="nav-item dropdown dropdown-list" dropdown>
+                <a class="nav-link dropdown-toggle dropdown-toggle-nocaret" dropdownToggle>
+                    <em class="icon-bell"></em>
+                    <span class="badge badge-danger">11</span>
+                </a>
+                <!-- START Dropdown menu-->
+                <div *dropdownMenu class="dropdown-menu dropdown-menu-right animated flipInX">
+                    <div class="dropdown-item">
+                        <!-- START list group-->
+                        <div class="list-group">
+                           <!-- list item-->
+                           <div class="list-group-item list-group-item-action">
+                              <div class="media">
+                                 <div class="align-self-start mr-2">
+                                    <em class="fab fa-twitter fa-2x text-info"></em>
+                                 </div>
+                                 <div class="media-body">
+                                    <p class="m-0">New followers</p>
+                                    <p class="m-0 text-muted text-sm">1 new follower</p>
+                                 </div>
+                              </div>
+                           </div>
+                           <!-- list item-->
+                           <div class="list-group-item list-group-item-action">
+                              <div class="media">
+                                 <div class="align-self-start mr-2">
+                                    <em class="fa fa-envelope fa-2x text-warning"></em>
+                                 </div>
+                                 <div class="media-body">
+                                    <p class="m-0">New e-mails</p>
+                                    <p class="m-0 text-muted text-sm">You have 10 new emails</p>
+                                 </div>
+                              </div>
+                           </div>
+                           <!-- list item-->
+                           <div class="list-group-item list-group-item-action">
+                              <div class="media">
+                                 <div class="align-self-start mr-2">
+                                    <em class="fa fa-tasks fa-2x text-success"></em>
+                                 </div>
+                                 <div class="media-body">
+                                    <p class="m-0">Pending Tasks</p>
+                                    <p class="m-0 text-muted text-sm">11 pending task</p>
+                                 </div>
+                              </div>
+                           </div>
+                           <!-- last list item-->
+                           <div class="list-group-item list-group-item-action">
+                              <span class="d-flex align-items-center">
+                                 <span class="text-sm">More notifications</span>
+                                 <span class="badge badge-danger ml-auto">14</span>
+                              </span>
+                           </div>
+                        </div>
+                        <!-- END list group-->
+                    </div>
+                </div>
+                <!-- END Dropdown menu-->
+            </li>
+            <!-- END Alert menu-->
+            <!-- START Offsidebar button-->
+            <li class="nav-item">
+                <a class="nav-link" (click)="toggleOffsidebar(); $event.stopPropagation()">
+                    <em class="icon-notebook"></em>
+                </a>
+            </li>
+            <!-- END Offsidebar menu-->
+        </ul>
+        <!-- END Right Navbar-->
+    </div>
+    <!-- END Nav wrapper-->
+    <app-navsearch [visible]="getNavSearchVisible()" (onclose)="setNavSearchVisible(false)"></app-navsearch>
+</nav>
+<!-- END Top Navbar-->

+ 117 - 0
src/app/layout/header/header.component.html

@@ -0,0 +1,117 @@
+<nav class="navbar topnavbar" role="navigation">
+  <div class="navbar-header">
+    <a class="navbar-brand" href="#/">
+      <div class="brand-logo">
+        <img class="img-fluid" src="assets/img/logo.png" alt="App Logo" style="height: 34px;"/>
+      </div>
+      <div class="brand-logo-collapsed">
+        <img class="img-fluid" src="assets/img/logo-single.png" alt="App Logo"/>
+      </div>
+    </a>
+  </div>
+  <ul class="navbar-nav mr-auto flex-row">
+    <li class="nav-item">
+      <!-- Button used to collapse the left sidebar. Only visible on tablet and desktops-->
+      <a class="nav-link d-none d-md-block d-lg-block d-xl-block" trigger-resize="" (click)="toggleCollapsedSideabar()" *ngIf="!isCollapsedText()">
+        <em class="fas fa-bars"></em>
+      </a>
+      <!-- Button to show/hide the sidebar on mobile. Visible on mobile only.-->
+      <a class="nav-link sidebar-toggle d-md-none" (click)="settings.toggleLayoutSetting('asideToggled'); $event.stopPropagation()">
+        <em class="fas fa-bars"></em>
+      </a>
+    </li>
+    <!-- START User avatar toggle-->
+    <li class="nav-item d-none d-md-block">
+      <!-- Button used to collapse the left sidebar. Only visible on tablet and desktops-->
+      <a class="nav-link  cursor-hand" (click)="modifyOptr($event)">
+        <em class="icon-user"></em>
+      </a>
+    </li>
+    <li class="nav-item d-none d-md-block">
+      <!-- Button used to collapse the left sidebar. Only visible on tablet and desktops-->
+      <a class="nav-link cursor-hand" (click)="modifyPassword($event)">
+        <em class="icon-key"></em>
+      </a>
+    </li>
+    <!-- END User avatar toggle-->
+    <!-- START lock screen-->
+    <!--
+    <li class="nav-item d-none d-md-block">
+        <a class="nav-link"  title="Lock screen" [routerLink]="'/lock'">
+            <em class="icon-lock"></em>
+        </a>
+    </li>
+    -->
+    <!-- END lock screen-->
+  </ul>
+  <!-- END Left navbar-->
+  <!-- START Right Navbar-->
+  <ul class="navbar-nav flex-row">
+    <!-- Search icon-->
+    <li class="nav-item">
+      <a class="nav-link" (click)="openNavSearch($event)">
+        <em class="icon-magnifier"></em>
+      </a>
+    </li>
+    <!-- Fullscreen (only desktops)-->
+    <li class="nav-item d-none d-md-block">
+      <a class="nav-link" #fsbutton (click)="toggleFullScreen($event)">
+        <em class="fa fa-expand"></em>
+      </a>
+    </li>
+    <!--
+     <li class="nav-item dropdown dropdown-list" dropdown>
+         <a class="nav-link dropdown-toggle dropdown-toggle-nocaret" dropdownToggle>
+             <em class="icon-bell"></em>
+             <span class="badge badge-danger">11</span>
+         </a>
+         <div *dropdownMenu class="dropdown-menu dropdown-menu-right animated flipInX">
+             <div class="dropdown-item">
+                 <div class="list-group">
+                    <div class="list-group-item list-group-item-action">
+                       <div class="media">
+                          <div class="align-self-start mr-2">
+                             <em class="fa fa-tasks fa-2x text-success"></em>
+                          </div>
+                          <div class="media-body">
+                             <p class="m-0">Pending Tasks</p>
+                             <p class="m-0 text-muted text-sm">11 pending task</p>
+                          </div>
+                       </div>
+                    </div>
+                    <div class="list-group-item list-group-item-action">
+                       <span class="d-flex align-items-center">
+                          <span class="text-sm">More notifications</span>
+                          <span class="badge badge-danger ml-auto">14</span>
+                       </span>
+                    </div>
+                 </div>
+             </div>
+         </div>
+     </li>
+     -->
+    <!--
+    <li class="nav-item">
+        <a class="nav-link" (click)="toggleOffsidebar(); $event.stopPropagation()">
+            <em class="icon-notebook"></em>
+        </a>
+    </li>
+    -->
+    <!--
+    <li class="nav-item">
+      <a class="nav-link cursor-hand" title="Logout" (click)="toggleOffsidebar(); $event.stopPropagation()">
+        <em class="icon-login"></em>
+      </a>
+    </li>
+    -->
+
+    <li class="nav-item">
+      <a class="nav-link cursor-hand" title="Logout" (click)="logout(); $event.stopPropagation()">
+        <em class="icon-login"></em>
+      </a>
+    </li>
+  </ul>
+
+  <app-navsearch [visible]="getNavSearchVisible()" (onclose)="setNavSearchVisible(false)"></app-navsearch>
+
+</nav>

+ 0 - 0
src/app/layout/header/header.component.scss


+ 148 - 0
src/app/layout/header/header.component.ts

@@ -0,0 +1,148 @@
+import {Component, OnInit, ViewChild, Injector} from '@angular/core';
+import {Router} from '@angular/router';
+
+const screenfull = require('screenfull');
+
+import {UserblockService} from '../sidebar/userblock/userblock.service';
+import {SettingsService} from '../../core/settings/settings.service';
+import {MenuService} from '../../core/menu/menu.service';
+import {BsModalService} from "ngx-bootstrap/modal";
+import {FrameOptrPasswordComponent} from "../../routes/frame/optr/frame-optr-password/frame-optr-password.component";
+import {FrameService} from "../../core/service/frame.service";
+import {FrameOptrEditComponent} from "../../routes/frame/optr/frame-optr-edit/frame-optr-edit.component";
+import {MsgService} from "../../core/service/msg.service";
+import {AjaxService} from "../../core/service/ajax.service";
+
+@Component({
+  selector: 'app-header',
+  templateUrl: './header.component.html',
+  styleUrls: ['./header.component.scss']
+})
+export class HeaderComponent implements OnInit {
+
+  navCollapsed = true; // for horizontal layout
+  menuItems = []; // for horizontal layout
+  router: Router;
+
+  isNavSearchVisible: boolean;
+  @ViewChild('fsbutton', {static: true}) fsbutton;  // the fullscreen button
+
+  constructor(public menu: MenuService, public userblockService: UserblockService, public settings: SettingsService, public injector: Injector,public modalService: BsModalService) {
+
+    // show only a few items on demo
+    this.menuItems = menu.getMenu().slice(0, 4); // for horizontal layout
+
+  }
+
+  ngOnInit() {
+    this.isNavSearchVisible = false;
+
+    var ua = window.navigator.userAgent;
+    if (ua.indexOf("MSIE ") > 0 || !!ua.match(/Trident.*rv\:11\./)) { // Not supported under IE
+      this.fsbutton.nativeElement.style.display = 'none';
+    }
+
+    // Switch fullscreen icon indicator
+    const el = this.fsbutton.nativeElement.firstElementChild;
+    screenfull.on('change', () => {
+      if (el)
+        el.className = screenfull.isFullscreen ? 'fa fa-compress' : 'fa fa-expand';
+    });
+
+    this.router = this.injector.get(Router);
+
+    // Autoclose navbar on mobile when route change
+    this.router.events.subscribe((val) => {
+      // scroll view to top
+      window.scrollTo(0, 0);
+      // close collapse menu
+      this.navCollapsed = true;
+    });
+
+  }
+
+  toggleUserBlock(event) {
+    event.preventDefault();
+    this.userblockService.toggleVisibility();
+  }
+
+  openNavSearch(event) {
+    event.preventDefault();
+    event.stopPropagation();
+    this.setNavSearchVisible(true);
+  }
+
+  setNavSearchVisible(stat: boolean) {
+    // console.log(stat);
+    this.isNavSearchVisible = stat;
+  }
+
+  getNavSearchVisible() {
+    return this.isNavSearchVisible;
+  }
+
+  toggleOffsidebar() {
+    this.settings.toggleLayoutSetting('offsidebarOpen');
+  }
+
+  toggleCollapsedSideabar() {
+    this.settings.toggleLayoutSetting('isCollapsed');
+  }
+
+  isCollapsedText() {
+    return this.settings.getLayoutSetting('isCollapsedText');
+  }
+
+  toggleFullScreen(event) {
+    screenfull.toggle();
+  }
+
+  /****************
+   * 修改密码
+   */
+  modifyPassword($event) {
+    const modal = Object.assign({}, {
+      animated: true,
+      keyboard: false,
+      backdrop: true,
+      ignoreBackdropClick: true,
+      initialState: {},
+      class: 'modal-4p'
+    });
+    modal.initialState = {};
+    modal.initialState['ResponseEntity'] = FrameService.FrameOptr;
+    modal.initialState['optrModel'] = 'edit';
+    this.modalService.show(FrameOptrPasswordComponent,modal );
+  }
+
+
+  /****************
+   * 修改操作员信息
+   */
+  modifyOptr($event) {
+    const modal = Object.assign({}, {
+      animated: true,
+      keyboard: false,
+      backdrop: true,
+      ignoreBackdropClick: true,
+      initialState: {},
+      class: 'modal-65p'
+    });
+    modal.initialState = {};
+    let opt = Object.assign({},FrameService.FrameOptr);
+    opt.resMap = undefined;
+    modal.initialState['ResponseEntity'] = opt;
+    this.modalService.show(FrameOptrEditComponent,modal );
+  }
+
+  //退出系统
+  logout(){
+    MsgService.confirmAlert('确定要退出系统吗?', () => {
+      AjaxService.request("frame/optr/logout.htm",{},  () => {
+        window.location.href = "./assets/login/index.html";
+      });
+    });
+  }
+
+
+}

+ 9 - 0
src/app/layout/header/navsearch/navsearch.component.html

@@ -0,0 +1,9 @@
+<!-- START Search form-->
+<form class="navbar-form" role="search" action="search.html" [class.open]="visible" (submit)="handleForm()">
+    <div class="form-group">
+        <input [(ngModel)]="term" name="term" class="form-control" type="text" placeholder="{{'header.search.PLACEHOLDER' | translate}}" />
+        <div class="fa fa-times navbar-form-close" (click)="closeNavSearch()"></div>
+    </div>
+    <button class="d-none" type="submit">Submit</button>
+</form>
+<!-- END Search form-->

+ 0 - 0
src/app/layout/header/navsearch/navsearch.component.scss


+ 45 - 0
src/app/layout/header/navsearch/navsearch.component.ts

@@ -0,0 +1,45 @@
+import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChange, ElementRef } from '@angular/core';
+
+@Component({
+    selector: 'app-navsearch',
+    templateUrl: './navsearch.component.html',
+    styleUrls: ['./navsearch.component.scss']
+})
+export class NavsearchComponent implements OnInit, OnChanges {
+
+    @Input() visible: boolean;
+    @Output() onclose = new EventEmitter<boolean>();
+    term: string;
+
+    constructor(public elem: ElementRef) { }
+
+    ngOnInit() {
+        document.addEventListener('keyup', event => {
+            if (event.keyCode === 27) {// ESC
+                this.closeNavSearch();
+            }
+        });
+        document.addEventListener('click', event => {
+            const contains = (this.elem.nativeElement !== event.target && this.elem.nativeElement.contains(event.target));
+            if (!contains) {
+                this.closeNavSearch();
+            }
+        });
+    }
+
+    closeNavSearch() {
+        this.visible = false;
+        this.onclose.emit();
+    }
+
+    ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
+        // console.log(changes['visible'].currentValue)
+        if (changes['visible'].currentValue === true) {
+            this.elem.nativeElement.querySelector('input').focus();
+        }
+    }
+
+    handleForm() {
+        console.log('Form submit: ' + this.term);
+    }
+}

+ 15 - 0
src/app/layout/layout.component.h.html

@@ -0,0 +1,15 @@
+<div class="wrapper">
+    <!-- top navbar-->
+    <app-header class="topnavbar-wrapper"></app-header>
+    <!-- offsidebar-->
+    <app-offsidebar class="offsidebar"></app-offsidebar>
+    <!-- Main section-->
+    <section class="section-container">
+        <!-- Page content-->
+        <div class="content-wrapper">
+            <router-outlet></router-outlet>
+        </div>
+    </section>
+    <!-- Page footer-->
+    <footer class="footer-container" app-footer></footer>
+</div>

+ 17 - 0
src/app/layout/layout.component.html

@@ -0,0 +1,17 @@
+<div class="wrapper">
+    <!-- top navbar-->
+    <app-header class="topnavbar-wrapper"></app-header>
+    <!-- sidebar-->
+    <app-sidebar class="aside-container"></app-sidebar>
+    <!-- offsidebar-->
+    <app-offsidebar class="offsidebar"></app-offsidebar>
+    <!-- Main section-->
+    <section class="section-container">
+        <!-- Page content-->
+        <div class="content-wrapper">
+            <router-outlet></router-outlet>
+        </div>
+    </section>
+    <!-- Page footer-->
+    <footer class="footer-container" app-footer></footer>
+</div>

+ 0 - 0
src/app/layout/layout.component.scss


+ 15 - 0
src/app/layout/layout.component.ts

@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+    selector: 'app-layout',
+    templateUrl: './layout.component.html',
+    styleUrls: ['./layout.component.scss']
+})
+export class LayoutComponent implements OnInit {
+
+    constructor() { }
+
+    ngOnInit() {
+    }
+
+}

+ 47 - 0
src/app/layout/layout.module.ts

@@ -0,0 +1,47 @@
+import {NgModule} from '@angular/core';
+
+import {LayoutComponent} from './layout.component';
+import {SidebarComponent} from './sidebar/sidebar.component';
+import {HeaderComponent} from './header/header.component';
+import {NavsearchComponent} from './header/navsearch/navsearch.component';
+import {OffsidebarComponent} from './offsidebar/offsidebar.component';
+import {UserblockComponent} from './sidebar/userblock/userblock.component';
+import {UserblockService} from './sidebar/userblock/userblock.service';
+import {FooterComponent} from './footer/footer.component';
+
+import {SharedModule} from '../shared/shared.module';
+import {FrameOptrPasswordComponent} from "../routes/frame/optr/frame-optr-password/frame-optr-password.component";
+import {FrameOptrEditComponent} from "../routes/frame/optr/frame-optr-edit/frame-optr-edit.component";
+
+@NgModule({
+  imports: [
+    SharedModule
+  ],
+  providers: [
+    UserblockService
+  ],
+  declarations: [
+    LayoutComponent,
+    SidebarComponent,
+    UserblockComponent,
+    HeaderComponent,
+    NavsearchComponent,
+    OffsidebarComponent,
+    FooterComponent,
+    FrameOptrPasswordComponent,
+    FrameOptrEditComponent
+  ],
+  exports: [
+    LayoutComponent,
+    SidebarComponent,
+    UserblockComponent,
+    HeaderComponent,
+    NavsearchComponent,
+    OffsidebarComponent,
+    FooterComponent,
+    FrameOptrPasswordComponent,
+    FrameOptrEditComponent
+  ]
+})
+export class LayoutModule {
+}

+ 272 - 0
src/app/layout/offsidebar/offsidebar.component.html

@@ -0,0 +1,272 @@
+<tabset [justified]="true">
+  <tab>
+    <ng-template tabHeading>
+      <em class="icon-equalizer fa-lg"></em>
+    </ng-template>
+    <h3 class="text-center text-thin mt-4" [innerHTML]="'offsidebar.setting.SETTINGS' | translate"></h3>
+    <div class="p-2">
+      <h4 class="text-muted text-thin">Themes</h4>
+      <div class="row row-flush mb-2">
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="A"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-info"></span>
+                            <span class="color bg-info-light"></span>
+                            </span>
+              <span class="color bg-white"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="B"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-green"></span>
+                            <span class="color bg-green-light"></span>
+                            </span>
+              <span class="color bg-white"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="C"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-purple"></span>
+                            <span class="color bg-purple-light"></span>
+                            </span>
+              <span class="color bg-white"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="D"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-danger"></span>
+                            <span class="color bg-danger-light"></span>
+                            </span>
+              <span class="color bg-white"></span>
+            </label>
+          </div>
+        </div>
+      </div>
+      <div class="row row-flush mb-2">
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="E"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-info-dark"></span>
+                            <span class="color bg-info"></span>
+                            </span>
+              <span class="color bg-gray-dark"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="F"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-green-dark"></span>
+                            <span class="color bg-green"></span>
+                            </span>
+              <span class="color bg-gray-dark"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="G"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-purple-dark"></span>
+                            <span class="color bg-purple"></span>
+                            </span>
+              <span class="color bg-gray-dark"></span>
+            </label>
+          </div>
+        </div>
+        <div class="col mb-2">
+          <div class="setting-color">
+            <label>
+              <input type="radio" name="setting-theme" [(ngModel)]="currentTheme" (ngModelChange)="setTheme()"
+                     value="H"/>
+              <span class="icon-check"></span>
+              <span class="split">
+                            <span class="color bg-danger-dark"></span>
+                            <span class="color bg-danger"></span>
+                            </span>
+              <span class="color bg-gray-dark"></span>
+            </label>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="p-2">
+      <h4 class="text-muted text-thin">Layout</h4>
+      <div class="clearfix">
+        <p class="float-left">Fixed</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('isFixed')"
+                   (ngModelChange)="settings.setLayoutSetting('isFixed', $event)"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <div class="clearfix">
+        <p class="float-left">Boxed</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('isBoxed')"
+                   (ngModelChange)="settings.setLayoutSetting('isBoxed', $event)"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <!--
+<div class="clearfix">
+<p class="float-left">RTL</p>
+<div class="float-right">
+   <label class="switch">
+      <input type="checkbox" [(ngModel)]="layoutRTL"/>
+      <span></span>
+   </label>
+</div>
+</div>
+-->
+    </div>
+    <div class="p-2" *ngIf="!settings.getLayoutSetting('horizontal')">
+      <h4 class="text-muted text-thin">Aside</h4>
+      <div class="clearfix">
+        <p class="float-left">Collapsed</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('isCollapsed')"
+                   (ngModelChange)="settings.setLayoutSetting('isCollapsed', $event)"
+                   [disabled]="settings.getLayoutSetting('isCollapsedText')"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <div class="clearfix">
+        <p class="float-left">Collapsed Text</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('isCollapsedText')"
+                   (ngModelChange)="settings.setLayoutSetting('isCollapsedText', $event)"
+                   [disabled]="settings.getLayoutSetting('isCollapsed')"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <div class="clearfix">
+        <p class="float-left">Float</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('isFloat')"
+                   (ngModelChange)="settings.setLayoutSetting('isFloat', $event)"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <div class="clearfix">
+        <p class="float-left">Hover</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('asideHover')"
+                   (ngModelChange)="settings.setLayoutSetting('asideHover', $event)"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+      <div class="clearfix">
+        <p class="float-left">Show Scrollbar</p>
+        <div class="float-right">
+          <label class="switch">
+            <input type="checkbox"
+                   [ngModel]="settings.getLayoutSetting('asideScrollbar')"
+                   (ngModelChange)="settings.setLayoutSetting('asideScrollbar', $event)"/>
+            <span></span>
+          </label>
+        </div>
+      </div>
+    </div>
+    <div class="p-2">
+      <h4 class="text-muted text-thin">Language</h4>
+      <select class="form-control" [ngModel]="selectedLanguage" (ngModelChange)="setLang($event)">
+        <option [value]="lang.code" *ngFor="let lang of getLangs()">{{lang.text}}</option>
+      </select>
+    </div>
+  </tab>
+  <tab>
+    <ng-template tabHeading>
+      <em class="icon-user fa-lg"></em>
+    </ng-template>
+    <h3 class="text-center text-thin mt-4">Connections</h3>
+    <div class="list-group">
+      <!-- START list title-->
+      <div class="list-group-item border-0">
+        <small class="text-muted">ONLINE</small>
+      </div>
+      <!-- END list title-->
+     
+    </div>
+    <div class="px-3 py-4 text-center">
+      <!-- Optional link to list more users-->
+      <a class="btn btn-purple btn-sm" href="#" title="See more contacts">
+        <strong>Load more..</strong>
+      </a>
+    </div>
+    <!-- Extra items-->
+    <div class="px-3 py-2">
+      <p>
+        <small class="text-muted">Tasks completion</small>
+      </p>
+      <div class="progress progress-xs m-0">
+        <div class="progress-bar bg-success" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"
+             style="width: 80%;">
+          <span class="sr-only">80% Complete</span>
+        </div>
+      </div>
+    </div>
+    <div class="px-3 py-2">
+      <p>
+        <small class="text-muted">Upload quota</small>
+      </p>
+      <div class="progress progress-xs m-0">
+        <div class="progress-bar bg-warning" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"
+             style="width: 40%;">
+          <span class="sr-only">40% Complete</span>
+        </div>
+      </div>
+    </div>
+  </tab>
+</tabset>

+ 0 - 0
src/app/layout/offsidebar/offsidebar.component.scss


+ 52 - 0
src/app/layout/offsidebar/offsidebar.component.ts

@@ -0,0 +1,52 @@
+import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core';
+
+import { SettingsService } from '../../core/settings/settings.service';
+import { ThemesService } from '../../core/themes/themes.service';
+import { TranslatorService } from '../../core/translator/translator.service';
+
+@Component({
+    selector: 'app-offsidebar',
+    templateUrl: './offsidebar.component.html',
+    styleUrls: ['./offsidebar.component.scss']
+})
+export class OffsidebarComponent implements OnInit, OnDestroy {
+
+    currentTheme: any;
+    selectedLanguage: string;
+
+    constructor(public settings: SettingsService, public themes: ThemesService, public translator: TranslatorService, public elem: ElementRef) {
+        this.currentTheme = themes.getDefaultTheme();
+        this.selectedLanguage = this.getLangs()[0].code;
+    }
+
+    ngOnInit() {
+        this.anyClickClose();
+    }
+
+    setTheme() {
+        this.themes.setTheme(this.currentTheme);
+    }
+
+    getLangs() {
+        return this.translator.getAvailableLanguages();
+    }
+
+    setLang(value) {
+        this.translator.useLanguage(value);
+    }
+
+    anyClickClose() {
+        document.addEventListener('click', this.checkCloseOffsidebar, false);
+    }
+
+    checkCloseOffsidebar = e => {
+        const contains = (this.elem.nativeElement !== e.target && this.elem.nativeElement.contains(e.target));
+        if (!contains) {
+            this.settings.setLayoutSetting('offsidebarOpen', false);
+        }
+    }
+
+    ngOnDestroy() {
+        document.removeEventListener('click', this.checkCloseOffsidebar);
+    }
+}

+ 113 - 0
src/app/layout/sidebar/sidebar.component.html

@@ -0,0 +1,113 @@
+<div class="aside-inner">
+  <nav class="sidebar" sidebar-anyclick-close="" [class.show-scrollbar]="settings.getLayoutSetting('asideScrollbar')">
+
+    <ul class="sidebar-nav">
+      <!--
+      <li class="has-user-block">
+          <app-userblock></app-userblock>
+      </li>
+    -->
+
+      <li *ngFor='let item of menuItems' [ngClass]="{'nav-heading': item.heading}" [routerLinkActive]="['active']">
+        <!-- menu heading -->
+        <span *ngIf="item.heading">{{(item.translate | translate) || item.text}}</span>
+        <!-- external links -->
+        <a href *ngIf="!item.heading && !item.submenu && item.elink" [attr.target]="item.target" [attr.href]="item.elink" title="{{item.text}}">
+          <span class="float-right" *ngIf="item.alert" [ngClass]="item.label || 'badge badge-success'">{{item.alert}}</span>
+          <em class="{{item.icon}}" *ngIf="item.icon"></em>
+          <span>{{(item.translate | translate) || item.text}}</span>
+        </a>
+        <!-- single menu item -->
+        <a href *ngIf="!item.heading && !item.submenu && !item.elink" [routerLink]="item.link" [attr.route]="item.link" title="{{item.text}}"
+           (click)="toggleSubmenuClick($event)" (mouseenter)="toggleSubmenuHover($event)">
+          <span class="float-right" *ngIf="item.alert" [ngClass]="item.label || 'badge badge-success'">{{item.alert}}</span>
+          <em class="{{item.icon}}" *ngIf="item.icon"></em>
+          <span>{{(item.translate | translate) || item.text}}</span>
+        </a>
+        <!-- has submenu -->
+        <a href *ngIf="!item.heading && item.submenu" title="{{item.text}}"
+           (click)="toggleSubmenuClick($event)" (mouseenter)="toggleSubmenuHover($event)">
+          <span class="float-right" *ngIf="item.alert" [ngClass]="item.label || 'badge badge-success'">{{item.alert}}</span>
+          <em class="{{item.icon}}" *ngIf="item.icon"></em>
+          <span>{{(item.translate | translate) || item.text}}</span>
+        </a>
+        <!-- SUBLEVEL -->
+        <ul *ngIf="item.submenu" class="sidebar-nav sidebar-subnav" [routerLinkActive]="['opening']">
+          <li class="sidebar-subnav-header">{{(item.translate | translate) || item.text}}</li>
+          <li *ngFor='let subitem of item.submenu' [routerLinkActive]="['active']">
+            <!-- sublevel: external links -->
+            <a href *ngIf="!subitem.heading && !subitem.submenu && subitem.elink" [attr.target]="subitem.target" [attr.href]="subitem.elink" title="{{subitem.text}}">
+              <span class="float-right" *ngIf="subitem.alert" [ngClass]="subitem.label || 'badge badge-success'">{{subitem.alert}}</span>
+              <em class="{{subitem.icon}}" *ngIf="subitem.icon"></em>
+              <span>{{(subitem.translate | translate) || subitem.text}}</span>
+            </a>
+            <!-- sublevel: single menu item  -->
+            <a href *ngIf="!subitem.submenu && !subitem.elink" [routerLink]="subitem.link" [attr.route]="subitem.link" title="{{subitem.text}}">
+              <span class="float-right" *ngIf="subitem.alert" [ngClass]="subitem.label || 'badge badge-success'">{{subitem.alert}}</span>
+              <em class="{{subitem.icon}}" *ngIf="subitem.icon"></em>
+              <span>{{(subitem.translate | translate) || subitem.text}}</span>
+            </a>
+            <!-- sublevel: has submenu -->
+            <a href *ngIf="subitem.submenu" title="{{subitem.text}}"
+               (click)="toggleSubmenuClick($event)" (mouseenter)="toggleSubmenuHover($event)">
+              <span class="float-right" *ngIf="subitem.alert" [ngClass]="subitem.label || 'badge badge-success'">{{subitem.alert}}</span>
+              <em class="{{subitem.icon}}" *ngIf="subitem.icon"></em>
+              <span>{{(subitem.translate | translate) || subitem.text}}</span>
+            </a>
+            <!-- SUBLEVEL 2 -->
+            <ul *ngIf="subitem.submenu" class="sidebar-nav sidebar-subnav level2" [routerLinkActive]="['opening']">
+              <li *ngFor='let subitem2 of subitem.submenu' [routerLinkActive]="['active']">
+                <!-- sublevel 2: single menu item  -->
+                <a href *ngIf="!subitem2.submenu" class="ml-1" [routerLink]="subitem2.link" [attr.route]="subitem2.link" title="{{subitem2.text}}">
+                  <span class="float-right" *ngIf="subitem2.alert" [ngClass]="subitem2.label || 'badge badge-success'">{{subitem2.alert}}</span>
+                  <em class="{{subitem2.icon}}" *ngIf="subitem2.icon"></em>
+                  <span>{{(subitem2.translate | translate) || subitem2.text}}</span>
+                </a>
+                <!-- sublevel2: has submenu -->
+                <a href *ngIf="subitem2.submenu" title="{{subitem2.text}}"
+                   (click)="toggleSubmenuClick($event)" (mouseenter)="toggleSubmenuHover($event)">
+                  <span class="float-right" *ngIf="subitem2.alert" [ngClass]="subitem2.label || 'badge badge-success'">{{subitem2.alert}}</span>
+                  <em class="{{subitem2.icon}}" *ngIf="subitem2.icon"></em>
+                  <span>{{(subitem2.translate | translate) || subitem2.text}}</span>
+                </a>
+                <!-- SUBLEVEL 3 -->
+                <ul *ngIf="subitem2.submenu" class="sidebar-nav sidebar-subnav level3" [routerLinkActive]="['opening']">
+                  <li *ngFor='let subitem3 of subitem2.submenu' [routerLinkActive]="['active']">
+                    <!-- sublevel 2: single menu item  -->
+                    <a href *ngIf="!subitem3.submenu" [routerLink]="subitem3.link" [attr.route]="subitem3.link" title="{{subitem3.text}}">
+                      <span class="float-right" *ngIf="subitem3.alert" [ngClass]="subitem3.label || 'badge badge-success'">{{subitem3.alert}}</span>
+                      <em class="{{subitem3.icon}}" *ngIf="subitem3.icon"></em>
+                      <span>{{(subitem3.translate | translate) || subitem3.text}}</span>
+                    </a>
+                    <!-- sublevel3: has submenu -->
+                    <a href *ngIf="subitem3.submenu" title="{{subitem3.text}}"
+                       (click)="toggleSubmenuClick($event)" (mouseenter)="toggleSubmenuHover($event)">
+                      <span class="float-right" *ngIf="subitem3.alert" [ngClass]="subitem3.label || 'badge badge-success'">{{subitem3.alert}}</span>
+                      <em class="{{subitem3.icon}}" *ngIf="subitem3.icon"></em>
+                      <span>{{(subitem3.translate | translate) || subitem3.text}}</span>
+                    </a>
+                    <!-- SUBLEVEL 4 -->
+                    <ul *ngIf="subitem3.submenu" class="sidebar-nav sidebar-subnav level3" [routerLinkActive]="['opening']">
+                      <li *ngFor='let subitem4 of subitem3.submenu' [routerLinkActive]="['active']">
+                        <!-- sublevel 2: single menu item  -->
+                        <a href *ngIf="!subitem4.submenu" [routerLink]="subitem4.link" [attr.route]="subitem4.link" title="{{subitem4.text}}">
+                          <span class="float-right" *ngIf="subitem4.alert" [ngClass]="subitem4.label || 'badge badge-success'">{{subitem4.alert}}</span>
+                          <em class="{{subitem4.icon}}" *ngIf="subitem4.icon"></em>
+                          <span>{{(subitem4.translate | translate) || subitem4.text}}</span>
+                        </a>
+                      </li>
+                    </ul>
+                  </li>
+                </ul>
+              <li>
+            </ul>
+          <li>
+        </ul>
+      </li>
+
+    </ul>
+    <!-- END sidebar nav-->
+
+  </nav>
+</div>
+<!-- END Sidebar (left)-->

+ 0 - 0
src/app/layout/sidebar/sidebar.component.scss


+ 195 - 0
src/app/layout/sidebar/sidebar.component.ts

@@ -0,0 +1,195 @@
+import { Component, OnInit, Injector, OnDestroy } from '@angular/core';
+import { Router } from '@angular/router';
+declare var $: any;
+
+import { MenuService } from '../../core/menu/menu.service';
+import { SettingsService } from '../../core/settings/settings.service';
+
+@Component({
+    selector: 'app-sidebar',
+    templateUrl: './sidebar.component.html',
+    styleUrls: ['./sidebar.component.scss']
+})
+export class SidebarComponent implements OnInit, OnDestroy {
+
+    menuItems: Array<any>;
+    router: Router;
+    sbclickEvent = 'click.sidebar-toggle';
+    $doc: any = null;
+
+    constructor(public menu: MenuService, public settings: SettingsService, public injector: Injector) {
+
+        this.menuItems = menu.getMenu();
+       
+
+    }
+
+    ngOnInit() {
+
+        this.router = this.injector.get(Router);
+
+        this.router.events.subscribe((val) => {
+            // close any submenu opened when route changes
+            this.removeFloatingNav();
+            // scroll view to top
+            window.scrollTo(0, 0);
+            // close sidebar on route change
+            this.settings.setLayoutSetting('asideToggled', false);
+        });
+
+        // enable sidebar autoclose from extenal clicks
+        this.anyClickClose();
+        this.router.navigateByUrl('./art/exam/entrance/interview');
+    }
+
+    anyClickClose() {
+        this.$doc = $(document).on(this.sbclickEvent, (e) => {
+            if (!$(e.target).parents('.aside-container').length) {
+                this.settings.setLayoutSetting('asideToggled', false);
+            }
+        });
+    }
+
+    ngOnDestroy() {
+        if (this.$doc)
+            this.$doc.off(this.sbclickEvent);
+    }
+
+    toggleSubmenuClick(event) {
+
+        event.preventDefault();
+
+        if (!this.isSidebarCollapsed() && !this.isSidebarCollapsedText() && !this.isEnabledHover()) {
+
+            let ul = $(event.currentTarget.nextElementSibling);
+
+            // hide other submenus
+            let parentNav = ul.parents('.sidebar-subnav');
+            $('.sidebar-subnav').each((idx, el) => {
+                let $el = $(el);
+                // if element is not a parent or self ul
+                if (el !== parentNav[0] && el !== ul[0]) {
+                    this.closeMenu($el);
+                }
+            });
+
+            // abort if not UL to process
+            if (!ul.length) {
+                return;
+            }
+
+            // any child menu should start closed
+            ul.find('.sidebar-subnav').each((idx, el) => {
+                this.closeMenu($(el));
+            });
+
+            // toggle UL height
+            const ulHeight = ul.css('height')
+            if (ulHeight === 'auto' || parseInt(ulHeight, 10)) {
+                this.closeMenu(ul);
+            }
+            else {
+                // expand menu
+                ul.on('transitionend', () => {
+                    ul.css('height', 'auto').off('transitionend');
+                }).css('height', ul[0].scrollHeight);
+                // add class to manage animation
+                ul.addClass('opening');
+            }
+
+        }
+
+    }
+
+    // Close menu collapsing height
+    closeMenu(elem) {
+        elem.css('height', elem[0].scrollHeight); // set height
+        elem.css('height', 0); // and move to zero to collapse
+        elem.removeClass('opening');
+    }
+
+    toggleSubmenuHover(event) {
+        let self = this;
+        if (this.isSidebarCollapsed() || this.isSidebarCollapsedText() || this.isEnabledHover()) {
+            event.preventDefault();
+
+            this.removeFloatingNav();
+
+            let ul = $(event.currentTarget.nextElementSibling);
+            let anchor = $(event.currentTarget);
+
+            if (!ul.length) {
+                return; // if not submenu return
+            }
+
+            let $aside = $('.aside-container');
+            let $asideInner = $aside.children('.aside-inner'); // for top offset calculation
+            let $sidebar = $asideInner.children('.sidebar');
+            let mar = parseInt($asideInner.css('padding-top'), 0) + parseInt($aside.css('padding-top'), 0);
+            let itemTop = ((anchor.parent().position().top) + mar) - $sidebar.scrollTop();
+
+            let floatingNav = ul.clone().appendTo($aside);
+            let vwHeight = document.body.clientHeight;
+
+            // let itemTop = anchor.position().top || anchor.offset().top;
+
+            floatingNav
+                .addClass('nav-floating')
+
+            // each item has ~40px height
+            // multiply to force space for at least N items
+            var safeOffsetValue = (40 * 5)
+            var navHeight = floatingNav.outerHeight(true) + 2; // 2px border
+            var safeOffset = navHeight < safeOffsetValue ? navHeight : safeOffsetValue;
+
+            var displacement = 25; // displacement in px from bottom
+
+            // if not enough space to show N items, use then calculated 'safeOffset'
+            var menuTop = (vwHeight - itemTop > safeOffset) ? itemTop : (vwHeight - safeOffset - displacement);
+
+            floatingNav
+                .removeClass('opening') // necesary for demo if switched between normal//collapsed mode
+                .css({
+                    position: this.settings.getLayoutSetting('isFixed') ? 'fixed' : 'absolute',
+                    top: menuTop,
+                    bottom: (floatingNav.outerHeight(true) + menuTop > vwHeight) ? (displacement+'px') : 'auto'
+                });
+
+            floatingNav
+                .on('mouseleave', () => { floatingNav.remove(); })
+                .find('a').on('click', function(e) {
+                    e.preventDefault(); // prevents page reload on click
+                    // get the exact route path to navigate
+                    let routeTo = $(this).attr('route');
+                    if (routeTo) self.router.navigate([routeTo]);
+                });
+
+            this.listenForExternalClicks();
+
+        }
+
+    }
+
+    listenForExternalClicks() {
+        let $doc = $(document).on('click.sidebar', (e) => {
+            if (!$(e.target).parents('.aside-container').length) {
+                this.removeFloatingNav();
+                $doc.off('click.sidebar');
+            }
+        });
+    }
+
+    removeFloatingNav() {
+        $('.nav-floating').remove();
+    }
+
+    isSidebarCollapsed() {
+        return this.settings.getLayoutSetting('isCollapsed');
+    }
+    isSidebarCollapsedText() {
+        return this.settings.getLayoutSetting('isCollapsedText');
+    }
+    isEnabledHover() {
+        return this.settings.getLayoutSetting('asideHover');
+    }
+}

+ 14 - 0
src/app/layout/sidebar/userblock/userblock.component.html

@@ -0,0 +1,14 @@
+<div class="item user-block" *ngIf="userBlockIsVisible()">
+    <!-- User picture-->
+    <div class="user-block-picture">
+        <div class="user-block-status">
+            <img class="img-thumbnail rounded-circle" [src]="user.picture" alt="Avatar" />
+            <div class="circle bg-success circle-lg"></div>
+        </div>
+    </div>
+    <!-- Name and Job-->
+    <div class="user-block-info">
+        <span class="user-block-name">{{ 'sidebar.WELCOME' | translate }}  John</span>
+        <span class="user-block-role">Programmer</span>
+    </div>
+</div>

+ 0 - 0
src/app/layout/sidebar/userblock/userblock.component.scss


+ 20 - 0
src/app/layout/sidebar/userblock/userblock.component.spec.ts

@@ -0,0 +1,20 @@
+/* tslint:disable:no-unused-variable */
+
+import { TestBed, async, inject } from '@angular/core/testing';
+
+import { UserblockComponent } from './userblock.component';
+import { UserblockService } from './userblock.service';
+
+describe('Component: Userblock', () => {
+
+    beforeEach(() => {
+        TestBed.configureTestingModule({
+            providers: [UserblockService]
+        }).compileComponents();
+    });
+
+    it('should create an instance', async(inject([UserblockService], (userBlockService) => {
+        let component = new UserblockComponent(userBlockService);
+        expect(component).toBeTruthy();
+    })));
+});

+ 26 - 0
src/app/layout/sidebar/userblock/userblock.component.ts

@@ -0,0 +1,26 @@
+import { Component, OnInit } from '@angular/core';
+
+import { UserblockService } from './userblock.service';
+
+@Component({
+    selector: 'app-userblock',
+    templateUrl: './userblock.component.html',
+    styleUrls: ['./userblock.component.scss']
+})
+export class UserblockComponent implements OnInit {
+    user: any;
+    constructor(public userblockService: UserblockService) {
+
+        this.user = {
+            picture: 'assets/img/user/01.jpg'
+        };
+    }
+
+    ngOnInit() {
+    }
+
+    userBlockIsVisible() {
+        return this.userblockService.getVisibility();
+    }
+
+}

+ 16 - 0
src/app/layout/sidebar/userblock/userblock.service.spec.ts

@@ -0,0 +1,16 @@
+/* tslint:disable:no-unused-variable */
+
+import { TestBed, async, inject } from '@angular/core/testing';
+import { UserblockService } from './userblock.service';
+
+describe('Service: Userblock', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [UserblockService]
+    });
+  });
+
+  it('should ...', inject([UserblockService], (service: UserblockService) => {
+    expect(service).toBeTruthy();
+  }));
+});

+ 23 - 0
src/app/layout/sidebar/userblock/userblock.service.ts

@@ -0,0 +1,23 @@
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class UserblockService {
+    public userBlockVisible: boolean;
+    constructor() {
+        // initially visible
+        this.userBlockVisible  = true;
+    }
+
+    getVisibility() {
+        return this.userBlockVisible;
+    }
+
+    setVisibility(stat = true) {
+        this.userBlockVisible = stat;
+    }
+
+    toggleVisibility() {
+        this.userBlockVisible = !this.userBlockVisible;
+    }
+
+}

+ 43 - 0
src/app/routes/art/admit/admit-aspect/admit-aspect.component.html

@@ -0,0 +1,43 @@
+<div class="content-heading  justify-content-between">
+  <div>录取专业明细</div>
+  <div class="float-right">
+
+  </div>
+</div>
+<div class=" table-responsive bg-white">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th class="th-index">#</th>
+      <th>专业代码</th>
+      <th>专业名称</th>
+      <th>专业全称</th>
+      <th>方向代码</th>
+      <th>方向名称</th>
+      <th>方向全称</th>
+      <th>层次</th>
+      <th>年制</th>
+      <th>省内拟录</th>
+      <th>省外拟录</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let v of pager.getRecords(); let i = index">
+      <td>{{i+1}}</td>
+      <td>{{v.major_code}}</td>
+      <td>{{v.major_name}}</td>
+      <td>{{v.major_full_name}}</td>
+      <td>{{v.aspect_code}}</td>
+      <td>{{v.aspect_name}}</td>
+      <td>{{v.aspect_full_name}}</td>
+      <td>{{v.major_level}}</td>
+      <td>{{v.major_years}}</td>
+      <td>{{v.beadmit_province_level}}</td>
+      <td>{{v.beadmit_country_level}}</td>
+     </tr>
+    </tbody>
+  </table>
+  <frame-pager [url]="'./admit/aspect/page.htm'" [hidden]="true" #pager></frame-pager>
+</div>
+
+

+ 0 - 0
src/app/routes/art/admit/admit-aspect/admit-aspect.component.scss


+ 19 - 0
src/app/routes/art/admit/admit-aspect/admit-aspect.component.ts

@@ -0,0 +1,19 @@
+import { Component, OnInit } from '@angular/core';
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { FramePageComponent } from '../../../frame/core/page/frame.page';
+
+@Component({
+  selector: 'app-admit-aspect',
+  templateUrl: './admit-aspect.component.html',
+  styleUrls: ['./admit-aspect.component.scss']
+})
+export class AdmitAspectComponent extends FramePageComponent implements OnInit {
+
+  constructor(private modalService: BsModalService) {
+    super(modalService);
+  }
+
+  ngOnInit() {
+  }
+
+}

+ 39 - 0
src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.html

@@ -0,0 +1,39 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">该省专业代码单独定义:{{ResponseMap?.AmProvince.province_name}}</h4>
+</div>
+<div class="modal-body">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th>专业名称</th>
+      <th>国标代码</th>
+      <th>省编名称</th>
+      <th>省编代码</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let v of ResponseMap?.AspectArray; let i = index">
+      <td>{{v.aspect_full_name}}</td>
+      <td>{{v.aspect_code}}</td>
+      <td><input class="form-control input-group-sm" type="text" [(ngModel)]="v.province_aspect_name"/></td>
+      <td><input class="form-control input-group-sm" type="text" [(ngModel)]="v.province_aspect_code"/></td>
+    </tr>
+    </tbody>
+  </table>
+
+</div>
+<div class="modal-footer">
+  <!-- <button class="btn btn-secondary" type="button"
+          (click)="uploader.uploadFiles({province_id: ResponseMap.AmProvince.province_id})">上传专业代码
+  </button> -->
+  <button class="btn btn-secondary" type="button" >上传专业代码
+  </button>
+  <button class="btn btn-primary ml-2" type="button"
+          (click)="saveAspect()">确定该省单独编码
+  </button>
+  <button class="btn btn-secondary ml-2" type="button"
+          (click)="closeWindow()">关闭窗口
+  </button>
+</div>
+
+<!-- <frame-uploader [uploadUrl]="'admit/province/aspect/upload.htm'" [allowedFileType]="['xls']" (afterUpload)="afterUpload($event)" #uploader></frame-uploader> -->

+ 0 - 0
src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.scss


+ 49 - 0
src/app/routes/art/admit/admit-province-aspect/admit-province-aspect.component.ts

@@ -0,0 +1,49 @@
+import { Component, OnInit } from '@angular/core';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../core/service/ajax.service';
+import { MsgService } from '../../../../core/service/msg.service';
+import { FrameDetailComponent } from '../../../frame/core/detail/frame.detail';
+
+@Component({
+  selector: 'app-admit-province-aspect',
+  templateUrl: './admit-province-aspect.component.html',
+  styleUrls: ['./admit-province-aspect.component.scss']
+})
+export class AdmitProvinceAspectComponent  extends FrameDetailComponent implements OnInit {
+
+  AspectArray = [];
+
+  constructor(bsModalRef: BsModalRef) {
+    super(bsModalRef);
+  }
+
+  ngOnInit() {
+  }
+
+  afterUpload($event) {
+    AjaxService.requestMap('admit/province/aspect/init.htm', {province_id: this.ResponseMap.AmProvince.province_id}, (map) => {
+      this.ResponseMap = map;
+    });
+  }
+
+  saveAspect() {
+    for (const s of this.ResponseMap.AspectArray) {
+      if (!s.province_aspect_code || s.province_aspect_code.length === 0) {
+        MsgService.errorToastr(`请输入【${s.aspect_full_name}】的专业代码`);
+        break;
+      }
+    }
+
+    const fn = () => {
+      AjaxService.request('admit/province/aspect/save.htm', {
+        aspect_json: JSON.stringify(this.ResponseMap.AspectArray),
+        province_id: this.ResponseMap.AmProvince.province_id
+      }, () => {
+        this.closeWindow();
+        MsgService.successAlert(`${this.ResponseMap.AmProvince.province_name} 专业代码保存成功!`);
+      }, { RequestWithLoading: true });
+    };
+    MsgService.confirmAlert('请仔细核对文化线,确定要保存吗?', fn);
+  }
+
+}

+ 63 - 0
src/app/routes/art/admit/admit-province-score/admit-province-score.component.html

@@ -0,0 +1,63 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">确认文化线:{{ResponseMap?.AmProvince.province_name}}</h4>
+</div>
+<div class="modal-body">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th>大类名称</th>
+      <th>文科合格线</th>
+      <th>理科合格线</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let v of ResponseMap?.ScoreArray; let i = index">
+      <td>{{v.dict_text}}</td>
+      <td><input class="form-control input-group-sm" type="number" [(ngModel)]="v.qualified_score_liberal"/></td>
+      <td><input class="form-control input-group-sm" type="number" [(ngModel)]="v.qualified_score_science"/></td>
+    </tr>
+    </tbody>
+  </table>
+  <div class="card card-default">
+    <div class="card-header">
+      <button class="btn btn-primary" type="button"
+              (click)="addAspect()">添加例外专业
+      </button>
+    </div>
+    <div class="card-body">
+      <table class="table table-striped table-hover table-bordered">
+        <thead>
+        <tr>
+          <th>专业名称</th>
+          <th>文科合格线</th>
+          <th>理科合格线</th>
+          <th></th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr *ngFor="let v of ResponseMap?.AspectArray; let i = index">
+          <td>
+            <select class="form-control input-group-sm" [hidden]="v.aspect_code" (change)="changAspect($event,v)">
+              <option [value]="a.aspect_code" *ngFor="let a of AspectArray">{{a.aspect_full_name}}</option>
+            </select>
+            <span *ngIf="v.aspect_code">{{v.aspect_full_name}}</span></td>
+          <td><input class="form-control input-group-sm" type="number" [(ngModel)]="v.qualified_score_liberal"/></td>
+          <td><input class="form-control input-group-sm" type="number" [(ngModel)]="v.qualified_score_science"/></td>
+          <td><span class="table-button ml-3 text-danger" (click)="deleteAspect(v)"><em class="fa-lg fas fa-trash-alt "></em></span></td>
+        </tr>
+        </tbody>
+      </table>
+    </div>
+  </div>
+</div>
+<div class="modal-footer">
+  <button class="btn btn-info" type="button"
+          (click)="makeWish()">重新解析志愿文件
+  </button>
+  <button class="btn btn-primary ml-5" type="button"
+          (click)="confirmScore()">确定该省文化线
+  </button>
+  <button class="btn btn-secondary ml-2" type="button"
+          (click)="closeWindow()">关闭窗口
+  </button>
+</div>

+ 0 - 0
src/app/routes/art/admit/admit-province-score/admit-province-score.component.scss


+ 113 - 0
src/app/routes/art/admit/admit-province-score/admit-province-score.component.ts

@@ -0,0 +1,113 @@
+import {Component, OnInit} from '@angular/core';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../core/service/ajax.service';
+import { MsgService } from '../../../../core/service/msg.service';
+import { FrameDetailComponent } from '../../../frame/core/detail/frame.detail';
+
+@Component({
+  selector: 'app-admit-province-score',
+  templateUrl: './admit-province-score.component.html',
+  styleUrls: ['./admit-province-score.component.scss']
+})
+export class AdmitProvinceScoreComponent extends FrameDetailComponent implements OnInit {
+
+  AspectArray = [];
+
+  constructor(bsModalRef: BsModalRef) {
+    super(bsModalRef);
+  }
+
+  ngOnInit() {
+  }
+
+
+  addAspect() {
+    const array = [];
+    array.push({});
+    for (const a of this.ResponseMap.AmAspectArray) {
+      let has = false;
+      for (const b of this.ResponseMap.AspectArray) {
+        if (a.aspect_code === b.aspect_code) {
+          has = true;
+          break;
+        }
+      }
+      if (!has) {
+        array.push(a);
+      }
+    }
+    this.AspectArray = array;
+    this.ResponseMap.AspectArray.push({province_id: this.ResponseMap.AmProvince.province_id});
+  }
+
+  // 更改了专业
+  changAspect($event, v) {
+    for (const a of this.ResponseMap.AmAspectArray) {
+      if (a.aspect_code === $event.target.value) {
+        v.aspect_full_name = a.aspect_full_name;
+        v.aspect_code = a.aspect_code;
+      }
+    }
+  }
+
+  // 删除专业
+  deleteAspect(v) {
+    for (let index = 0; index < this.ResponseMap.AspectArray.length; index++) {
+      if (this.ResponseMap.AspectArray[index].aspect_code === v.aspect_code) {
+        this.ResponseMap.AspectArray.splice(index, 1);
+        break;
+      }
+    }
+  }
+
+  confirmScore() {
+    for (const s of this.ResponseMap.ScoreArray) {
+      if (!s.qualified_score_liberal) {
+        MsgService.errorToastr(`请输入【${s.dict_text}】的文科分数线`);
+        break;
+      }
+
+      if (!s.qualified_score_science) {
+        MsgService.errorToastr(`请输入【${s.dict_text}】的理科分数线`);
+        break;
+      }
+    }
+    for (const s of this.ResponseMap.AspectArray) {
+      if (!s.qualified_score_liberal) {
+        MsgService.errorToastr(`请输入【${s.aspect_full_name}】的文科分数线,如该专业不需要单独划线,请直接删除`);
+        break;
+      }
+    }
+    for (const s of this.ResponseMap.AspectArray) {
+      if (!s.qualified_score_science) {
+        MsgService.errorToastr(`请输入【${s.aspect_full_name}】的理科分数线,如该专业不需要单独划线,请直接删除`);
+        break;
+      }
+    }
+
+
+    const fn = () => {
+      AjaxService.request('admit/province/score.htm', {
+        category_json: JSON.stringify(this.ResponseMap.ScoreArray),
+        aspect_json: JSON.stringify(this.ResponseMap.AspectArray),
+        province_id: this.ResponseMap.AmProvince.province_id
+      }, () => {
+        this.closeWindow();
+        MsgService.successAlert(`${this.ResponseMap.AmProvince.province_name} 文化分数线保存成功!`);
+      }, { RequestWithLoading: true });
+    };
+    MsgService.confirmAlert('请仔细核对文化线,确定要保存吗?', fn);
+  }
+
+
+  makeWish() {
+    const fn = () => {
+      AjaxService.request('admit/province/wish/make.htm', {
+        province_id: this.ResponseMap.AmProvince.province_id
+      }, () => {
+        MsgService.successAlert(`${this.ResponseMap.AmProvince.province_name} 志愿信息重新合成完成!`);
+      }, { RequestWithLoading: true });
+    };
+    MsgService.confirmAlert('确定要重新合成该省的志愿吗?', fn);
+  }
+}

+ 91 - 0
src/app/routes/art/admit/admit-province/admit-province.component.html

@@ -0,0 +1,91 @@
+<div class="content-heading  justify-content-between">
+  <div>录取状态一览</div>
+  <div class="float-">
+    <div class="btn-group mb-1 mr-5" dropdown>
+      <button class="btn dropdown-toggle btn-secondary btn-lg" type="button" dropdownToggle>志愿管理
+        <span class="caret"></span>
+      </button>
+      <div *dropdownMenu class="dropdown-menu modal-lg" role="menu">
+        <a class="dropdown-item" href="#" (click)="FuncService.ajaxDownload('admit/score/download.htm')">下载各专业录取线</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="FuncService.ajaxDownload('admit/wish/aspect/download.htm')">下载拟录取名单(专业排序)</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="FuncService.ajaxDownload('admit/wish2/download.htm')">下载第二志愿拟录</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="FuncService.ajaxDownload('admit/wish/province/download.htm')">下载分省拟录名单</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="FuncService.ajaxDownload('admit/wish/all/download.htm')">下载全国志愿总表</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="makeAllWish()">合成所有省份志愿</a>
+        <a class="dropdown-item" href="#" (click)="admitAllWish()">进行拟录判断</a>
+
+
+
+      </div>
+    </div>
+  </div>
+</div>
+<table class="table table-striped table-hover table-bordered">
+  <thead>
+  <tr>
+    <th class="text-center">代码</th>
+    <th class="text-center">省份名称</th>
+    <th class="text-center">启用录取</th>
+    <th class="text-center">专业状态</th>
+    <th class="text-center">合格名单</th>
+    <th class="text-center">文化线状态</th>
+    <th class="text-center">考生志愿</th>
+    <th class="text-center">拟录取名单</th>
+    <th class="text-center">最终录取</th>
+    <td class="th-b-1"></td>
+  </tr>
+  </thead>
+  <tbody>
+  <tr *ngFor="let v of pager.getRecords(); let i = index">
+    <td class="text-center">{{v.province_id}}</td>
+    <td>{{v.province_name}}</td>
+    <td class="text-center" [innerHTML]="v.province_status | frameStatus"></td>
+
+    <td [hidden]="v.province_status === 'InActive'" class="text-center">
+      <span class="table-button text-danger" title="上传【{{v.province_name}}】专业编码" (click)="uploadAspect(v)" *ngIf="v.aspect_code_status !== 'Active'">
+          未导入
+      </span>
+      <span class="table-button text-success" title="上传【{{v.province_name}}】专业编码" (click)="uploadAspect(v)" *ngIf="v.aspect_code_status === 'Active'">
+          已导入
+      </span>
+    </td>
+
+    <td [hidden]="v.province_status === 'InActive'" class="text-center"
+        [innerHTML]="v.qualified_std_upload_status | frameStatusCustom : ['已上传','未上传']"></td>
+    <td [hidden]="v.province_status === 'InActive'" class="text-center">
+      <span class="table-button" [ngClass]="{'text-success': v.cultural_line_status === 'Active', 'text-danger':v.cultural_line_status !== 'Active'}" title="上传【{{v.province_name}}】志愿表" (click)="confirmScore(v)">
+          {{v.cultural_line_status === 'Active'? '已确定':'未确定'}}
+      </span>
+    </td>
+    <td [hidden]="v.province_status === 'InActive'" class="text-center">
+      <span class="table-button text-danger" title="上传【{{v.province_name}}】志愿表" (click)="uploadWish(v)" *ngIf="v.wish_status !== 'Active'">
+          未导入
+      </span>
+      <span class="table-button text-success" title="上传【{{v.province_name}}】志愿表" (click)="uploadWish(v)" *ngIf="v.wish_status === 'Active'">
+          已导入
+      </span>
+    </td>
+    <td [hidden]="v.province_status === 'InActive'" class="text-center"
+        [innerHTML]="v.beadmit_upload_status | frameStatusCustom : ['已发送','未完成']"></td>
+    <td [hidden]="v.province_status === 'InActive'" class="text-center"
+        [innerHTML]="v.admit_upload_status | frameStatusCustom : ['已完成','未完成']"></td>
+    <td  [hidden]="v.province_status === 'InActive'">
+      <span class="table-button" title="下载考生志愿" [hidden]="v.wish_status !== 'Active'" (click)="FuncService.ajaxDownload('admit/province/wish/download.htm', {province_id: v.province_id})"><em class="fa-lg fas fa-cloud-download-alt"></em></span>
+
+    </td>
+  </tr>
+  </tbody>
+</table>
+
+<frame-pager [url]="'./admit/province/page.htm'" [showPagination]="false" #pager ></frame-pager>
+
+<!-- <frame-uploader [uploadUrl]="'admit/province/wish/upload.htm'" (afterUpload)="afterUpload($event)" #uploader></frame-uploader> -->
+
+
+
+

+ 0 - 0
src/app/routes/art/admit/admit-province/admit-province.component.scss


+ 78 - 0
src/app/routes/art/admit/admit-province/admit-province.component.ts

@@ -0,0 +1,78 @@
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {AdmitWishConfirmComponent} from '../admit-wish-confirm/admit-wish-confirm.component';
+import {AdmitProvinceScoreComponent} from '../admit-province-score/admit-province-score.component';
+import {AdmitProvinceAspectComponent} from '../admit-province-aspect/admit-province-aspect.component';
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../core/service/ajax.service';
+import { MsgService } from '../../../../core/service/msg.service';
+import { FramePageComponent } from '../../../frame/core/page/frame.page';
+import { ModalService } from '../../../../core/service/modal.service';
+
+@Component({
+  selector: 'app-admit-province',
+  templateUrl: './admit-province.component.html',
+  styleUrls: ['./admit-province.component.scss']
+})
+export class AdmitProvinceComponent extends FramePageComponent implements OnInit {
+
+  // @ViewChild('uploader', {static: false}) uploader: UploaderComponent;
+
+  constructor(private modalService: BsModalService) {
+    super(modalService);
+  }
+
+  ngOnInit() {
+  }
+
+  // 上传志愿
+  uploadWish(v) {
+    // this.uploader.uploadUrl = './admit/province/wish/upload.htm';
+    // this.uploader.uploadFiles({province_id: v.province_id});
+  }
+
+  // 确认志愿字段
+  afterUpload($event) {
+    this.pager.reload();
+    this.showComponentModal(AdmitWishConfirmComponent, {
+      'mapUrl': 'admit/province/wish/confirm/init.htm',
+      'mapParam': {province_id: $event.entity}
+    });
+  }
+
+  // 确认分数线
+  confirmScore(v) {
+    this.showComponentModal(AdmitProvinceScoreComponent, {
+      'mapUrl': 'admit/province/score/init.htm',
+      'mapParam': {province_id: v.province_id}
+    });
+  }
+
+  // 上传专业代码
+  uploadAspect(v) {
+    this.showComponentModal(AdmitProvinceAspectComponent, {
+      'mapUrl': 'admit/province/aspect/init.htm',
+      'mapParam': {province_id: v.province_id}
+    });
+
+  }
+
+  makeAllWish() {
+    MsgService.confirmAlert('确定要合成所有省份的考生志愿吗?需要运行很长时间!', () => {
+      AjaxService.request('admit/province/wish/make.htm', {}, (data) => {
+        // this.showThreadRunning(data.entity);
+        ModalService.showThreadWindow(this.bsService,data.entity);
+      });
+    });
+
+  }
+
+  admitAllWish() {
+    MsgService.confirmAlert('确定要进行拟录取判断吗?需要运行很长时间!', () => {
+      AjaxService.request('admit/wish/make.htm', {}, (data) => {
+        // this.showThreadRunning(data.entity);
+        ModalService.showThreadWindow(this.bsService,data.entity);
+      }, {RequestWithLoading: true});
+    });
+  }
+
+}

+ 32 - 0
src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.html

@@ -0,0 +1,32 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">志愿数据字段定义:{{ResponseMap?.AmProvince.province_name}}
+    【{{ResponseMap?.StdCount}}】个考生 </h4>
+</div>
+<div class="modal-body">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th class="th-index">#</th>
+      <th>字段名称</th>
+      <th>中文描述</th>
+      <th>字段定义</th>
+      <th>字段定义描述</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let v of ResponseMap?.ColumnArray; let i = index">
+      <td>{{v.column_order}}</td>
+      <td>{{v.column_name}}</td>
+      <td><input class="form-control input-group-sm" type="text" [(ngModel)]="v.column_alias"/></td>
+      <td><select class="form-control input-group-sm" frameDict [dictName]="'AdmitWishDataType'" [tipMsg]=" " [(ngModel)]="v.data_type"> </select></td>
+      <td><input class="form-control input-group-sm" type="text" [(ngModel)]="v.data_type_param"/></td>
+
+    </tr>
+    </tbody>
+  </table>
+</div>
+<div class="modal-footer">
+  <button class="btn btn-primary" type="button"
+          (click)="confirm()">确定该省志愿字段定义
+  </button>
+</div>

+ 0 - 0
src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.scss


+ 111 - 0
src/app/routes/art/admit/admit-wish-confirm/admit-wish-confirm.component.ts

@@ -0,0 +1,111 @@
+import {Component, OnInit} from '@angular/core';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../core/service/ajax.service';
+import { MsgService } from '../../../../core/service/msg.service';
+import { FrameDetailComponent } from '../../../frame/core/detail/frame.detail';
+
+@Component({
+  selector: 'app-admit-wish-confirm',
+  templateUrl: './admit-wish-confirm.component.html',
+  styleUrls: ['./admit-wish-confirm.component.scss']
+})
+export class AdmitWishConfirmComponent extends FrameDetailComponent implements OnInit {
+
+  constructor(bsModalRef: BsModalRef) {
+    super(bsModalRef);
+  }
+
+  ngOnInit() {
+  }
+
+
+  confirm() {
+    // 定义几个必须的
+    let exam_id, wish_1, score_total, score_chinese, score_math, score_english;
+    for (const v of this.ResponseMap.ColumnArray) {
+      // 判断几个重要的是否都有
+      if (v.data_type === 'exam_id') {
+        if (exam_id) {
+          MsgService.errorAlert('定义错误:同时定义了两个考生号字段');
+          return;
+        }
+        exam_id = v;
+      }
+
+      if (v.data_type === 'wish_1') {
+        if (wish_1) {
+          MsgService.errorAlert('定义错误:同时定义了两个第一志愿字段');
+          return;
+        }
+        wish_1 = v;
+      }
+
+      if (v.data_type === 'score_total') {
+        if (score_total) {
+          MsgService.errorAlert('定义错误:同时定义了两个总分字段');
+          return;
+        }
+        score_total = v;
+      }
+
+      if (v.data_type === 'score_chinese') {
+        if (score_chinese) {
+          MsgService.errorAlert('定义错误:同时定义了两个语文分数字段');
+          return;
+        }
+        score_chinese = v;
+      }
+
+      if (v.data_type === 'score_math') {
+        if (score_math) {
+          MsgService.errorAlert('定义错误:同时定义了两个数学分数字段');
+          return;
+        }
+        score_math = v;
+      }
+
+      if (v.data_type === 'score_english') {
+        if (score_english) {
+          MsgService.errorAlert('定义错误:同时定义了两个外语分数字段');
+          return;
+        }
+        score_english = v;
+      }
+
+    }
+
+    if (!exam_id) {
+      MsgService.errorAlert('定义错误:未定义考生号字段');
+      return;
+    }
+    if (!wish_1) {
+      MsgService.errorAlert('定义错误:未定义第一志愿字段');
+      return;
+    }
+    if (!score_total) {
+      MsgService.errorAlert('定义错误:未定义高考总分字段');
+      return;
+    }
+    // if (!score_chinese) {
+    //   MsgService.errorAlert('定义错误:未定义语文字段');
+    //   return;
+    // }
+    // if (!score_math) {
+    //   MsgService.errorAlert('定义错误:未定义数学字段');
+    //   return;
+    // }
+    // if (!score_english) {
+    //   MsgService.errorAlert('定义错误:未定义外语字段');
+    //   return;
+    // }
+
+    const fn = () => {
+      AjaxService.request('admit/province/wish/confirm.htm', {json_value: JSON.stringify(this.ResponseMap.ColumnArray), province_id: this.ResponseMap.AmProvince.province_id}, () => {
+        this.closeWindow();
+        MsgService.successAlert('志愿字段定义保存成功,如要修改请重新上传该省志愿!');
+      },{RequestWithLoading: true});
+    };
+    MsgService.confirmAlert('请仔细核对,确定要保存吗?', fn);
+
+  }
+}

+ 27 - 0
src/app/routes/art/admit/admit.art.module.ts

@@ -0,0 +1,27 @@
+import {NgModule} from '@angular/core';
+import {Routes, RouterModule} from '@angular/router';
+import {SharedModule} from '../../../shared/shared.module';
+import {AdmitAspectComponent} from './admit-aspect/admit-aspect.component';
+import {AdmitProvinceComponent} from './admit-province/admit-province.component';
+import { AdmitWishConfirmComponent } from './admit-wish-confirm/admit-wish-confirm.component';
+import { AdmitProvinceScoreComponent } from './admit-province-score/admit-province-score.component';
+import { AdmitProvinceAspectComponent } from './admit-province-aspect/admit-province-aspect.component';
+
+const routes: Routes = [
+  {path: 'aspect', component: AdmitAspectComponent},
+  {path: 'province', component: AdmitProvinceComponent}
+];
+
+@NgModule({
+  imports: [
+    SharedModule,
+    RouterModule.forChild(routes)
+  ],
+  entryComponents: [AdmitWishConfirmComponent, AdmitProvinceScoreComponent, AdmitProvinceAspectComponent],
+  declarations: [AdmitAspectComponent, AdmitProvinceComponent, AdmitWishConfirmComponent, AdmitProvinceScoreComponent, AdmitProvinceAspectComponent],
+  exports: [
+    RouterModule
+  ]
+})
+export class ArtAdmitModule {
+}

+ 35 - 0
src/app/routes/art/art.module.ts

@@ -0,0 +1,35 @@
+import {NgModule} from '@angular/core';
+import {Routes, RouterModule} from '@angular/router';
+import {SharedModule} from '../../shared/shared.module';
+import {StdRegChooseComponent} from './std/reg/std-reg-choose/std-reg-choose.component';
+import {StdRegSearchComponent} from './std/reg/std-reg-search/std-reg-search.component';
+
+
+const routes: Routes = [
+  {path: 'conf', loadChildren: () => import('./conf/conf.art.module').then(m => m.ArtConfModule)},
+  {path: 'enrol', loadChildren: () => import('./enrol/enrol.art.module').then(m => m.ArtEnrolModule)},
+  {path: 'std', loadChildren: () => import('./std/std.art.module').then(m => m.ArtStdModule)},
+  {path: 'exam', loadChildren: () => import('./exam/exam.art.module').then(m => m.ArtExamModule)},
+  {path: 'layout', loadChildren: () => import('./layout/layout.art.module').then(m => m.ArtLayoutModule)},
+  {path: 'score', loadChildren: () => import('./score/score.art.module').then(m => m.ArtScoreModule)},
+  {path: 'admit', loadChildren: () => import('./admit/admit.art.module').then(m => m.ArtAdmitModule)},
+  {path: 'plan', loadChildren: () => import('./plan/plan.art.module').then(m => m.ArtPlanModule)},
+  {path: 'wish', loadChildren: () => import('./wish/wish.art.module').then(m => m.ArtWishModule)},
+  {path: 'devops', loadChildren: () => import('./devops/devops.art.module').then(m => m.ArtDevopsModule)}
+];
+
+@NgModule({
+  imports: [
+    RouterModule.forChild(routes),
+    SharedModule
+  ],
+  entryComponents: [],
+  declarations: [StdRegSearchComponent,StdRegChooseComponent],
+  exports: [
+    RouterModule,
+    StdRegSearchComponent,
+    StdRegChooseComponent
+  ]
+})
+export class ArtModule {
+}

+ 32 - 0
src/app/routes/art/conf/agent/agent.conf.module.ts

@@ -0,0 +1,32 @@
+import {NgModule} from '@angular/core';
+import {Routes, RouterModule} from '@angular/router';
+import {ConfAgentPageComponent} from './conf-agent-page/conf-agent-page.component';
+import {SharedModule} from '../../../../shared/shared.module';
+import { ConfAgentDetailComponent } from './conf-agent-detail/conf-agent-detail.component';
+import { ConfAgentCategoryComponent } from './conf-agent-category/conf-agent-category.component';
+import { ConfAgentAspectComponent } from './conf-agent-aspect/conf-agent-aspect.component';
+import { ConfAgentProvinceComponent } from './conf-agent-province/conf-agent-province.component';
+import { ConfAgentDeptComponent } from './conf-agent-dept/conf-agent-dept.component';
+import { ConfAgentDeptDetailComponent } from './conf-agent-dept-detail/conf-agent-dept-detail.component';
+import { ConfAgentAspectAppointmentComponent } from './conf-agent-aspect-appointment/conf-agent-aspect-appointment.component';
+import { ConfAgentAspectAppointmentDetailComponent } from './conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component';
+
+const routes: Routes = [
+  {path: 'page', component: ConfAgentPageComponent},
+  {path: 'adPage', component: ConfAgentDeptComponent},
+  {path: 'appointment', component: ConfAgentAspectAppointmentComponent}
+];
+
+@NgModule({
+  imports: [
+    SharedModule,
+    RouterModule.forChild(routes)
+  ],
+  entryComponents: [],
+  declarations: [ConfAgentPageComponent, ConfAgentDetailComponent, ConfAgentCategoryComponent, ConfAgentAspectComponent, ConfAgentProvinceComponent, ConfAgentDeptComponent, ConfAgentDeptDetailComponent, ConfAgentAspectAppointmentComponent, ConfAgentAspectAppointmentDetailComponent],
+  exports: [
+    RouterModule
+  ]
+})
+export class ConfAgentModule {
+}

+ 74 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.html

@@ -0,0 +1,74 @@
+<div class="modal-header">
+    <h4 class="modal-title pull-left">编辑-报考预约容量-【{{currentEntity.aspect_name}}】</h4>
+</div>
+  <div class="modal-body mb-1">
+
+    <tabset class="bg-white p-0" justified="true" *ngIf="appointmentArr != undefined 
+    &&  appointmentArr.length>0 && appointmentArr[0].appointment_date">
+      <tab *ngFor="let item of appointmentArr" >
+        <ng-template tabHeading>
+          <em class="far fa-clock fa-fw"></em> {{item.appointment_date}}
+        </ng-template>
+        <div>
+          <div class="card card-default">
+            <div class="card-header bg-info"><span class="ml-2">容量安排</span>
+              <div class="float-right">
+                <button class="btn btn-primary mr-2" type="button" (click)="addTime(item)">
+                  增加时段
+                </button>
+              </div>
+            </div>
+            <div class="card-body">
+              <table class="table table-striped table-hover table-bordered">
+                <thead>
+                <tr>
+                  <th class="th-index">#</th>
+                  <th>开始时间</th>
+                  <th>结束时间</th>
+                  <th class="wd-sm">考生数</th>
+                  <th class="tb-b-1"></th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr *ngFor="let v of item.appointments; let i = index">
+                  <td>{{i+1}}</td>
+                  <td><input class="form-control" type="text" [(ngModel)]="v.start_time" /></td>
+                  <td><input class="form-control" type="text" [(ngModel)]="v.end_time" /></td>
+                  <td>
+                    <input class="form-control form-control-sm wd-sm" type="number" [(ngModel)]="v.appointment_capacity" />
+                  </td>
+                  <td>
+                    <span class="table-button text-danger" (click)="deleteTime(item,v, i)">
+                       <em class="fa-lg fas fa-trash-alt "></em></span>
+                  </td>
+                </tr>
+                </tbody>
+              </table>
+            </div>
+          </div>
+         
+  
+        </div>
+      </tab>
+    </tabset>
+  </div>
+  
+  <div class="modal-footer justify-content-between">
+    <div>
+      <div class="row">
+        <label class="col-lg-5 col-form-label text-lg-right text-right">预约日期:</label>
+        <div class="col-lg-3 col-md-3">
+          <input class="form-control wd-sm" type="text" [(ngModel)]="appointment_date" maxlength="11"/>
+        </div>
+      </div>
+    </div>
+    <div>
+      <button class="btn btn-primary" type="button" (click)="addDate()">
+        增加预约日期
+      </button>
+      <button class="btn btn-primary ml-2" type="button" (click)="saveAppointment()">
+        保存容量
+      </button>
+      <button class="btn btn-secondary ml-2" type="button" (click)="closeWindow()">关闭窗口</button>
+    </div>
+  </div>

+ 0 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.scss


+ 113 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component.ts

@@ -0,0 +1,113 @@
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, Validators } from '@angular/forms';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { AjaxService } from 'src/app/core/service/ajax.service';
+import { FrameDetailComponent } from '../../../../frame/core/detail/frame.detail';
+import { dateValidator } from '../../../../../shared/validators/date.validator';
+import { MsgService } from 'src/app/core/service/msg.service';
+
+@Component({
+  selector: 'app-conf-agent-aspect-appointment-detail',
+  templateUrl: './conf-agent-aspect-appointment-detail.component.html',
+  styleUrls: ['./conf-agent-aspect-appointment-detail.component.scss']
+})
+export class ConfAgentAspectAppointmentDetailComponent extends FrameDetailComponent implements OnInit {
+
+  appointmentArr = [];
+  appointment_date: any;
+
+  constructor(bsModalRef: BsModalRef, fb: FormBuilder) {
+    super(bsModalRef);
+   }
+
+  ngOnInit(): void {
+  }
+
+  initDefautEntity() {
+    AjaxService.requestArray('conf/agent/aspect/appointment/detail.htm', {
+      agent_id : this.currentEntity.agent_id,
+      aspect_id : this.currentEntity.aspect_id
+    }, (array) => {
+      this.appointmentArr = array;
+    })
+  }
+
+  //增加时段
+  addTime(item) {
+    let arr = [];
+    if(item) {
+      arr = item.appointments;
+      arr.push({start_time: '', end_time: '', appointment_capacity: 0});
+    }
+  }
+
+  //删除时段
+  deleteTime(item,v, index) {
+    let arr = [];
+    if(item) {
+      arr = item.appointments;
+      arr.splice(index,1);
+    }
+  }
+
+  //增加考试日期
+  addDate() {
+    if(!this.appointment_date) {
+      MsgService.errorToastr('请填写预约日期!');
+      return;
+    }
+    if (this.appointment_date.length !== 10) {
+      MsgService.errorToastr('请填写正确的预约日期');
+      return;
+    }
+    if (!isNaN(this.appointment_date) || isNaN(Date.parse(this.appointment_date))) {
+      MsgService.errorToastr('请填写正确的预约日期');
+      return;
+    }
+    for(const app of this.appointmentArr) {
+      if(app.appointment_date == this.appointment_date) {
+        MsgService.errorToastr('填写的预约日期已经存在,请不要重复增加!');
+        return ;
+      }
+    }
+    let arr = [];
+    if(this.appointmentArr && this.appointmentArr.length == 1 && !this.appointmentArr[0].appointment_date) {
+      this.appointmentArr[0]['appointment_date'] = this.appointment_date;
+    } else {
+      this.appointmentArr.push(
+        {
+          appointment_date: this.appointment_date,
+          agent_id : this.currentEntity.agent_id,
+          aspect_id : this.currentEntity.aspect_id,
+          appointments: arr
+        }
+        );
+    }
+      this.appointmentArr.sort(function(a,b) {
+        return new Date(Date.parse(a.appointment_date)).getTime()- new Date(Date.parse(b.appointment_date)).getTime();
+      });
+  }
+  
+
+  //保存容量
+  saveAppointment() {
+    for(const app of this.appointmentArr) {
+      if(app.appointments && app.appointments.length >0) {
+        for(const time of app.appointments) {
+          if(!time.start_time  || !time.end_time ) {
+            MsgService.errorToastr('请填写时间段!');
+            return;
+          }
+        }
+      }
+    }
+    AjaxService.request('conf/agent/aspect/appointment/save.htm', {
+      agent_id: this.currentEntity.agent_id,
+      aspect_id: this.currentEntity.aspect_id,
+      data_json: JSON.stringify(this.appointmentArr)
+    }, () => {
+      MsgService.successToastr('预约容量保存成功!');
+      this.closeWindow();
+    });
+  }
+}

+ 77 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.html

@@ -0,0 +1,77 @@
+<div class="content-heading  justify-content-between">
+    <div>报考预约容量管理</div>
+    <div class="float-right">
+      <div class="row">
+
+        <div class="mr-2 ml-3">
+          <select class="form-control mt-1" [(ngModel)]="appointment.agent_id"  (change)="requestAspect()">
+            <option [value]="v.agent_id" *ngFor="let v of agentArr">{{v.agent_name}}</option>
+          </select>
+        </div>
+
+        <div class="mr-2 ml-3" *ngIf="appointment.agent_id">
+          <select class="form-control mt-1" [(ngModel)]="appointment.aspect_id" (change) = "requestAppointment()">
+            <option value="">全部</option>
+            <option [value]="v.aspect_id" *ngFor="let v of aspectArr" >{{v.aspect_name}}</option>
+          </select>
+        </div>
+
+        <!-- <div class="btn-group mb-1 mr-1" dropdown>
+          <button class="btn dropdown-toggle btn-secondary btn-lg" type="button" dropdownToggle>报考预约容量管理
+            <span class="caret"></span>
+          </button>
+          <div *dropdownMenu class="dropdown-menu modal-lg" role="menu">
+            <a class="dropdown-item" href="#" (click)="showModal()">新增招考方向容量</a>
+            <div class="dropdown-divider"></div>
+          </div>
+        </div> -->
+      </div>
+    </div>
+  </div>
+
+  <div class="card card-default" *ngFor="let v of ajax.getArray(); let i = index">
+    <div class="row card-header justify-content-between bg-gray">
+      <div class="row ml-2 text-1-2">
+        <span class="table-button" [ngClass]="{'text-success':v.appointments.length>0,'text-primary':v.appointments.length===0}">
+          【{{v.agent_name}}】-{{v.aspect_name}}
+        </span>
+      </div>
+
+      <div class="float-right mr-5">
+        <div class="btn-group btn-group-sm mb-1 mr-1 ml-3" dropdown>
+          <button class="btn dropdown-toggle btn-secondary btn-lg" type="button" dropdownToggle>容量管理
+            <span class="caret"></span>
+          </button>
+          <div *dropdownMenu class="dropdown-menu modal-lg" role="menu">
+            <a class="dropdown-item" href="#" (click)="editAppointment(v)">编辑容量</a>
+            <div class="dropdown-divider"></div>
+          </div>
+        </div>
+      </div>
+
+    </div>
+
+    <table class="table table-striped table-hover table-bordered">
+      <tbody>
+        <tr align="center">
+          <th>#</th>
+          <th>预约日期</th>
+          <th>开始时间</th>
+          <th>结束时间</th>
+          <th>容量</th>
+        </tr>
+        <tr *ngFor="let item of v.appointments; let j = index" align="center">
+          <td>{{j+1}}</td>
+          <td>{{item.appointment_date | date: 'yyyy-MM-dd'}}</td>
+          <td>{{item.start_time}}</td>
+          <td>{{item.end_time}}</td>
+          <td>{{item.appointment_capacity}}人</td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+  
+  
+  
+  <frame-ajax #ajax [url]="'conf/agent/aspect/appointment/all.htm'" [ajaxDataType]="'array'" [autoRequest]="true"
+  [requestData]="{aspect_id: appointment.aspect_id, agent_id: appointment.agent_id}"></frame-ajax>

+ 0 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.scss


+ 52 - 0
src/app/routes/art/conf/agent/conf-agent-aspect-appointment/conf-agent-aspect-appointment.component.ts

@@ -0,0 +1,52 @@
+import { Component, OnInit } from '@angular/core';
+import { BsModalService } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../../core/service/ajax.service';
+import { FramePageComponent } from '../../../../frame/core/page/frame.page';
+import { ConfAgentAspectAppointmentDetailComponent } from '../conf-agent-aspect-appointment-detail/conf-agent-aspect-appointment-detail.component';
+
+@Component({
+  selector: 'app-conf-agent-aspect-appointment',
+  templateUrl: './conf-agent-aspect-appointment.component.html',
+  styleUrls: ['./conf-agent-aspect-appointment.component.scss']
+})
+export class ConfAgentAspectAppointmentComponent extends FramePageComponent implements OnInit {
+
+  agentArr = [];
+  aspectArr = [];
+  appointment : any;
+
+  constructor(private modalService: BsModalService) {
+    super(modalService);
+    this.modalComponent = ConfAgentAspectAppointmentDetailComponent;
+   }
+
+  ngOnInit(): void {
+    this.appointment = {};
+    AjaxService.requestArray('conf/agent/all.htm',{}, (array) => {
+      this.agentArr = array;
+      this.requestAspect();
+    });
+  }
+
+  onModalClose() {
+    this.ajax.reload();
+  }
+
+  requestAspect() {
+    AjaxService.requestArray('conf/aspect/appointment/list.htm', {agent_id: this.appointment.agent_id }, (array) => {
+      this.aspectArr = array;
+    });
+  }
+
+  requestAppointment() {
+    this.ajax.reload({agent_id: this.appointment.agent_id, aspect_id: this.appointment.aspect_id});
+  }
+
+  //编辑容量
+  editAppointment(v) {
+    this.showComponentModal(ConfAgentAspectAppointmentDetailComponent, {
+      ResponseEntity: v
+    });
+  }
+
+}

+ 63 - 0
src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.html

@@ -0,0 +1,63 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">考点【{{currentEntity.agent_name}}】报考专业方向</h4>
+</div>
+<div class="modal-body">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th class="th-b-2"></th>
+      <th class="th-b-2">#</th>
+      <th>专业方向</th>
+      <th>开考日期</th>
+      <th>人数上限</th>
+      <th>是否开启预约</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let s of aspectArray; let i = index">
+      <td>
+        <div class="form-check">
+          <div class="checkbox c-checkbox">
+            <label>
+              <input type="checkbox" [(ngModel)]="s.checked"/><span class="fa fa-check"></span></label>
+          </div>
+        </div>
+      </td>
+      <td>{{s.aspect_id}}</td>
+      <td>{{s.aspect_name}}</td>
+      <td><input class="form-control form-control-sm" type="text" style="width:120px;"
+                 [(ngModel)]="s.aa_start_date" [readonly]="s.checked === false"/></td>
+      <td><input class="form-control form-control-sm" type="number" style="width:120px;"
+                 [(ngModel)]="s.aa_enrol_limit" [readonly]="s.checked === false"/></td>
+      <td>
+        <ngx-select [items]="'FrameStatus' | frameDict" [(ngModel)]="s.appointment_flag" 
+         optionValueField="dict_value" optionTextField="dict_text"></ngx-select>
+      </td>
+    </tr>
+    </tbody>
+  </table>
+</div>
+<div class="modal-footer justify-content-between">
+  <div>
+    <div class="btn-group mb-1 mr-1" dropdown>
+      <button class="btn dropdown-toggle btn-secondary " type="button" dropdownToggle>辅助操作
+        <span class="caret"></span>
+      </button>
+      <div *dropdownMenu class="dropdown-menu" role="menu">
+
+        <a class="dropdown-item" href="#" (click)="selectAll(true)">全部选中</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" (click)="selectAll(false)">全部取消</a>
+      </div>
+    </div>
+
+  </div>
+  <div>
+
+
+    <button class="btn btn-primary ml-2" type="button" (click)="saveAspect()">配置专业方向
+    </button>
+    <button class="btn btn-secondary ml-2" type="button" (click)="closeWindow()">关闭窗口
+    </button>
+  </div>
+</div>

+ 0 - 0
src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.scss


+ 70 - 0
src/app/routes/art/conf/agent/conf-agent-aspect/conf-agent-aspect.component.ts

@@ -0,0 +1,70 @@
+import {Component, OnInit} from '@angular/core';
+import {FormBuilder} from '@angular/forms';
+import { BsModalRef } from 'ngx-bootstrap/modal';
+import { AjaxService } from '../../../../../core/service/ajax.service';
+import { MsgService } from '../../../../../core/service/msg.service';
+import { FrameDetailComponent } from '../../../../frame/core/detail/frame.detail';
+
+@Component({
+  selector: 'app-conf-agent-aspect',
+  templateUrl: './conf-agent-aspect.component.html',
+  styleUrls: ['./conf-agent-aspect.component.scss']
+})
+export class ConfAgentAspectComponent extends FrameDetailComponent implements OnInit {
+  aspectArray = [];
+
+  constructor(bsModalRef: BsModalRef, fb: FormBuilder) {
+    super(bsModalRef);
+
+  }
+
+  ngOnInit() {
+  }
+
+  initDefautEntity() {
+    for (const s of this.ResponseArray) {
+      if (s.agent_id !== undefined) {
+        s.checked = true;
+      } else {
+        s.checked = false;
+      }
+      if (s.aa_start_date !== undefined) {
+        s.aa_start_date = s.aa_start_date.replace(' 00:00:00', '');
+      }
+      this.aspectArray.push(s);
+    }
+  }
+
+
+  saveAspect() {
+    const array = [];
+    for (const s of this.aspectArray) {
+      if (s.checked === true) {
+        s.agent_id = this.currentEntity.agent_id;
+        array.push(s);
+      }
+    }
+    let msg = `该考点确定开放这【${array.length}】个专业方向吗?`;
+    if (array.length === 0) {
+      msg = '确定要取消所有专业方向吗?';
+    }
+
+    MsgService.confirmAlert(msg, () => {
+      AjaxService.request('conf/agent/aspect/save.htm', {
+        agent_id: this.currentEntity.agent_id,
+        json_value: JSON.stringify(array)
+      }, () => {
+        MsgService.successToastr('考点专业方向配置操作完成!');
+        this.closeWindow();
+      });
+    });
+
+
+  }
+
+  selectAll(b) {
+    for (const v of this.aspectArray) {
+      v.checked = b;
+    }
+  }
+}

+ 32 - 0
src/app/routes/art/conf/agent/conf-agent-category/conf-agent-category.component.html

@@ -0,0 +1,32 @@
+<div class="modal-header">
+  <h4 class="modal-title pull-left">考点【{{currentEntity.agent_name}}】报考大类配置</h4>
+</div>
+<div class="modal-body">
+  <table class="table table-striped table-hover table-bordered">
+    <thead>
+    <tr>
+      <th class="th-b-2">#</th>
+      <th>大类名称</th>
+      <th>开放</th>
+      <th>人数上限</th>
+    </tr>
+    </thead>
+    <tbody>
+    <tr *ngFor="let s of categoryArray; let i = index">
+      <td>{{s.category_id}}</td>
+      <td>{{s.category_name}}</td>
+      <td><select class="form-control form-control-sm" frameDict [dictName]="'FrameStatus'" [tipFlag]="false"
+                  style="width:100px;"
+                  [(ngModel)]="s.ac_status"></select></td>
+      <td><input class="form-control form-control-sm" type="number" style="width:120px;"
+                 [(ngModel)]="s.ac_enrol_limit" [readonly]="s.ac_status === 'InActive'"/></td>
+    </tr>
+    </tbody>
+  </table>
+</div>
+<div class="modal-footer">
+  <button class="btn btn-primary ml-2" type="button" (click)="saveCategory()">配置报考大类
+  </button>
+  <button class="btn btn-secondary ml-2" type="button" (click)="closeWindow()">关闭窗口
+  </button>
+</div>

+ 0 - 0
src/app/routes/art/conf/agent/conf-agent-category/conf-agent-category.component.scss


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott