├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── angular2-demo.gif ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── core │ │ ├── config.ts │ │ ├── core.module.ts │ │ ├── navbar │ │ │ ├── navbar.component.html │ │ │ ├── navbar.component.scss │ │ │ └── navbar.component.ts │ │ └── service-support.service.ts │ ├── index.ts │ ├── index │ │ ├── index.component.html │ │ ├── index.component.scss │ │ └── index.component.ts │ ├── modules │ │ ├── delivery │ │ │ ├── delivery-routing.module.ts │ │ │ ├── delivery-submit │ │ │ │ ├── delivery-submit.component.html │ │ │ │ ├── delivery-submit.component.scss │ │ │ │ └── delivery-submit.component.ts │ │ │ ├── delivery-verify │ │ │ │ ├── delivery-verify.component.html │ │ │ │ ├── delivery-verify.component.scss │ │ │ │ └── delivery-verify.component.ts │ │ │ ├── delivery.component.html │ │ │ ├── delivery.component.ts │ │ │ └── delivery.module.ts │ │ ├── home │ │ │ ├── home-routing.module.ts │ │ │ ├── home-submit │ │ │ │ ├── home-submit.component.html │ │ │ │ ├── home-submit.component.scss │ │ │ │ ├── home-submit.component.spec.ts │ │ │ │ └── home-submit.component.ts │ │ │ ├── home-verify │ │ │ │ ├── home-verify.component.html │ │ │ │ ├── home-verify.component.scss │ │ │ │ ├── home-verify.component.spec.ts │ │ │ │ └── home-verify.component.ts │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ ├── home.component.ts │ │ │ └── home.module.ts │ │ ├── index.ts │ │ └── order │ │ │ ├── order-address │ │ │ ├── order-address.component.html │ │ │ ├── order-address.component.scss │ │ │ └── order-address.component.ts │ │ │ ├── order-detail │ │ │ ├── order-detail.component.html │ │ │ ├── order-detail.component.scss │ │ │ └── order-detail.component.ts │ │ │ ├── order-routing.module.ts │ │ │ ├── order-verify │ │ │ ├── order-verify.component.html │ │ │ ├── order-verify.component.scss │ │ │ └── order-verify.component.ts │ │ │ ├── order.component.html │ │ │ ├── order.component.scss │ │ │ ├── order.component.ts │ │ │ ├── order.module.ts │ │ │ └── order.service.ts │ ├── services │ │ ├── index.ts │ │ └── validators.service.ts │ └── shared │ │ ├── bubble │ │ ├── bubble.component.html │ │ ├── bubble.component.scss │ │ └── bubble.component.ts │ │ ├── cascade-list │ │ ├── cascade-list.component.html │ │ ├── cascade-list.component.scss │ │ ├── cascade-list.component.spec.ts │ │ └── cascade-list.component.ts │ │ ├── components │ │ ├── countdown │ │ │ ├── countdown.component.html │ │ │ ├── countdown.component.scss │ │ │ ├── countdown.component.spec.ts │ │ │ └── countdown.component.ts │ │ ├── input-control │ │ │ ├── input-control.component.html │ │ │ ├── input-control.component.scss │ │ │ └── input-control.component.ts │ │ └── verify-form │ │ │ ├── verify-form.component.html │ │ │ ├── verify-form.component.scss │ │ │ └── verify-form.component.ts │ │ ├── directives │ │ ├── highlight.directive.ts │ │ └── index.ts │ │ ├── modal │ │ ├── index.ts │ │ ├── modal.component.html │ │ ├── modal.component.scss │ │ ├── modal.component.ts │ │ └── modal.service.ts │ │ ├── object-to-array.pipe.spec.ts │ │ ├── object-to-array.pipe.ts │ │ ├── select-control │ │ ├── select-control.component.html │ │ ├── select-control.component.scss │ │ ├── select-control.component.ts │ │ └── select-control.ts │ │ ├── select-list │ │ ├── index.ts │ │ ├── select-list.component.html │ │ ├── select-list.component.scss │ │ ├── select-list.component.ts │ │ ├── select-list.service.ts │ │ └── select-list.ts │ │ └── shared.module.ts ├── assets │ ├── .gitkeep │ └── global │ │ ├── _base.scss │ │ ├── _function.scss │ │ ├── _mixin.scss │ │ ├── _reset.scss │ │ ├── _variables.scss │ │ ├── flexible.js │ │ └── images │ │ ├── favicon.ico │ │ ├── icon-arrow.png │ │ ├── icon-back.png │ │ ├── icon-close.png │ │ ├── icon-delete.png │ │ ├── icon-location.svg │ │ ├── icon-popup-arrow.png │ │ ├── icon-selected.png │ │ ├── radio-checked.png │ │ └── submit-complete.png ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.json └── typings.d.ts └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = 0 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | /typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Angular2 学习项目 2 | 3 | ### 涉及到的 Ng2 特性 4 | 5 | - 模块划分 6 | - 路由懒加载 7 | - HTTP 模块 8 | - 自定义输入组件 9 | - 自定义弹层组件 10 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.19-3", 4 | "name": "angular2-mcare" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.scss" 22 | ], 23 | "scripts": [ 24 | "./assets/global/flexible.js", 25 | "../node_modules/jshashes/hashes.min.js" 26 | ], 27 | "environments": { 28 | "source": "environments/environment.ts", 29 | "dev": "environments/environment.ts", 30 | "prod": "environments/environment.prod.ts" 31 | } 32 | } 33 | ], 34 | "addons": [], 35 | "packages": [], 36 | "e2e": { 37 | "protractor": { 38 | "config": "./protractor.conf.js" 39 | } 40 | }, 41 | "test": { 42 | "karma": { 43 | "config": "./karma.conf.js" 44 | } 45 | }, 46 | "defaults": { 47 | "styleExt": "scss", 48 | "prefixInterfaces": false, 49 | "inline": { 50 | "style": false, 51 | "template": false 52 | }, 53 | "spec": { 54 | "class": false, 55 | "component": true, 56 | "directive": true, 57 | "module": false, 58 | "pipe": true, 59 | "service": true 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /angular2-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/angular2-demo.gif -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { McareAppPage } from './app.po'; 2 | 3 | describe('mcare-app App', function() { 4 | let page: McareAppPage; 5 | 6 | beforeEach(() => { 7 | page = new McareAppPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class McareAppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | remapIstanbulReporter: { 21 | reports: { 22 | html: 'coverage', 23 | lcovonly: './coverage/coverage.lcov' 24 | } 25 | }, 26 | angularCli: { 27 | config: './angular-cli.json', 28 | environment: 'dev' 29 | }, 30 | reporters: config.angularCli && config.angularCli.codeCoverage 31 | ? ['progress', 'karma-remap-istanbul'] 32 | : ['progress'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-mcare", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/common": "~2.1.0", 16 | "@angular/compiler": "~2.1.0", 17 | "@angular/core": "~2.1.0", 18 | "@angular/forms": "~2.1.0", 19 | "@angular/http": "~2.1.0", 20 | "@angular/platform-browser": "~2.1.0", 21 | "@angular/platform-browser-dynamic": "~2.1.0", 22 | "@angular/router": "~3.1.0", 23 | "core-js": "^2.4.1", 24 | "jshashes": "^1.0.6", 25 | "rxjs": "5.0.0-beta.12", 26 | "ts-helpers": "^1.1.1", 27 | "zone.js": "^0.6.23" 28 | }, 29 | "devDependencies": { 30 | "@types/jasmine": "^2.2.30", 31 | "@types/node": "^6.0.42", 32 | "angular-cli": "1.0.0-beta.19-3", 33 | "codelyzer": "1.0.0-beta.1", 34 | "jasmine-core": "2.4.1", 35 | "jasmine-spec-reporter": "2.5.0", 36 | "karma": "1.2.0", 37 | "karma-chrome-launcher": "^2.0.0", 38 | "karma-cli": "^1.0.1", 39 | "karma-jasmine": "^1.0.2", 40 | "karma-remap-istanbul": "^0.2.1", 41 | "protractor": "4.0.9", 42 | "ts-node": "1.2.1", 43 | "tslint": "3.13.0", 44 | "typescript": "~2.0.3", 45 | "webdriver-manager": "10.2.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { IndexComponent } from "./index/index.component"; 4 | 5 | export const routes: Routes = [ 6 | { path: '', redirectTo: 'index', pathMatch: 'full' }, 7 | { path: 'index', component: IndexComponent, data: { title: '我要维修' } }, 8 | { path: 'delivery', loadChildren: './modules/delivery/delivery.module#DeliveryModule' }, 9 | { path: 'home', loadChildren: './modules/home/home.module#HomeModule' }, 10 | { path: 'order', loadChildren: './modules/order/order.module#OrderModule' }, 11 | { path: '**', redirectTo: 'index' } 12 | ] 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forRoot(routes)], 16 | exports: [RouterModule] 17 | }) 18 | export class AppRoutingModule { } 19 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/app.component.scss -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent implements OnInit{ 9 | 10 | constructor(){ 11 | } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { CoreModule } from './core/core.module'; 6 | import { AppComponent } from './app.component'; 7 | import { IndexComponent } from './index/index.component'; 8 | 9 | @NgModule({ 10 | declarations: [ 11 | AppComponent, 12 | IndexComponent 13 | ], 14 | imports: [ 15 | BrowserModule, 16 | CoreModule, 17 | AppRoutingModule 18 | ], 19 | bootstrap: [AppComponent] 20 | }) 21 | export class AppModule { 22 | } 23 | -------------------------------------------------------------------------------- /src/app/core/config.ts: -------------------------------------------------------------------------------- 1 | // 这里可以配置各种全局配置信息,通过注入的方式来获取 2 | export const ApiConfig = { 3 | 4 | csApi: { 5 | 6 | }, 7 | 8 | servcie: { 9 | 10 | }, 11 | 12 | retail: { 13 | 14 | }, 15 | 16 | store: { 17 | 18 | }, 19 | 20 | baiduMap: { 21 | // 地址逆解析API地址 22 | baiduGeocoder: 'http://api.map.baidu.com/geocoder/v2/', 23 | ak: 'R6l3LOp0hMYGpxw0nZGHXWymNP9Y6kIH', 24 | location: 'http://api.map.baidu.com/location/ip', 25 | // 使用GPS经纬度坐标 26 | coordtype: 'wgs84ll' 27 | }, 28 | 29 | wan: { 30 | 31 | } 32 | } 33 | 34 | export const ConstConfig = { 35 | RepairBaseInfoKey: 'repairBaseInfo', 36 | DeliveryRepairKey: 'deliveryRepair', 37 | OrderRepairKey: 'orderRepair', 38 | HomeRepairKey: 'homeRepair', 39 | 40 | repairType: { 41 | repair: 1, // 维修 42 | replaceNew: 2, // 维修 43 | returnBack: 3 // 退机 44 | }, 45 | 46 | applyCode: { 47 | delivery: 1, // 寄送快修 48 | order: 2, // 预约维修 49 | home: 3 // 上门快修 50 | }, 51 | 52 | // 固定配置信息 53 | serviceType: [{ 54 | id: 1, 55 | label: '维修', 56 | isSelected: false 57 | }, { 58 | id: 2, 59 | label: '换新', 60 | isSelected: false 61 | }], 62 | 63 | // 故障类型 64 | faultList: [{ 65 | id: 1, 66 | label: '死机', 67 | isSelected: false 68 | }, { 69 | id: 2, 70 | label: '无法上网', 71 | isSelected: false, 72 | }, { 73 | id: 3, 74 | label: '通话异常', 75 | isSelected: false 76 | }, { 77 | id: 4, 78 | label: '屏幕故障', 79 | isSelected: false 80 | }, { 81 | id: 5, 82 | label: '信号问题', 83 | isSelected: false 84 | }, { 85 | id: 6, 86 | label: '进水', 87 | isSelected: false 88 | }, { 89 | id: 7, 90 | label: '自动关机', 91 | isSelected: false 92 | }], 93 | } 94 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NgModule, 3 | Optional, 4 | SkipSelf, 5 | ModuleWithProviders, 6 | } from '@angular/core'; 7 | import { CommonModule } from '@angular/common'; 8 | import { HttpModule, JsonpModule } from '@angular/http'; 9 | 10 | import { NavbarComponent } from './navbar/navbar.component'; 11 | import { ApiConfig, ConstConfig } from './config'; 12 | import { ServiceSupportService } from './service-support.service'; 13 | 14 | 15 | @NgModule({ 16 | imports: [CommonModule], 17 | exports: [HttpModule, JsonpModule, NavbarComponent], 18 | declarations: [NavbarComponent], 19 | // 这里注册全局的单例配置服务 20 | providers: [ServiceSupportService, { 21 | provide: 'ApiConfig', 22 | useValue: ApiConfig 23 | }, { 24 | provide: 'ConstConfig', 25 | useValue: ConstConfig 26 | }], 27 | }) 28 | export class CoreModule { 29 | 30 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) { 31 | if (parentModule) { 32 | throw new Error('CoreModule is already loaded. Import it in the AppModule only'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/core/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/app/core/navbar/navbar.component.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import "../../../assets/global/function"; 4 | @import "../../../assets/global/mixin"; 5 | @import "../../../assets/global/variables"; 6 | 7 | nav { 8 | background-color: $mainColor; 9 | color: #fff; 10 | padding: px2rem(45) px2rem(54); 11 | @include font-dpr(16px); 12 | 13 | a { 14 | display: inline-block; 15 | height: 100%; 16 | padding-right: 20px; 17 | vertical-align: middle; 18 | } 19 | 20 | .icon, 21 | .title { 22 | display: inline-block; 23 | margin-right: px2rem($bodyPadding); 24 | } 25 | 26 | .icon { 27 | width: px2rem(33); 28 | height: px2rem(58); 29 | background: url("/assets/global/images/icon-back.png") center no-repeat; 30 | background-size: px2rem(33) px2rem(58); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/core/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Title } from "@angular/platform-browser"; 3 | import { Router, ActivatedRoute, NavigationEnd } from "@angular/router"; 4 | 5 | @Component({ 6 | selector: 'app-navbar', 7 | templateUrl: './navbar.component.html', 8 | styleUrls: ['./navbar.component.scss'] 9 | }) 10 | export class NavbarComponent implements OnInit { 11 | private _title: string; 12 | 13 | constructor( 14 | private titleService: Title, 15 | private router: Router) { 16 | 17 | router.events.subscribe(event => { 18 | if (event instanceof NavigationEnd) { 19 | this._title = this.getTitle(router.routerState, router.routerState.root).join(' - '); 20 | titleService.setTitle(this._title); 21 | } 22 | }); 23 | } 24 | 25 | getTitle(state, parent) { 26 | var data = []; 27 | if (parent && parent.snapshot.data && parent.snapshot.data.title) { 28 | data.push(parent.snapshot.data.title); 29 | } 30 | 31 | if (state && parent) { 32 | data.push(...this.getTitle(state, state.firstChild(parent))); 33 | } 34 | 35 | return data; 36 | } 37 | 38 | 39 | ngOnInit() { 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/core/service-support.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@angular/core'; 2 | import { URLSearchParams, Jsonp, Response, Http } from '@angular/http'; 3 | import { Observable, Observer, ReplaySubject } from 'rxjs'; 4 | 5 | import * as Hashes from 'jshashes'; 6 | 7 | const smsConfig = { 8 | key: 'xxxx', 9 | client: 'xxxx' 10 | } 11 | 12 | /** 13 | * 服务支持服务类 14 | */ 15 | @Injectable() 16 | export class ServiceSupportService { 17 | // 级联地址库数据 18 | addressLib: Object; 19 | 20 | constructor( 21 | private _jsonp: Jsonp, 22 | private http: Http, 23 | @Inject('ApiConfig') private apiConfig: any) { 24 | } 25 | 26 | // 设置公共请求参数 27 | generateSearchParam(query?: { jsonp: boolean }): URLSearchParams { 28 | let search = new URLSearchParams(); 29 | // 统一 jsonp 请求参数 30 | if (query.jsonp) search.set('callback', 'JSONP_CALLBACK'); 31 | return search; 32 | } 33 | 34 | // 定位当前省份城市 35 | getLoaction(): Observable { 36 | let search = this.generateSearchParam({ jsonp: true }); 37 | search.set('ak', this.apiConfig.baiduMap.ak); 38 | return this._jsonp.get(this.apiConfig.baiduMap.location, { search: search }) 39 | .map(res => res.json()) 40 | .map(data => { 41 | let address = data.content.address_detail; 42 | return { 43 | province: address.province, 44 | city: address.city 45 | } 46 | }).catch((error) => Observable.throw(error.json().error || 'Server error')); 47 | } 48 | 49 | // 获取短信验证码 50 | sendSms(mobile): Observable { 51 | let timestamp = +new Date(); 52 | let sign = this.generateMsgSign(timestamp, mobile); 53 | return this.http.post(this.apiConfig.wan.sms, { 54 | phone: mobile, 55 | sign: sign, 56 | client: smsConfig.client, 57 | timestamp: timestamp 58 | }).map(res => res.json()); 59 | } 60 | 61 | checkSms(opt): Observable { 62 | return this.http.post(this.apiConfig.wan.checkSms, { 63 | phone: opt.phone, 64 | code: opt.captcha 65 | }).map(res => res.json()).map(json => { 66 | return json.data.success; 67 | }).catch((error) => Observable.throw(error.json().error || 'Server error')); 68 | } 69 | 70 | /** 71 | * 获取短信接口签名 72 | * @param {[type]} timestamp 时间戳 73 | * @param {[type]} mobile 手机号 74 | * @return {[type]} [description] 75 | */ 76 | generateMsgSign(timestamp, mobile) { 77 | return new Hashes.SHA1().hex_hmac(smsConfig.key, '?phone=' + mobile + '×tamp=' + timestamp); 78 | } 79 | 80 | checkSN(opt) { 81 | let search = this.generateSearchParam({ jsonp: true }); 82 | 83 | // return this._jsonp.get(ApiConfig.csApi.checkSn, search) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/index/index.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | -------------------------------------------------------------------------------- /src/app/index/index.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../assets/global/function"; 2 | 3 | .container { 4 | padding: 0 0.33333rem; 5 | } 6 | 7 | .service-list { 8 | position: relative; 9 | padding: px2rem(36) 0; 10 | 11 | .list-item { 12 | $blockHeight: calc(100vh/3.56); 13 | height: $blockHeight; 14 | line-height: $blockHeight; 15 | border-radius: 4px; 16 | vertical-align: middle; 17 | width:100%; 18 | background-color: #7D88F2; 19 | text-align: center; 20 | color: #fff; 21 | margin-bottom: px2rem(30); 22 | 23 | a { 24 | display: block; 25 | width: 100%; 26 | height:100%; 27 | } 28 | 29 | &:first-child { 30 | background-color: #7ED70B; 31 | } 32 | 33 | &:last-child { 34 | background-color: #FB9538; 35 | margin-bottom: 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-index', 5 | templateUrl: './index.component.html', 6 | styleUrls: ['./index.component.scss'] 7 | }) 8 | export class IndexComponent implements OnInit { 9 | 10 | constructor() { 11 | 12 | } 13 | 14 | setHtmlTitle(title: string) { 15 | } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { DeliveryVerifyComponent } from './delivery-verify/delivery-verify.component'; 5 | import { DeliverySubmitComponent } from './delivery-submit/delivery-submit.component'; 6 | import { DeliveryComponent } from "./delivery.component"; 7 | 8 | export const routes: Routes = [ 9 | { 10 | path: '', 11 | component: DeliveryComponent, 12 | data: { title: '寄送快修' }, 13 | children: [ 14 | { path: 'verify', component: DeliveryVerifyComponent, data: { title: '基础信息' } }, 15 | { path: 'submit', component: DeliverySubmitComponent, data: { title: '设备信息' } }, 16 | { path: '', redirectTo: 'verify', pathMatch: 'full' }, 17 | { path: '**', redirectTo: 'verify' } 18 | ] 19 | } 20 | ] 21 | 22 | @NgModule({ 23 | imports: [RouterModule.forChild(routes)], 24 | exports: [RouterModule] 25 | }) 26 | export class DeliveryRoutingModule { 27 | } 28 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-submit/delivery-submit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |

请输入手机号 SN 码

7 |
8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 |

保内服务免运费,保外服务每单运费40元

20 |
21 |
22 | 23 | 24 |
25 | 26 |
我已阅读以下并同意对寄修可能产生的费用进行支付。
《魅族手机售后服务政策》《保外服务报价清单》 27 |
28 |
29 |
30 | 下一步 31 |
32 |
33 |
34 |
35 | 37 |
38 |
39 |

{{modalTitle}}

40 |
41 |
42 |
43 | 44 |
45 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-submit/delivery-submit.component.scss: -------------------------------------------------------------------------------- 1 | @mixin font-dpr($font-size) { 2 | font-size: $font-size; 3 | 4 | [data-dpr="2"] & { 5 | font-size: $font-size * 2; 6 | } 7 | 8 | [data-dpr="3"] & { 9 | font-size: $font-size * 3; 10 | } 11 | } 12 | 13 | // 设计稿宽度1080,基准值为 1080/10 14 | @function px2rem($pixels, $context: 108) { 15 | @return ($pixels / $context) * 1rem; 16 | } 17 | 18 | .content{ 19 | padding: 0; 20 | background-color: #F5F5F5; 21 | .row-control{ 22 | background-color: #fff; 23 | margin-bottom: px2rem(33); 24 | 25 | .input-control:last-child, 26 | .label-control:last-child{ 27 | border-bottom: 0; 28 | } 29 | &:last-child{ 30 | margin-bottom: 0; 31 | } 32 | 33 | // 阅读协议 34 | .protocol{ 35 | padding-left: px2rem(140); 36 | text-indent: 0; 37 | border-bottom: 0; 38 | @include font-dpr(12px); 39 | 40 | .label{ 41 | padding-left: 0; 42 | text-align: left; 43 | line-height: 1.4; 44 | } 45 | 46 | a{ 47 | color: #18BD91; 48 | } 49 | .icon { 50 | left: px2rem(30); 51 | top: px2rem(62); 52 | transform: none; 53 | } 54 | } 55 | } 56 | // 按钮 57 | .btn-control{ 58 | margin-top: px2rem(30); 59 | padding-bottom: px2rem(187); 60 | } 61 | 62 | .send-address, 63 | .rec-address { 64 | .result { 65 | max-width: px2rem(600); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-submit/delivery-submit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation, Inject, AfterViewInit } from '@angular/core'; 2 | import { FormGroup, AbstractControl, FormBuilder, Validators } from '@angular/forms'; 3 | 4 | import { ServiceSupportService } from '../../../core/service-support.service'; 5 | import { ModalService, ModalComponent } from '../../../shared/modal/'; 6 | import { SelectListComponent } from '../../../shared/select-list/'; 7 | import { CascadeListComponent } from '../../../shared/cascade-list/cascade-list.component'; 8 | 9 | @Component({ 10 | selector: 'delivery-device', 11 | templateUrl: './delivery-submit.component.html', 12 | styleUrls: ['./delivery-submit.component.scss'], 13 | encapsulation: ViewEncapsulation.None 14 | }) 15 | export class DeliverySubmitComponent implements OnInit { 16 | formActive: boolean = true; 17 | deliveryFormGroup: FormGroup; 18 | sn: AbstractControl; 19 | serviceType: AbstractControl; 20 | faultType: AbstractControl; 21 | 22 | localData: any; 23 | modalTitle: string; 24 | serviceTypeObj: any; 25 | faultTypeObj: any; 26 | faultRemark: string; 27 | areaData: any; 28 | 29 | constructor( 30 | @Inject('ConstConfig') private constConfig: any, 31 | private formBuilder: FormBuilder, 32 | private serviceSupportService: ServiceSupportService, 33 | private modalService: ModalService) { 34 | } 35 | 36 | ngOnInit() { 37 | if (localStorage.getItem('DeliveryDataKey')) { 38 | this.localData = JSON.parse(localStorage.getItem('DeliveryDataKey')); 39 | } else { 40 | this.localData = {}; 41 | } 42 | this.serviceTypeObj = this.constConfig.serviceType[0]; 43 | this.faultTypeObj = this.constConfig.faultList[0]; 44 | this.deliveryFormGroup = this.formBuilder.group({ 45 | 'sn': [this.localData.sn, Validators.required], 46 | 'serviceType': [this.serviceTypeObj, Validators.required], 47 | 'faultType': [this.faultTypeObj, Validators.required] 48 | }); 49 | 50 | this.sn = this.deliveryFormGroup.controls['sn']; 51 | this.serviceType = this.deliveryFormGroup.controls['serviceType']; 52 | this.faultType = this.deliveryFormGroup.controls['faultType']; 53 | } 54 | 55 | _handleSelectClick(event, type) { 56 | event.stopPropagation(); 57 | switch (type) { 58 | case 'serviceType': 59 | this.modalTitle = '请选择维修类型'; 60 | this.modalService.open(SelectListComponent, { 61 | default: [this.serviceTypeObj], 62 | listData: this.constConfig.serviceType 63 | }).subscribe(componentRef => { 64 | let instance = componentRef.instance; 65 | if (!instance.selected.length) return; 66 | this.serviceType.setValue(instance.selected[0]); 67 | this.serviceTypeObj = instance.selected[0]; 68 | }); 69 | break; 70 | case 'faultType': 71 | this.modalTitle = '请选择故障类型'; 72 | this.modalService.open(SelectListComponent, { 73 | default: [this.faultTypeObj], 74 | listData: this.constConfig.faultList, 75 | multiple: true, 76 | remark: this.faultRemark 77 | }).subscribe(componentRef => { 78 | let instance = componentRef.instance; 79 | let faultsText = []; 80 | let faultsIds = []; 81 | if (!instance.selected.length) return; 82 | 83 | instance.selected.forEach((item) => { 84 | faultsIds.push(item.id); 85 | faultsText.push(item.label); 86 | }); 87 | 88 | faultsText.push(instance.remark); 89 | this.faultRemark = instance.remark; 90 | 91 | this.faultTypeObj = { 92 | id: faultsIds.join(','), 93 | label: faultsText.join(',') 94 | } 95 | 96 | this.faultType.setValue(this.faultTypeObj); 97 | }); 98 | break; 99 | case 'send-address': 100 | // TODO 动态加载地址组件 101 | default: 102 | break; 103 | } 104 | } 105 | 106 | actionOnConfirm() { 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-verify/delivery-verify.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-verify/delivery-verify.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/modules/delivery/delivery-verify/delivery-verify.component.scss -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery-verify/delivery-verify.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { FormGroup, FormControl, FormBuilder, Validators, AbstractControl } from '@angular/forms'; 3 | import { Router, ActivatedRoute } from '@angular/router'; 4 | 5 | import { ModalService } from '../../../shared/modal/'; 6 | 7 | @Component({ 8 | selector: 'delivery-verify', 9 | templateUrl: './delivery-verify.component.html', 10 | styleUrls: ['./delivery-verify.component.scss'], 11 | encapsulation: ViewEncapsulation.None 12 | }) 13 | export class DeliveryVerifyComponent implements OnInit { 14 | formActive: boolean = true; 15 | color: string = 'green'; 16 | verifyInfo: FormGroup; 17 | username: AbstractControl; 18 | mobile: AbstractControl; 19 | captcha: AbstractControl; 20 | 21 | modalId: string; 22 | 23 | constructor( 24 | private modalService: ModalService, 25 | private router: Router, 26 | private activateRoute: ActivatedRoute) { 27 | this.modalId = +new Date() + ''; 28 | } 29 | 30 | ngOnInit() { 31 | 32 | } 33 | 34 | onSubmit() { 35 | this.router.navigate(['../submit'], { relativeTo: this.activateRoute}); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'delivery', 5 | templateUrl: './delivery.component.html', 6 | encapsulation: ViewEncapsulation.None 7 | }) 8 | export class DeliveryComponent implements OnInit { 9 | constructor() { 10 | } 11 | 12 | ngOnInit() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/modules/delivery/delivery.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../../shared/shared.module'; 3 | 4 | import { DeliveryRoutingModule } from './delivery-routing.module'; 5 | import { DeliveryVerifyComponent } from './delivery-verify/delivery-verify.component'; 6 | import { DeliverySubmitComponent } from './delivery-submit/delivery-submit.component'; 7 | import { DeliveryComponent } from "./delivery.component"; 8 | 9 | @NgModule({ 10 | imports: [ 11 | SharedModule, 12 | DeliveryRoutingModule 13 | ], 14 | declarations: [ 15 | DeliveryComponent, 16 | DeliveryVerifyComponent, 17 | DeliverySubmitComponent 18 | ] 19 | }) 20 | export class DeliveryModule { 21 | } 22 | -------------------------------------------------------------------------------- /src/app/modules/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | 4 | import { HomeComponent } from "./home.component"; 5 | import { HomeVerifyComponent } from "./home-verify/home-verify.component"; 6 | import { HomeSubmitComponent } from "./home-submit/home-submit.component"; 7 | 8 | @NgModule({ 9 | imports: [ 10 | RouterModule.forChild([ 11 | { 12 | path: '', 13 | component: HomeComponent, 14 | data: { title: '上门快修' }, 15 | children: [ 16 | { path: 'verify', component: HomeVerifyComponent, data: { title: '基础信息'} }, 17 | { path: 'submit', component: HomeSubmitComponent, data: { title: '维修信息'} }, 18 | { path: '', redirectTo: 'verify', pathMatch: 'full' }, 19 | { path: '**', redirectTo: 'verify' } 20 | ] 21 | } 22 | ]) 23 | ], 24 | exports: [ 25 | RouterModule 26 | ] 27 | }) 28 | export class HomeRoutingModule { 29 | } 30 | -------------------------------------------------------------------------------- /src/app/modules/home/home-submit/home-submit.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 | 46 | 47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 |
55 |
56 | 57 |
我已阅读以下并同意对寄修可能产生的费用进行支付。
《魅族手机售后服务政策》《保外服务报价清单》 58 |
59 |
60 |
61 | 确认申请 62 |
63 |
64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /src/app/modules/home/home-submit/home-submit.component.scss: -------------------------------------------------------------------------------- 1 | @function px2rem($pixels, $context: 108) { 2 | @return ($pixels / $context) * 1rem; 3 | } 4 | 5 | @mixin font-dpr($font-size){ 6 | font-size: $font-size; 7 | 8 | [data-dpr="2"] & { 9 | font-size: $font-size * 2; 10 | } 11 | 12 | [data-dpr="3"] & { 13 | font-size: $font-size * 3; 14 | } 15 | } 16 | 17 | .content{ 18 | padding: 0; 19 | background-color: #F5F5F5; 20 | .row-control{ 21 | background-color: #fff; 22 | margin-bottom: px2rem(33); 23 | 24 | .input-control:last-child, 25 | .label-control:last-child{ 26 | border-bottom: 0; 27 | } 28 | &:last-child{ 29 | margin-bottom: 0; 30 | } 31 | 32 | // 注释 33 | .remark{ 34 | opacity: .5; 35 | padding: px2rem(30) 0; 36 | padding-left: px2rem(36); 37 | font-size: 12px; 38 | } 39 | 40 | // 阅读协议 41 | .protocol{ 42 | padding-left: px2rem(140); 43 | text-indent: 0; 44 | border-bottom: 0; 45 | @include font-dpr(12px); 46 | 47 | .label{ 48 | text-align: left; 49 | padding-left: 0; 50 | line-height: 1.4; 51 | } 52 | 53 | a{ 54 | color: #18BD91; 55 | } 56 | 57 | .icon{ 58 | left: px2rem(30); 59 | top: px2rem(62); 60 | transform: none; 61 | } 62 | } 63 | } 64 | // 按钮 65 | .btn-control{ 66 | margin-top: px2rem(30); 67 | padding-bottom: px2rem(187); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/app/modules/home/home-submit/home-submit.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomeSubmitComponent } from './home-submit.component'; 7 | 8 | describe('HomeSubmitComponent', () => { 9 | let component: HomeSubmitComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomeSubmitComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomeSubmitComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/modules/home/home-submit/home-submit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home-submit', 5 | templateUrl: './home-submit.component.html', 6 | styleUrls: ['./home-submit.component.scss'] 7 | }) 8 | export class HomeSubmitComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/modules/home/home-verify/home-verify.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 |
12 |
13 |

自定义弹层

14 |
15 |
16 |
17 | 这是自定义弹层内容,呵呵哒22222 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/app/modules/home/home-verify/home-verify.component.scss: -------------------------------------------------------------------------------- 1 | // 设计稿宽度1080,基准值为 1080/10 2 | @function px2rem($pixels, $context: 108) { 3 | @return ($pixels / $context) * 1rem; 4 | } 5 | 6 | @mixin font-dpr($font-size){ 7 | font-size: $font-size; 8 | 9 | [data-dpr="2"] & { 10 | font-size: $font-size * 2; 11 | } 12 | 13 | [data-dpr="3"] & { 14 | font-size: $font-size * 3; 15 | } 16 | } 17 | 18 | 19 | .container { 20 | .row-control { 21 | .sms-control { 22 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 23 | 24 | &.error { 25 | border-bottom: 0; 26 | } 27 | } 28 | } 29 | 30 | // 发送验证码 31 | .btn-send-sms { 32 | position: absolute; 33 | z-index: 3; 34 | outline: none; 35 | @include font-dpr(14px); 36 | color: #18BD91; 37 | right: 0; 38 | top: px2rem(26); 39 | padding: 0 px2rem(52); 40 | height: px2rem(112); 41 | line-height: px2rem(114); 42 | text-align: center; 43 | border: 1px solid #18BD91; 44 | border-radius: px2rem(60); 45 | cursor: pointer; 46 | 47 | &.timing{ 48 | color: #ccc; 49 | border-color: #ccc; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/modules/home/home-verify/home-verify.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomeVerifyComponent } from './home-verify.component'; 7 | 8 | describe('HomeVerifyComponent', () => { 9 | let component: HomeVerifyComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomeVerifyComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomeVerifyComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/modules/home/home-verify/home-verify.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | import { ModalService } from '../../../shared/modal'; 4 | 5 | @Component({ 6 | selector: 'app-home-verify', 7 | templateUrl: './home-verify.component.html', 8 | styleUrls: ['./home-verify.component.scss'], 9 | encapsulation: ViewEncapsulation.None 10 | }) 11 | export class HomeVerifyComponent implements OnInit { 12 | username: string = 'wrf'; 13 | modalId: string; 14 | 15 | constructor(private modalService: ModalService) { 16 | this.modalId = +new Date() + ''; 17 | } 18 | 19 | ngOnInit() { 20 | } 21 | 22 | onSubmit() { 23 | // this.router.navigate(['../submit'], { relativeTo: this.activateRoute}); 24 | console.log('home 提交表单'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/modules/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/modules/home/home.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/modules/home/home.component.scss -------------------------------------------------------------------------------- /src/app/modules/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomeComponent } from './home.component'; 7 | 8 | describe('HomeComponent', () => { 9 | let component: HomeComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/modules/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.scss'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/modules/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { HomeComponent } from './home.component'; 6 | import { HomeVerifyComponent } from './home-verify/home-verify.component'; 7 | import { HomeSubmitComponent } from './home-submit/home-submit.component'; 8 | import { HomeRoutingModule } from "./home-routing.module"; 9 | 10 | @NgModule({ 11 | imports: [ 12 | SharedModule, 13 | HomeRoutingModule 14 | ], 15 | declarations: [HomeComponent, HomeVerifyComponent, HomeSubmitComponent] 16 | }) 17 | export class HomeModule { 18 | } 19 | -------------------------------------------------------------------------------- /src/app/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './delivery/delivery.module'; 2 | export * from "./home/home.module"; 3 | export * from "./order/order.module"; 4 | -------------------------------------------------------------------------------- /src/app/modules/order/order-address/order-address.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 |

预约时间选择次日的三天内,如当天1月1号,次日1月2号,只能预约1月2号到1月4号的时间段

19 |
20 | 下一步 21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/modules/order/order-address/order-address.component.scss: -------------------------------------------------------------------------------- 1 | @function px2rem($pixels, $context: 108) { 2 | @return ($pixels / $context) * 1rem; 3 | } 4 | 5 | .content { 6 | background-color: #F5F5F5; 7 | 8 | .row-control { 9 | background-color: #fff; 10 | margin-bottom: 0.2657rem; 11 | } 12 | 13 | // 默认三个就近地址 14 | .address-choice { 15 | padding: px2rem(60) px2rem(36); 16 | 17 | .icon-checked { 18 | visibility: hidden; 19 | right: px2rem(36); 20 | background: url('/assets/global/images/icon-selected.png?__inline'); 21 | width: px2rem(58); 22 | height: px2rem(42); 23 | top: 40%; 24 | background-size: px2rem(58) px2rem(42); 25 | } 26 | 27 | &.selected { 28 | .icon-checked { 29 | visibility: visible; 30 | } 31 | } 32 | 33 | .icon-location { 34 | left: 0; 35 | background: url('/assets/global/images/icon-location.svg?__inline'); 36 | width: px2rem(40); 37 | height: px2rem(44); 38 | background-size: px2rem(44) px2rem(44); 39 | } 40 | 41 | .site-name, 42 | .address-detail { 43 | position: relative; 44 | line-height: 1.4; 45 | } 46 | 47 | .site-name { 48 | margin-bottom: px2rem(30); 49 | } 50 | 51 | .address-detail { 52 | font-size: 14px; 53 | padding-left: px2rem(50); 54 | 55 | span{ 56 | opacity: .5; 57 | } 58 | } 59 | } 60 | } 61 | 62 | // 预约时间段弹层样式 63 | .order-time-template { 64 | .row-control { 65 | .row-wrapper { 66 | border-bottom: 1px solid rgba(0,0,0, .05); 67 | } 68 | 69 | .title { 70 | position: absolute; 71 | top: 50%; 72 | transform: translateY(-50%); 73 | width: px2rem(218); 74 | text-align: center; 75 | line-height: 1.6; 76 | font-size: 16px; 77 | } 78 | 79 | .date { 80 | opacity: .5; 81 | } 82 | 83 | .label-control { 84 | margin-left: px2rem(218); 85 | 86 | .tips { 87 | float: right; 88 | margin-right: px2rem(36); 89 | } 90 | } 91 | 92 | .order-full { 93 | opacity: .5; 94 | } 95 | } 96 | 97 | .row-control:first-child { 98 | .row-wrapper { 99 | border-top: 1px solid rgba(0,0,0, .05); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/app/modules/order/order-address/order-address.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-order-address', 5 | templateUrl: './order-address.component.html', 6 | styleUrls: ['./order-address.component.scss'] 7 | }) 8 | export class OrderAddressComponent implements OnInit { 9 | 10 | constructor() { 11 | } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/app/modules/order/order-detail/order-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 | 8 | 9 | 10 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 51 |
52 |
53 | 54 | 55 | 56 | 63 |
64 |
65 | 66 | 67 | 68 | 75 |
76 |
77 | 78 | 79 | 80 | 87 |
88 |
89 | 上一步 90 | 确认申请 91 |
92 |
93 |
94 |
95 |
96 | -------------------------------------------------------------------------------- /src/app/modules/order/order-detail/order-detail.component.scss: -------------------------------------------------------------------------------- 1 | @function px2rem($pixels, $context: 108) { 2 | @return ($pixels / $context) * 1rem; 3 | } 4 | 5 | .container { 6 | .content { 7 | background-color: #F5F5F5; 8 | } 9 | 10 | .row-control { 11 | background-color: #fff; 12 | margin-bottom: 0.2657rem; 13 | 14 | &:last-child { 15 | margin-bottom: 0; 16 | } 17 | } 18 | 19 | .btn-control { 20 | overflow: hidden; 21 | 22 | .button { 23 | width: px2rem(396); 24 | 25 | &:nth-child(1) { 26 | float: left; 27 | } 28 | 29 | &:nth-child(2) { 30 | float: right; 31 | } 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/app/modules/order/order-detail/order-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, OnInit, HostBinding, 3 | trigger, transition, animate, 4 | style, state 5 | } from '@angular/core'; 6 | 7 | @Component({ 8 | selector: 'app-order-detail', 9 | templateUrl: './order-detail.component.html', 10 | styleUrls: ['./order-detail.component.scss'] 11 | }) 12 | export class OrderDetailComponent implements OnInit { 13 | 14 | constructor() { 15 | } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/app/modules/order/order-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { OrderComponent } from "./order.component"; 5 | import { OrderAddressComponent } from "./order-address/order-address.component"; 6 | import { OrderDetailComponent } from "./order-detail/order-detail.component"; 7 | import { OrderVerifyComponent } from "./order-verify/order-verify.component"; 8 | import { OrderService } from "./order.service"; 9 | 10 | export const routes: Routes = [ 11 | { 12 | path: '', 13 | component: OrderComponent, 14 | data: { title: '预约快修' }, 15 | children: [ 16 | { path: 'verify', component: OrderVerifyComponent, data: { title: '基础信息' } }, 17 | { path: 'address', component: OrderAddressComponent, data: { title: '地址信息' } }, 18 | { path: 'detail', component: OrderDetailComponent, data: { title: '维修信息' } }, 19 | { path: '', redirectTo: 'verify', pathMatch: 'full' }, 20 | { path: '**', redirectTo: 'verify' } 21 | ] 22 | } 23 | ] 24 | 25 | @NgModule({ 26 | imports: [RouterModule.forChild(routes)], 27 | exports: [RouterModule] 28 | }) 29 | export class OrderRoutingModule { 30 | } 31 | -------------------------------------------------------------------------------- /src/app/modules/order/order-verify/order-verify.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/modules/order/order-verify/order-verify.component.scss: -------------------------------------------------------------------------------- 1 | // 设计稿宽度1080,基准值为 1080/10 2 | @function px2rem($pixels, $context: 108) { 3 | @return ($pixels / $context) * 1rem; 4 | } 5 | 6 | @mixin font-dpr($font-size){ 7 | font-size: $font-size; 8 | 9 | [data-dpr="2"] & { 10 | font-size: $font-size * 2; 11 | } 12 | 13 | [data-dpr="3"] & { 14 | font-size: $font-size * 3; 15 | } 16 | } 17 | 18 | 19 | .container { 20 | .row-control { 21 | .sms-control { 22 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 23 | 24 | &.error { 25 | border-bottom: 0; 26 | } 27 | } 28 | } 29 | 30 | // 发送验证码 31 | .btn-send-sms { 32 | position: absolute; 33 | z-index: 3; 34 | outline: none; 35 | @include font-dpr(14px); 36 | color: #18BD91; 37 | right: 0; 38 | top: px2rem(26); 39 | padding: 0 px2rem(52); 40 | height: px2rem(112); 41 | line-height: px2rem(114); 42 | text-align: center; 43 | border: 1px solid #18BD91; 44 | border-radius: px2rem(60); 45 | cursor: pointer; 46 | 47 | &.timing{ 48 | color: #ccc; 49 | border-color: #ccc; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/modules/order/order-verify/order-verify.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-order-verify', 5 | templateUrl: './order-verify.component.html', 6 | styleUrls: ['./order-verify.component.scss'] 7 | 8 | }) 9 | export class OrderVerifyComponent implements OnInit { 10 | 11 | constructor() { 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/modules/order/order.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/modules/order/order.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/modules/order/order.component.scss -------------------------------------------------------------------------------- /src/app/modules/order/order.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, OnInit 3 | } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'app-order', 7 | templateUrl: './order.component.html', 8 | styleUrls: ['./order.component.scss'] 9 | }) 10 | export class OrderComponent implements OnInit { 11 | 12 | constructor() { 13 | } 14 | 15 | ngOnInit() { 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/modules/order/order.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { SharedModule } from '../../shared/shared.module'; 3 | 4 | import { OrderRoutingModule } from "./order-routing.module"; 5 | import { OrderComponent } from './order.component'; 6 | import { OrderAddressComponent } from './order-address/order-address.component'; 7 | import { OrderDetailComponent } from './order-detail/order-detail.component'; 8 | import { OrderVerifyComponent } from './order-verify/order-verify.component'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | SharedModule, 13 | OrderRoutingModule 14 | ], 15 | declarations: [ 16 | OrderComponent, 17 | OrderAddressComponent, 18 | OrderDetailComponent, 19 | OrderVerifyComponent 20 | ] 21 | }) 22 | export class OrderModule { 23 | } 24 | -------------------------------------------------------------------------------- /src/app/modules/order/order.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate } from "@angular/router"; 3 | 4 | @Injectable() 5 | export class OrderService implements CanActivate { 6 | 7 | canActivate() { 8 | console.log('验证用户名,手机号,短信验证码'); 9 | return true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validators.service'; 2 | -------------------------------------------------------------------------------- /src/app/services/validators.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | 4 | @Injectable() 5 | export class ValidatorsService { 6 | 7 | static mobileReg = /^1[3|4|5|6|7|8]\d{9}/; 8 | 9 | constructor() { } 10 | 11 | static mobileValidator(c: FormControl) { 12 | return ValidatorsService.mobileReg.test(c.value) ? null : { 13 | mobile: { 14 | invalid: true 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/bubble/bubble.component.html: -------------------------------------------------------------------------------- 1 |
{{tips}}
2 | -------------------------------------------------------------------------------- /src/app/shared/bubble/bubble.component.scss: -------------------------------------------------------------------------------- 1 | $baseColor: rgba(0, 0, 0, .5); 2 | $greenColor: #18BD91; 3 | 4 | .bubble { 5 | position:relative; 6 | width: 100px; 7 | margin: 20px auto; 8 | text-align: center; 9 | border: 1px solid $baseColor; 10 | background-color: #fff; 11 | border-radius: 4px; 12 | padding: 10px; 13 | 14 | &::after, 15 | &::before { 16 | position: absolute; 17 | content: ''; 18 | width: 0; 19 | height: 0; 20 | border:8px solid $baseColor; 21 | border-left-color: transparent; 22 | border-bottom-color: transparent; 23 | } 24 | 25 | &::after { 26 | border-width: 7px; 27 | border-color: #fff; 28 | border-left-color: transparent; 29 | border-bottom-color: transparent; 30 | } 31 | 32 | &.left-top::after, 33 | &.left-top::before { 34 | left: 0; 35 | top: 10px; 36 | transform: translateX(-100%); 37 | } 38 | 39 | &.left-top::after { 40 | top: 11px; 41 | } 42 | 43 | &.bubble-green { 44 | border-color: $greenColor; 45 | 46 | &::before { 47 | border-top-color: $greenColor; 48 | border-right-color: $greenColor; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/app/shared/bubble/bubble.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-bubble', 5 | templateUrl: './bubble.component.html', 6 | styleUrls: ['./bubble.component.scss'] 7 | }) 8 | export class BubbleComponent implements OnInit { 9 | 10 | @Input() clazz: string; 11 | @Input() tips: string = '这是气泡文字'; 12 | 13 | constructor() { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/app/shared/cascade-list/cascade-list.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 4 | {{list[key].name}} 5 |
  • 6 |
7 |
8 | -------------------------------------------------------------------------------- /src/app/shared/cascade-list/cascade-list.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/shared/cascade-list/cascade-list.component.scss -------------------------------------------------------------------------------- /src/app/shared/cascade-list/cascade-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { CascadeListComponent } from './cascade-list.component'; 7 | 8 | describe('CascadeListComponent', () => { 9 | let component: CascadeListComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ CascadeListComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(CascadeListComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/shared/cascade-list/cascade-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Observable, Observer } from 'rxjs'; 3 | 4 | @Component({ 5 | selector: 'cascade-list', 6 | templateUrl: './cascade-list.component.html', 7 | styleUrls: ['./cascade-list.component.scss'] 8 | }) 9 | export class CascadeListComponent implements OnInit { 10 | 11 | data: Array = []; 12 | formateFn: Function; 13 | 14 | // 当前数据,以数组的形式表现,数组下表表示层级 15 | currentData: Array = []; 16 | currentData$: Observable>; 17 | /** 18 | * data: 初始数据源,需要自己格式化为符合数据格式规范,或者传入一个格式化函数 19 | * 列表基础属性: id, name, children: {}, pid 父级 id, 当前层级 level 20 | * 在每次的选择回调中,对外暴露接口:供用户选择操作,比如这个时候可以动态的获取下级数据,并设置实例对象的数据 21 | * 用一个数组来表示当前展示的数据 22 | */ 23 | 24 | constructor() { 25 | 26 | } 27 | 28 | ngOnInit() { 29 | console.log('init'); 30 | this.generateData(17); 31 | } 32 | 33 | // 传递多级默认选中的 id 值, ids [17, 181] 代表一级广东省,二级深圳市 34 | private generateData(...ids) { 35 | // 一级列表默认数据父级 id 为 0,所以这里去 data[0][0]; 36 | this.currentData[0] = this.data[0][0]; 37 | for (var i = 0; i < ids.length; i++) { 38 | // 数据从第二项算起,第一项默认为全部数据 39 | this.currentData[i + 1] = this.data[i + 1][ids[i]]; 40 | } 41 | 42 | console.log(this.currentData); 43 | } 44 | 45 | testAsync() { 46 | this.currentData$ = Observable.create(observer => { 47 | observer.next(this.currentData); 48 | }) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/app/shared/components/countdown/countdown.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/shared/components/countdown/countdown.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/shared/components/countdown/countdown.component.scss -------------------------------------------------------------------------------- /src/app/shared/components/countdown/countdown.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { CountdownComponent } from './countdown.component'; 7 | 8 | describe('CountdownComponent', () => { 9 | let component: CountdownComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ CountdownComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(CountdownComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/app/shared/components/countdown/countdown.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; 2 | import { Observable } from "rxjs"; 3 | 4 | @Component({ 5 | selector: 'count-down', 6 | templateUrl: './countdown.component.html', 7 | styleUrls: ['./countdown.component.scss'], 8 | host: { 9 | '[class.timing]': 'timing' 10 | }, 11 | encapsulation: ViewEncapsulation.None 12 | }) 13 | export class CountdownComponent implements OnInit { 14 | private timing: boolean = false; 15 | private _clickEmitter: EventEmitter; 16 | private text: string; 17 | 18 | @Input() counter: number = 60; 19 | @Input() title: string; 20 | @Input() countText: string; 21 | @Input() mobile: string; 22 | 23 | constructor() { 24 | this._clickEmitter = new EventEmitter(); 25 | } 26 | 27 | ngOnInit() { 28 | this.text = this.title; 29 | } 30 | 31 | _handleClick(event) { 32 | if (this.timing) { 33 | console.log('it\'s timing, stop count again'); 34 | return false; 35 | } else { 36 | this._handleCount(); 37 | } 38 | } 39 | 40 | @Output('click') 41 | get onClick(): Observable { 42 | return this._clickEmitter.asObservable(); 43 | } 44 | 45 | _counter(count) { 46 | let t; 47 | if (count == 0) { 48 | this.text = this.title; 49 | this.timing = false; 50 | clearTimeout(t); 51 | } else { 52 | count--; 53 | this.text = count + this.countText; 54 | t = setTimeout(() => { 55 | this._counter(count); 56 | }, 1000); 57 | } 58 | } 59 | 60 | _handleCount() { 61 | let time = this.counter; 62 | this.timing = true; 63 | this._counter(time); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/shared/components/input-control/input-control.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/shared/components/input-control/input-control.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/shared/components/input-control/input-control.component.scss -------------------------------------------------------------------------------- /src/app/shared/components/input-control/input-control.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | forwardRef, 3 | Component, 4 | HostListener, 5 | Input, 6 | ViewChild, 7 | ElementRef, 8 | EventEmitter, 9 | Output, 10 | ViewEncapsulation 11 | } from '@angular/core'; 12 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; 13 | import { Observable } from 'rxjs/Observable'; 14 | 15 | // 要实现双向数据绑定,这个不可少 16 | export const INPUT_CONTROL_VALUE_ACCESSOR: any = { 17 | provide: NG_VALUE_ACCESSOR, 18 | useExisting: forwardRef(() => InputControlComponent), 19 | multi: true 20 | }; 21 | 22 | const noop = () => { 23 | }; 24 | 25 | @Component({ 26 | selector: 'input-control', 27 | templateUrl: './input-control.component.html', 28 | styleUrls: ['./input-control.component.scss'], 29 | host: { 30 | // 宿主元素 click 事件,触发 focus() 事件 31 | '(click)': 'focus()', 32 | // 切换宿主元素 focus 样式 33 | '[class.focus]': 'focused', 34 | 'class': 'input-control' 35 | }, 36 | encapsulation: ViewEncapsulation.None, 37 | providers: [INPUT_CONTROL_VALUE_ACCESSOR] 38 | }) 39 | export class InputControlComponent implements ControlValueAccessor { 40 | private _focused: boolean = false; 41 | private _value: any = ''; 42 | private _disabled: boolean = false; 43 | private _readonly: boolean = false; 44 | private _required: boolean = false; 45 | 46 | /** Callback registered via registerOnTouched (ControlValueAccessor) 47 | * 此属性在做表单校验的时候,不可少, 48 | * 如果缺少了这个属性,FormControl.touched 属性将监测不到,切记!! 49 | */ 50 | private _onTouchedCallback: () => void = noop; 51 | /** Callback registered via registerOnChange (ControlValueAccessor) */ 52 | private _onChangeCallback: (_: any) => void = noop; 53 | 54 | private _focusEmitter: EventEmitter = new EventEmitter(); 55 | private _blurEmitter: EventEmitter = new EventEmitter(); 56 | private _inputChangeEmitter: EventEmitter = new EventEmitter(); 57 | 58 | // 外部传入属性 59 | @Input() type: string = 'text'; 60 | @Input() name: string = null; 61 | @Input() placeholder: string = null; 62 | @Input() minlength: number; 63 | @Input() maxlength: number; 64 | 65 | @Input() 66 | get disabled(): boolean { 67 | return this._disabled; 68 | } 69 | set disabled(value) { 70 | this._disabled = this._coerceBooleanProperty(value); 71 | } 72 | 73 | @Input() 74 | get readonly(): boolean { 75 | return this._readonly; 76 | } 77 | set readonly(value) { 78 | this._readonly = this._coerceBooleanProperty(value); 79 | } 80 | 81 | @Input() 82 | get required(): boolean { 83 | return this._required; 84 | } 85 | set required(value) { 86 | this._required = this._coerceBooleanProperty(value); 87 | } 88 | 89 | @ViewChild('input') _inputElement: ElementRef; 90 | @ViewChild('iconDelete') iconDelete: ElementRef; 91 | 92 | constructor(private hostRef: ElementRef) { 93 | } 94 | 95 | ngOnInit() { 96 | } 97 | 98 | // 监听全局的点击事件,如果不是当前 input-control 组,则视为失去焦点操作 99 | @HostListener('window:click', ['$event']) 100 | inputControlBlurHandler(event) { 101 | var parent = event.target; 102 | // 如何当前节点不是宿主节点,并且不等于 document 节点 103 | while (parent && parent != this.hostRef.nativeElement && parent != document) { 104 | // 取当前节点的父节点继续寻找 105 | parent = parent.parentNode; 106 | } 107 | 108 | // 找到最顶层,则表示已经不在宿主元素内部了,触发失去焦点 fn 109 | if (parent == document) { 110 | this._focused = false; 111 | } 112 | } 113 | 114 | // 只读属性 115 | get focused() { 116 | return this._focused; 117 | } 118 | 119 | // value 属性,以 get 方式拦截 120 | get value(): any { 121 | return this._value; 122 | }; 123 | 124 | @Input() set value(v: any) { 125 | v = this._convertValueForInputType(v); 126 | if (v !== this._value) { 127 | this._value = v; 128 | // 触发值改变事件,冒泡给父级 129 | this._onChangeCallback(v); 130 | } 131 | } 132 | 133 | // 对外暴露 focus 事件 134 | @Output('focus') onFocus = this._focusEmitter.asObservable(); 135 | 136 | // 宿主聚焦 137 | focus() { 138 | // 触发下面的 _handleFocus() 事件 139 | this._inputElement.nativeElement.focus(); 140 | } 141 | 142 | // 输入框聚焦 143 | _handleFocus(event: FocusEvent) { 144 | this._focused = true; 145 | this._focusEmitter.emit(event); 146 | } 147 | 148 | // 这里触发 blur 操作,但是不改变 this._focused 的值, 149 | // 不然删除图标无法实现它的功能, 150 | //设置 this._focused 的值将由上面的 @HostListener('window:click', ['$event']) 来处理 151 | _handleBlur(event: any) { 152 | this._onTouchedCallback(); 153 | this._blurEmitter.emit(event); 154 | } 155 | 156 | // 清空输入值 157 | _handleClear() { 158 | this.value = ''; 159 | return false; 160 | } 161 | 162 | private _convertValueForInputType(v: any): any { 163 | switch (this.type) { 164 | case 'number': 165 | return parseFloat(v); 166 | default: 167 | return v; 168 | } 169 | } 170 | 171 | private _coerceBooleanProperty(value: any): boolean { 172 | return value != null && `${value}` !== 'false'; 173 | } 174 | 175 | /** 176 | * Write a new value to the element. 177 | */ 178 | writeValue(value: any) { 179 | this._value = value; 180 | } 181 | 182 | /** 183 | * Set the function to be called when the control receives a change event. 184 | */ 185 | registerOnChange(fn: any) { 186 | this._onChangeCallback = fn; 187 | }; 188 | 189 | /** 190 | * Set the function to be called when the control receives a touch event. 191 | */ 192 | registerOnTouched(fn: any) { 193 | this._onTouchedCallback = fn; 194 | } 195 | } 196 | 197 | -------------------------------------------------------------------------------- /src/app/shared/components/verify-form/verify-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 9 |

请输入您的姓名

10 |
11 | 18 |

请输入正确的手机号码

19 |
20 | 27 | 28 |

请输入验证码

29 |
30 |
31 |
32 | 33 |
34 |
35 | 重置表单 36 |
37 |
38 | -------------------------------------------------------------------------------- /src/app/shared/components/verify-form/verify-form.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../../../assets/global/_function"; 2 | @import "../../../../assets/global/_mixin"; 3 | @import "../../../../assets/global/_variables"; 4 | 5 | // 发送验证码 6 | .btn-send-sms { 7 | position: absolute; 8 | z-index: 3; 9 | outline: none; 10 | @include font-dpr(14px); 11 | color: $mainColor; 12 | right: 0; 13 | top: px2rem(26); 14 | padding: 0 px2rem(52); 15 | height: px2rem(112); 16 | line-height: px2rem(112); 17 | text-align: center; 18 | border: 1px solid $mainColor; 19 | border-radius: px2rem(60); 20 | cursor: pointer; 21 | 22 | button { 23 | color: $mainColor; 24 | border:0; 25 | background-color: #fff; 26 | } 27 | 28 | &.timing { 29 | border-color: rgba(0,0,0, .3); 30 | 31 | button { 32 | color: rgba(0,0,0, .3); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/shared/components/verify-form/verify-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, OnInit, ViewEncapsulation, EventEmitter, Inject } from '@angular/core'; 2 | import { FormGroup, AbstractControl, FormBuilder, Validators } from '@angular/forms'; 3 | 4 | import { ServiceSupportService } from '../../../core/service-support.service'; 5 | import { ValidatorsService } from '../../../services/validators.service'; 6 | 7 | @Component({ 8 | selector: 'app-verify-form', 9 | templateUrl: './verify-form.component.html', 10 | styleUrls: ['./verify-form.component.scss'], 11 | encapsulation: ViewEncapsulation.None 12 | }) 13 | export class VerifyFormComponent implements OnInit { 14 | formActive: boolean = true; 15 | color: string = 'green'; 16 | verifyFormGroup: FormGroup; 17 | username: AbstractControl; 18 | mobile: AbstractControl; 19 | captcha: AbstractControl; 20 | 21 | // 本地缓存数据 22 | localData: any; 23 | 24 | // 对外暴露提交事件 25 | @Output() submit: EventEmitter = new EventEmitter(); 26 | 27 | constructor( 28 | @Inject('ConstConfig') private constConfig: any, 29 | private formBuilder: FormBuilder, 30 | private serviceSupport: ServiceSupportService) { 31 | } 32 | 33 | ngOnInit() { 34 | if (localStorage.getItem(this.constConfig.RepairBaseInfoKey)) { 35 | this.localData = JSON.parse(localStorage.getItem(this.constConfig.RepairBaseInfoKey)); 36 | } else { 37 | this.localData = { 38 | username: '张三', 39 | mobile: '13602532846' 40 | }; 41 | } 42 | this.initForm(); 43 | } 44 | 45 | initForm() { 46 | this.verifyFormGroup = this.formBuilder.group({ 47 | 'username': [this.localData.username, Validators.required], 48 | 'mobile': [this.localData.mobile, ValidatorsService.mobileValidator], 49 | 'captcha': ['', Validators.required] 50 | }); 51 | 52 | this.username = this.verifyFormGroup.controls['username']; 53 | this.mobile = this.verifyFormGroup.controls['mobile']; 54 | this.captcha = this.verifyFormGroup.controls['captcha']; 55 | 56 | this.serviceSupport.getLoaction().subscribe((data) => { 57 | console.log('当前地址:' + data.province + data.city); 58 | }, (error) => { 59 | console.log('获取当前地址失败:' + error); 60 | }); 61 | } 62 | 63 | onSubmit(event) { 64 | event.preventDefault(); 65 | window.localStorage.setItem(this.constConfig.RepairBaseInfoKey, JSON.stringify({ 66 | username: this.username.value, 67 | mobile: this.mobile.value, 68 | captcha: this.captcha.value 69 | })); 70 | // 校验短信验证码 71 | this.serviceSupport.checkSms({ 72 | phone: this.mobile.value, 73 | captcha: this.captcha.value 74 | }).subscribe(success => { 75 | if (success) { 76 | // 保存本地数据 77 | window.localStorage.setItem(this.constConfig.RepairBaseInfoKey, JSON.stringify({ 78 | username: this.username.value, 79 | mobile: this.mobile.value, 80 | captcha: this.captcha.value 81 | })); 82 | this.submit.emit(); 83 | } else { 84 | alert('短信验证码错误,请重新输入'); 85 | } 86 | }, (error) => { 87 | console.log('服务器繁忙,请稍后重试!', error); 88 | }); 89 | return false; 90 | } 91 | 92 | // 重置表单小技巧 93 | resetForm() { 94 | this.formActive = false; 95 | this.initForm(); 96 | setTimeout(() => this.formActive = true, 0); 97 | } 98 | 99 | sendSms() { 100 | console.log('发送短信验证码'); 101 | this.serviceSupport.sendSms(this.mobile.value).subscribe((data) => { 102 | console.dir('发送短信成功:' + data.code); 103 | }, (error) => { 104 | console.log('获取短信失败:' + error); 105 | }); 106 | return false; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/app/shared/directives/highlight.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostListener, Input, Renderer } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[highlight]' 5 | }) 6 | export class HighlightDirective { 7 | private _defaultColor = 'red'; 8 | 9 | constructor(private el: ElementRef, private renderer: Renderer) { 10 | } 11 | 12 | @Input() set defaultColor(colorName: string) { 13 | this._defaultColor = colorName || this._defaultColor; 14 | } 15 | 16 | @Input('highlight') highlightColor: string; 17 | 18 | @HostListener('mouseenter') onMouseEnter() { 19 | this.highlight(this.highlightColor || this._defaultColor); 20 | } 21 | 22 | @HostListener('mouseleave') onMouseLeave() { 23 | this.highlight(null); 24 | } 25 | 26 | private highlight(color: string) { 27 | this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/shared/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from './highlight.directive'; 2 | -------------------------------------------------------------------------------- /src/app/shared/modal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './modal.component'; 2 | export * from './modal.service'; 3 | -------------------------------------------------------------------------------- /src/app/shared/modal/modal.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | -------------------------------------------------------------------------------- /src/app/shared/modal/modal.component.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | @import "../../../assets/global/_function"; 3 | @import "../../../assets/global/_mixin"; 4 | 5 | .no-scroll { 6 | height: 100%; 7 | overflow: hidden; 8 | } 9 | 10 | .modal-mask { 11 | position: fixed; 12 | top: 0; 13 | left: 0; 14 | right: 0; 15 | bottom: 0; 16 | z-index: 9998; 17 | overflow: hidden; 18 | outline: 0; 19 | background-color: rgba(0, 0, 0, .2); 20 | } 21 | 22 | .modal-dialog { 23 | box-sizing: border-box; 24 | position: absolute; 25 | top: 50%; 26 | left: 50%; 27 | width: 100%; 28 | z-index: 9999; 29 | padding: 0 px2rem(54); 30 | color: #262626; 31 | font-size: px2rem(36); 32 | transform: translate3d(-50%, -50%, 0); 33 | 34 | .modal-content { 35 | position: relative; 36 | overflow: hidden; 37 | background-color: #fff; 38 | } 39 | 40 | .modal-header { 41 | position: absolute; 42 | top: 0; 43 | left: 0; 44 | width: 100%; 45 | box-sizing: border-box; 46 | border-bottom: 1px solid rgba(0, 0, 0, .05); 47 | padding: 0 px2rem(54); 48 | overflow: hidden; 49 | z-index: 999; 50 | 51 | .row-control { 52 | background-color: #fff; 53 | } 54 | 55 | .title { 56 | line-height: 1; 57 | position: relative; 58 | overflow: hidden; 59 | @include font-dpr(18px); 60 | text-align: center; 61 | padding: px2rem(60) 0; 62 | } 63 | 64 | } 65 | 66 | .modal-body { 67 | padding: px2rem(170) px2rem(36) px2rem(130); 68 | line-height: 1.6; 69 | max-height: px2rem(1280); 70 | overflow: hidden; 71 | overflow-y: scroll; 72 | z-index: 998; 73 | } 74 | 75 | .modal-footer { 76 | width: 100%; 77 | vertical-align: middle; 78 | overflow: hidden; 79 | position: absolute; 80 | bottom: 0; 81 | box-sizing: border-box; 82 | @include font-dpr(16px); 83 | z-index: 999; 84 | border-top: 1px solid rgba(0, 0, 0, .05); 85 | 86 | .button { 87 | float: left; 88 | width: 50%; 89 | text-align: center; 90 | box-sizing: border-box; 91 | padding: px2rem(36) 0; 92 | @include font-dpr(16px); 93 | background-color: #fff; 94 | border-right: 1px solid rgba(0, 0, 0, .05); 95 | 96 | &:last-child { 97 | border-right: 0; 98 | } 99 | 100 | &:hover, 101 | &:focus { 102 | background-image: none; 103 | outline: none; 104 | } 105 | 106 | &.primary { 107 | color: #18BD91; 108 | } 109 | 110 | &.cancel { 111 | color: #262626; 112 | } 113 | 114 | &.full-fill { 115 | width: 100%; 116 | } 117 | } 118 | 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/app/shared/modal/modal.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ComponentRef, 4 | OnInit, 5 | Input, 6 | Output, 7 | ViewEncapsulation, 8 | HostListener, 9 | ElementRef, 10 | ViewChild, 11 | Injector, 12 | ViewContainerRef, 13 | TemplateRef, 14 | ComponentFactoryResolver, 15 | AfterViewInit, 16 | EventEmitter, 17 | } from '@angular/core'; 18 | 19 | import { ModalService } from './modal.service'; 20 | 21 | @Component({ 22 | selector: 'mcare-modal', 23 | templateUrl: './modal.component.html', 24 | styleUrls: ['./modal.component.scss'], 25 | host: { 26 | '(document:keyup)': 'keyup($event)', 27 | '(document:click)': 'handOutSideClick($event)' 28 | }, 29 | encapsulation: ViewEncapsulation.None, 30 | }) 31 | export class ModalComponent implements OnInit, AfterViewInit { 32 | 33 | isOpened: boolean = false; 34 | 35 | // ======================= 36 | // 输入属性 37 | // ======================= 38 | @Input() modalId: string; 39 | 40 | @Input() clazz: string; 41 | 42 | @Input() closeOnEscape: boolean = true; 43 | 44 | @Input() title: string = '标题'; 45 | 46 | @Input() cancelButtonLabel: string = '取消'; 47 | 48 | @Input() confirmButtonLabel: string = '确定'; 49 | 50 | // ======================= 51 | // 输出属性 52 | // ======================= 53 | 54 | @Output() onCancel: EventEmitter = new EventEmitter(); 55 | 56 | @Output() onConfirm: EventEmitter = new EventEmitter(false); 57 | 58 | // ======================= 59 | // 公共属性 60 | // ======================= 61 | 62 | @ViewChild('modalRoot') modalRoot: ElementRef; 63 | 64 | // ======================= 65 | // 私有属性 66 | // ======================= 67 | 68 | @ViewChild('modalBody', {read: ViewContainerRef}) public dynamicTarget: ViewContainerRef; 69 | 70 | constructor( 71 | public injector: Injector, 72 | private modalService: ModalService) { 73 | } 74 | 75 | ngOnInit(): void { 76 | this.modalService.registerModal(this); 77 | } 78 | 79 | ngOnDestroy() { 80 | } 81 | 82 | ngAfterViewInit() { 83 | } 84 | 85 | private close() { 86 | this.isOpened = false; 87 | } 88 | 89 | cancel() { 90 | this.onCancel.emit(); 91 | this.close(); 92 | } 93 | 94 | confirm() { 95 | this.onConfirm.emit(); 96 | this.modalService.confirm(); 97 | this.close(); 98 | } 99 | 100 | preventClosing(event: MouseEvent) { 101 | event.stopPropagation(); 102 | } 103 | 104 | keyup(event: KeyboardEvent) { 105 | if (event.keyCode === 27 && this.closeOnEscape) { 106 | this.isOpened = false; 107 | } 108 | } 109 | 110 | // 点击弹层主区域之外,关闭弹层 111 | handOutSideClick(event) { 112 | var parent = event.target; 113 | // 如何当前节点不是宿主节点,并且不等于 document 节点 114 | while (parent && this.modalRoot && parent != this.modalRoot.nativeElement && parent != document) { 115 | // 取当前节点的父节点继续寻找 116 | parent = parent.parentNode; 117 | } 118 | 119 | // 找到最顶层,则表示已经不在宿主元素内部了,触发失去焦点 fn 120 | if (parent == document) { 121 | this.isOpened = false; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/app/shared/modal/modal.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | ComponentRef, 4 | ComponentFactory, 5 | ComponentFactoryResolver, 6 | Component, 7 | ViewContainerRef, 8 | Compiler, 9 | ReflectiveInjector, 10 | } from '@angular/core'; 11 | import { Observable, ReplaySubject } from 'rxjs'; 12 | 13 | import { ModalComponent } from './modal.component'; 14 | 15 | @Injectable() 16 | export class ModalService { 17 | 18 | // 弹层实例对象 19 | private modal: ModalComponent; 20 | private componentRef: ComponentRef; 21 | // private viewContainerRef: ViewContainerRef; 22 | componentRef$: ReplaySubject>; 23 | 24 | constructor( 25 | private compiler: Compiler, 26 | private componentFactoryResolver: ComponentFactoryResolver) { } 27 | 28 | registerModal(newModal: ModalComponent): void { 29 | this.modal = newModal; 30 | } 31 | 32 | /** 33 | * module 模块名称, 比如 SharedModule 34 | * component 组件类型 35 | * params 参数 36 | * 动态编译模块,但是这样子做过不了 aot 打包模式 37 | */ 38 | // create(module: any, component: any, params?: Object): Observable> { 39 | // this.componentRef$ = new ReplaySubject>(); 40 | // if (this.componentRef) { 41 | // this.componentRef.destroy(); 42 | // } 43 | // // 动态创建和编译一个 Module/Component 44 | // this.compiler.compileModuleAndAllComponentsAsync(module).then(factory => { 45 | // let componentFactory = factory.componentFactories.filter(item => item.componentType === component)[0]; 46 | 47 | // const childInjector = ReflectiveInjector.resolveAndCreate([], this.modal.injector); 48 | // this.componentRef = this.modal.dynamicTarget.createComponent(componentFactory, 0, childInjector); 49 | // // 给动态生成的组件赋值 50 | // Object.assign(this.componentRef.instance, params); 51 | // this.componentRef$.next(this.componentRef); 52 | // this.modal.isOpened = true; 53 | // }); 54 | 55 | // return this.componentRef$; 56 | // } 57 | 58 | open(component: any, params?: Object): Observable> { 59 | if (this.componentRef) { 60 | this.componentRef.destroy(); 61 | } 62 | this.componentRef$ = new ReplaySubject>(); 63 | let factory = this.componentFactoryResolver.resolveComponentFactory(component); 64 | this.componentRef = this.modal.dynamicTarget.createComponent(factory); 65 | Object.assign(this.componentRef.instance, params); 66 | this.componentRef$.next(this.componentRef); 67 | this.modal.isOpened = true; 68 | return this.componentRef$; 69 | } 70 | 71 | close() { 72 | } 73 | 74 | confirm() { 75 | this.componentRef$.next(this.componentRef); 76 | this.componentRef$.complete(); 77 | this.componentRef$ = null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/shared/object-to-array.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { ObjectToArrayPipe } from './object-to-array.pipe'; 5 | 6 | describe('ObjectToArrayPipe', () => { 7 | it('create an instance', () => { 8 | let pipe = new ObjectToArrayPipe(); 9 | expect(pipe).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/shared/object-to-array.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'objectToArray' 5 | }) 6 | export class ObjectToArrayPipe implements PipeTransform { 7 | 8 | keys = []; 9 | 10 | transform(value: any, args?: any): any { 11 | if (Object.prototype.toString.call(value) !== '[object object]') { 12 | throw new Error('输入值不是对象类型'); 13 | } 14 | this.keys = Object.keys(value); 15 | 16 | return this.keys; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/shared/select-control/select-control.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/app/shared/select-control/select-control.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/app/shared/select-control/select-control.component.scss -------------------------------------------------------------------------------- /src/app/shared/select-control/select-control.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | Input, 5 | Output, 6 | ElementRef, 7 | EventEmitter, 8 | ViewEncapsulation, 9 | forwardRef, 10 | } from '@angular/core'; 11 | 12 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; 13 | import { Observable } from 'rxjs/Observable'; 14 | 15 | import { SelectControl } from './select-control'; 16 | 17 | @Component({ 18 | selector: 'select-control', 19 | templateUrl: './select-control.component.html', 20 | styleUrls: ['./select-control.component.scss'], 21 | encapsulation: ViewEncapsulation.None, 22 | host: { 23 | 'class': 'select-control' 24 | } 25 | }) 26 | export class SelectControlComponent implements OnInit { 27 | // ======= 输入属性 ======= 28 | @Input() name: string; 29 | @Input() label: string; 30 | @Input() data: SelectControl; 31 | 32 | constructor() { } 33 | 34 | ngOnInit() { 35 | if (!this.data) this.data = new SelectControl(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/shared/select-control/select-control.ts: -------------------------------------------------------------------------------- 1 | export class SelectControl { 2 | id: string; 3 | label: string; 4 | 5 | constructor(obj?: any) { 6 | this.id = obj.id || ''; 7 | this.label = obj.label || ''; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/shared/select-list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './select-list.component'; 2 | export * from './select-list.service'; 3 | export * from './select-list'; 4 | -------------------------------------------------------------------------------- /src/app/shared/select-list/select-list.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 3 | 4 |
  • 5 |
  • 6 | 7 |
  • 8 |
9 | -------------------------------------------------------------------------------- /src/app/shared/select-list/select-list.component.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | @import "../../../assets/global/_function"; 3 | @import "../../../assets/global/_mixin"; 4 | 5 | .service-list { 6 | @include font-dpr(16px); 7 | 8 | .list-item { 9 | position: relative; 10 | padding: px2rem(36); 11 | border-bottom: 1px solid rgba(0, 0, 0, .05); 12 | 13 | &.checked::after { 14 | position: absolute; 15 | content: ''; 16 | right: px2rem(36); 17 | top: 50%; 18 | transform: translateY(-50%); 19 | background: url('/assets/global/images/icon-selected.png') center no-repeat; 20 | background-size: px2rem(72); 21 | width: px2rem(72); 22 | height: px2rem(72); 23 | } 24 | 25 | &:last-child { 26 | border-bottom: 0; 27 | } 28 | } 29 | 30 | .item-remark { 31 | input { 32 | font-size: inherit; 33 | border: 0; 34 | outline: none; 35 | width: 100%; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/shared/select-list/select-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; 2 | import { SelectListService } from './select-list.service'; 3 | import { SelectList } from './select-list'; 4 | 5 | @Component({ 6 | selector: 'select-list', 7 | styleUrls: ['./select-list.component.scss'], 8 | templateUrl: './select-list.component.html', 9 | encapsulation: ViewEncapsulation.None, 10 | }) 11 | export class SelectListComponent implements OnInit { 12 | private _multiple: boolean = false; 13 | selected: Array = []; 14 | 15 | @Input() remark: string; 16 | @Input() listData: Array = []; 17 | // 默认显示类型 18 | @Input() default: Array = []; 19 | 20 | @Input() 21 | get multiple() { 22 | return this._multiple; 23 | } 24 | set multiple(value) { 25 | this._multiple = this._coerceBooleanProperty(value); 26 | } 27 | 28 | constructor(private selectListService: SelectListService) { 29 | } 30 | 31 | ngOnInit() { 32 | this.setCurrent(); 33 | } 34 | 35 | // 点击事件 36 | checked(event, clickItem) { 37 | event.stopPropagation(); 38 | 39 | // 至少保留一项选中项 40 | if (this.selected.length === 1 && clickItem.id === this.selected[0].id) return; 41 | 42 | // 单选操作 43 | if (!this._multiple) { 44 | this.selected.length = 0; 45 | this.listData.forEach(item => { 46 | item.isSelected = false; 47 | }); 48 | } 49 | 50 | // 判断是否取消选中 51 | if (clickItem.isSelected) { 52 | console.log(this.selected.indexOf(clickItem)); 53 | this.selected.splice(this.selected.indexOf(clickItem), 1); 54 | } else { 55 | this.selected.push(clickItem); 56 | } 57 | 58 | clickItem.isSelected = !clickItem.isSelected; 59 | } 60 | 61 | // 设置当前已经选中的项 62 | setCurrent() { 63 | this.listData.forEach(item => { 64 | let activeItem = []; 65 | // 如果是单选,则传递的参数取数组第一项 66 | if (!this._multiple) { 67 | // 单选 68 | activeItem.push(this.default[0]); 69 | } else { 70 | activeItem = this.default; 71 | } 72 | 73 | activeItem.forEach((actived) => { 74 | if (item.id === actived.id || item.label === actived.label) { 75 | item.isSelected = true; 76 | this.selected.push(item); 77 | } 78 | }); 79 | }); 80 | } 81 | 82 | private _coerceBooleanProperty(value: any): boolean { 83 | return value != null && `${value}` !== 'false'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/app/shared/select-list/select-list.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, Response } from '@angular/http'; 3 | import { SelectList } from './select-list'; 4 | import { Observable, Observer } from 'rxjs'; 5 | 6 | export const mockHttpGet = (): Observable> => { 7 | return Observable.create((observer: Observer>) => { 8 | const timmer = setTimeout(() => { 9 | const result = [{ 10 | id: 1, 11 | label: '维修', 12 | isSelected: false 13 | }, { 14 | id: 2, 15 | label: '换新', 16 | isSelected: false 17 | }]; 18 | 19 | observer.next(result); 20 | observer.complete(); 21 | }, Math.random()*1000 + 10); 22 | }); 23 | } 24 | 25 | @Injectable() 26 | export class SelectListService { 27 | 28 | listData: Array; 29 | // 选中项数组 30 | selected: Array; 31 | 32 | constructor() { } 33 | 34 | getListData(): Observable> { 35 | return Observable.create((observer: Observer>) => { 36 | if (this.listData) { 37 | observer.next(this.listData); 38 | } else { 39 | mockHttpGet().subscribe(data => { 40 | this.listData = data; 41 | observer.next(this.listData); 42 | }); 43 | } 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/shared/select-list/select-list.ts: -------------------------------------------------------------------------------- 1 | export class SelectList { 2 | id: number; 3 | label: string; 4 | isSelected: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf, } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { HighlightDirective } from './directives/'; 6 | import { InputControlComponent } from './components/input-control/input-control.component'; 7 | import { CountdownComponent } from './components/countdown/countdown.component'; 8 | import { VerifyFormComponent } from './components/verify-form/verify-form.component'; 9 | import { ModalComponent, ModalService } from './modal/'; 10 | import { SelectControlComponent } from './select-control/select-control.component'; 11 | import { SelectListComponent, SelectListService } from './select-list/'; 12 | import { BubbleComponent } from './bubble/bubble.component'; 13 | import { CascadeListComponent } from './cascade-list/cascade-list.component'; 14 | import { ObjectToArrayPipe } from './object-to-array.pipe'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | FormsModule, 20 | ReactiveFormsModule 21 | ], 22 | declarations: [ 23 | HighlightDirective, 24 | InputControlComponent, 25 | CountdownComponent, 26 | VerifyFormComponent, 27 | ModalComponent, 28 | SelectControlComponent, 29 | SelectListComponent, 30 | BubbleComponent, 31 | CascadeListComponent, 32 | ObjectToArrayPipe 33 | ], 34 | exports: [ 35 | CommonModule, 36 | FormsModule, 37 | ReactiveFormsModule, 38 | HighlightDirective, 39 | InputControlComponent, 40 | CountdownComponent, 41 | VerifyFormComponent, 42 | ModalComponent, 43 | SelectControlComponent, 44 | SelectListComponent, 45 | BubbleComponent 46 | ], 47 | providers: [ModalService, SelectListService], 48 | entryComponents: [SelectListComponent, CascadeListComponent] 49 | }) 50 | export class SharedModule { 51 | constructor( @Optional() @SkipSelf() parentModule: SharedModule) { 52 | if (parentModule) { 53 | throw new Error('CoreModule is already loaded. Import it in the AppModule only'); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/global/_base.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | @import "function"; 3 | @import "mixin"; 4 | @import "reset"; 5 | -------------------------------------------------------------------------------- /src/assets/global/_function.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * _function.scss 3 | */ 4 | // 设计稿宽度1080,基准值为 1080/10 5 | @function px2rem($pixels, $context: 108) { 6 | @return ($pixels / $context) * 1rem; 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/global/_mixin.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * _mixin.scss 3 | */ 4 | @mixin clearfix { 5 | *zoom: 1; 6 | &:after, &:before { 7 | content: ""; 8 | display: table; 9 | height: 0px; 10 | clear: both; 11 | visibility: hidden; 12 | } 13 | } 14 | 15 | @mixin font-dpr($font-size) { 16 | font-size: $font-size; 17 | 18 | [data-dpr="2"] & { 19 | font-size: $font-size * 2; 20 | } 21 | 22 | [data-dpr="3"] & { 23 | font-size: $font-size * 3; 24 | } 25 | } 26 | 27 | $fontFamily: Helvetica, Tahoma, Arial, 'Hiragino Sans GB', STXihei, "Microsoft YaHei", SimSun, Heiti, sans-serif; 28 | 29 | -------------------------------------------------------------------------------- /src/assets/global/_reset.scss: -------------------------------------------------------------------------------- 1 | /* html5doctor.com Reset Stylesheet v1.6.1 Last Updated: 2010-09-17 Author: Richard Clark - http://richclarkdesign.com Twitter: @rich_clark */ 2 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video, button { 3 | margin: 0; 4 | padding: 0; 5 | border: 0; 6 | outline: 0; 7 | font-size: 100%; 8 | vertical-align: middle; 9 | background: transparent; 10 | } 11 | 12 | h1,h2,h3,h4,h5,h6,pre,code,address,caption,cite,code,em,strong,th,figcaption { 13 | font-size: 1em; 14 | font-weight: normal; 15 | font-style: normal; 16 | } 17 | 18 | body { 19 | line-height: normal; 20 | } 21 | 22 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 23 | display: block; 24 | } 25 | 26 | ol,ul { 27 | list-style: none; 28 | } 29 | 30 | blockquote, q { 31 | quotes: none; 32 | } 33 | 34 | blockquote:before, blockquote:after, q:before, q:after { 35 | content: ' '; 36 | display: none; 37 | } 38 | 39 | a { 40 | margin: 0; 41 | padding: 0; 42 | font-size: 100%; 43 | vertical-align: middle; 44 | background: transparent; 45 | text-decoration: none; 46 | color: inherit; 47 | } 48 | 49 | /* change colours to suit your needs */ 50 | ins { 51 | background-color: #ff9; 52 | color: #000; 53 | text-decoration: none; 54 | } 55 | 56 | /* change colours to suit your needs */ 57 | mark { 58 | background-color: #ff9; 59 | color: #000; 60 | font-style: italic; 61 | font-weight: bold; 62 | } 63 | 64 | del { 65 | text-decoration: line-through; 66 | } 67 | 68 | table { 69 | border-collapse: collapse; 70 | border-spacing: 0; 71 | } 72 | 73 | /* change border colour to suit your needs */ 74 | hr { 75 | display: block; 76 | height: 1px; 77 | border: 0; 78 | border-top: 1px solid #cccccc; 79 | margin: 1em 0; 80 | padding: 0; 81 | } 82 | 83 | input, select { 84 | vertical-align: middle; 85 | border: 0; 86 | } 87 | 88 | input:autofill, 89 | textarea:autofill, 90 | select:autofill { 91 | box-shadow: 0 0 0px 1000px white !important; 92 | background-color: #fff !important; 93 | } 94 | 95 | html,body{ 96 | user-select: none; 97 | font-family: $fontFamily; 98 | } 99 | 100 | html,a{ 101 | -webkit-tap-highlight-color:rgba(0,0,0,0); 102 | -webkit-touch-callout: none; 103 | } 104 | -------------------------------------------------------------------------------- /src/assets/global/_variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * _variables.scss 3 | */ 4 | 5 | $fontFamily: Helvetica, Tahoma, Arial, 'Hiragino Sans GB', STXihei, "Microsoft YaHei", SimSun, Heiti, sans-serif; 6 | 7 | // 全局外层左右 padding 8 | $bodyPadding: 36; 9 | 10 | // 全局输入组左右 padding 11 | $inputPadding: 36; 12 | 13 | // 全局边框默认颜色 14 | $borderDefaultColor: rgba(0, 0, 0, 0.05); 15 | // 激活态边框颜色 16 | $borderMainColor: rgba(24, 189, 145, 0.5); 17 | 18 | // 全局主色调 19 | $mainColor: #18BD91; 20 | $textDefaultColor: #000; 21 | 22 | $bgColor: #fff; 23 | -------------------------------------------------------------------------------- /src/assets/global/flexible.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Heaven on 2016/11/6 0006. 3 | */ 4 | !function(a,b){function c(){var b=f.getBoundingClientRect().width;b/i>540&&(b=540*i);var c=b/10;f.style.fontSize=c+"px",k.rem=a.rem=c}var d,e=a.document,f=e.documentElement,g=e.querySelector('meta[name="viewport"]'),h=e.querySelector('meta[name="flexible"]'),i=0,j=0,k=b.flexible||(b.flexible={});if(g){console.warn("将根据已有的meta标签来设置缩放比例");var l=g.getAttribute("content").match(/initial\-scale=([\d\.]+)/);l&&(j=parseFloat(l[1]),i=parseInt(1/j))}else if(h){var m=h.getAttribute("content");if(m){var n=m.match(/initial\-dpr=([\d\.]+)/),o=m.match(/maximum\-dpr=([\d\.]+)/);n&&(i=parseFloat(n[1]),j=parseFloat((1/i).toFixed(2))),o&&(i=parseFloat(o[1]),j=parseFloat((1/i).toFixed(2)))}}if(!i&&!j){var p=a.navigator.userAgent,q=(!!p.match(/android/gi),!!p.match(/iphone/gi)),r=q&&!!p.match(/OS 9_3/),s=a.devicePixelRatio;i=q&&!r?s>=3&&(!i||i>=3)?3:s>=2&&(!i||i>=2)?2:1:1,j=1/i}if(f.setAttribute("data-dpr",i),!g)if(g=e.createElement("meta"),g.setAttribute("name","viewport"),g.setAttribute("content","initial-scale="+j+", maximum-scale="+j+", minimum-scale="+j+", user-scalable=no"),f.firstElementChild)f.firstElementChild.appendChild(g);else{var t=e.createElement("div");t.appendChild(g),e.write(t.innerHTML)}a.addEventListener("resize",function(){clearTimeout(d),d=setTimeout(c,300)},!1),a.addEventListener("pageshow",function(a){a.persisted&&(clearTimeout(d),d=setTimeout(c,300))},!1),"complete"===e.readyState?e.body.style.fontSize=12*i+"px":e.addEventListener("DOMContentLoaded",function(){e.body.style.fontSize=12*i+"px"},!1),c(),k.dpr=a.dpr=i,k.refreshRem=c,k.rem2px=function(a){var b=parseFloat(a)*this.rem;return"string"==typeof a&&a.match(/rem$/)&&(b+="px"),b},k.px2rem=function(a){var b=parseFloat(a)/this.rem;return"string"==typeof a&&a.match(/px$/)&&(b+="rem"),b}}(window,window.lib||(window.lib={})); 5 | -------------------------------------------------------------------------------- /src/assets/global/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/favicon.ico -------------------------------------------------------------------------------- /src/assets/global/images/icon-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-arrow.png -------------------------------------------------------------------------------- /src/assets/global/images/icon-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-back.png -------------------------------------------------------------------------------- /src/assets/global/images/icon-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-close.png -------------------------------------------------------------------------------- /src/assets/global/images/icon-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-delete.png -------------------------------------------------------------------------------- /src/assets/global/images/icon-location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 地址 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/global/images/icon-popup-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-popup-arrow.png -------------------------------------------------------------------------------- /src/assets/global/images/icon-selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/icon-selected.png -------------------------------------------------------------------------------- /src/assets/global/images/radio-checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/radio-checked.png -------------------------------------------------------------------------------- /src/assets/global/images/submit-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/assets/global/images/submit-complete.png -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lichenbuliren/mcare-app/0d6161cb465c7e6319b31a6dad30bea62c5c640c/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | McareApp 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |

Loading

16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import "./assets/global/base"; 4 | 5 | .loading-bro { 6 | position: absolute; 7 | left: 50%; 8 | top: 50vh; 9 | transform: translate3d(-50%, -70%, 0); 10 | width: 150px; 11 | h1 { 12 | text-align: center; 13 | @include font-dpr(36px); 14 | margin-bottom: 1em; 15 | font-weight: 300; 16 | color: #8E8E8E; 17 | } 18 | } 19 | 20 | #load { 21 | width: 150px; 22 | animation: loading 3s linear infinite; 23 | #loading-inner { 24 | stroke: { 25 | dashoffset: 0; 26 | dasharray: 300; 27 | width: 10; 28 | miterlimit: 10; 29 | linecap: round; 30 | } 31 | animation: loading-circle 2s linear infinite; 32 | stroke: #51BBA7; 33 | fill: transparent; 34 | } 35 | } 36 | 37 | @keyframes loading { 38 | 0% { 39 | transform: rotate(0); 40 | } 41 | 100% { 42 | transform: rotate(360deg); 43 | } 44 | } 45 | @keyframes loading-circle { 46 | 0% { 47 | stroke-dashoffset: 0 48 | } 49 | 100% { 50 | stroke-dashoffset: -600; 51 | } 52 | } 53 | 54 | .container { 55 | font-family: Helvetica, Tahoma, Arial, 'Hiragino Sans GB', STXihei, "Microsoft YaHei", SimSun, Heiti, sans-serif; 56 | background-color: $bgColor; 57 | color: #000; 58 | -webkit-tap-highlight-color: transparent; 59 | user-select: none; 60 | @include font-dpr(16px); 61 | padding: 0; 62 | } 63 | 64 | .row-control { 65 | position: relative; 66 | vertical-align: middle; 67 | @include font-dpr(16px); 68 | box-sizing: border-box; 69 | padding: 0 px2rem($bodyPadding); 70 | 71 | .input-control, 72 | .select-control { 73 | position: relative; 74 | overflow: hidden; 75 | display: block; 76 | box-sizing: border-box; 77 | color: #000; 78 | @include font-dpr(16px); 79 | line-height: 1; 80 | border-bottom: 1px solid rgba(0, 0, 0, .05); 81 | 82 | // 验证错误 83 | &.error { 84 | border-color: #DF5742; 85 | border-bottom: 0; 86 | } 87 | 88 | .error-tips { 89 | position: relative; 90 | z-index: 2; 91 | border-top: 1px solid #DF5742; 92 | color: #DF5742; 93 | padding: px2rem(32) px2rem(36); 94 | } 95 | 96 | input { 97 | position: relative; 98 | z-index: 1; 99 | @include font-dpr(16px); 100 | width: 100%; 101 | outline: none; 102 | padding: px2rem(60) px2rem(36); 103 | color: #000; 104 | 105 | &::-webkit-input-placeholder { 106 | color: #000; /* change [placeholder color] by this*/ 107 | text-shadow: none; 108 | -webkit-text-fill-color: rgba(0, 0, 0, .3); 109 | } 110 | } 111 | 112 | // 聚焦样式 113 | &.focus { 114 | // border-color: #18BD91; 115 | border-color: rgba(24, 189, 145, .5); 116 | 117 | // 聚焦的时候,改变指针样式 118 | input { 119 | color: #18BD91; /* change [input cursor color] by this*/ 120 | text-shadow: 0 0 0 transparent; /* change [input font] by this*/ 121 | -webkit-text-fill-color: #000; 122 | } 123 | } 124 | 125 | .label { 126 | font-size: inherit; 127 | text-align: left; 128 | float: left; 129 | color: inherit; 130 | width: initial; 131 | padding: px2rem(60) 0 px2rem(60) px2rem(36); 132 | line-height: 1; 133 | 134 | &.result { 135 | position: absolute; 136 | @include font-dpr(12px); 137 | opacity: .5; 138 | right: px2rem(97); 139 | max-width: px2rem(600); 140 | top: 50%; 141 | padding: 0; 142 | transform: translateY(-50%); 143 | overflow: hidden; 144 | white-space: nowrap; 145 | text-overflow: ellipsis; 146 | } 147 | } 148 | 149 | .icon { 150 | position: absolute; 151 | z-index: 4; 152 | top: px2rem(87); 153 | transform: translateY(-50%); 154 | right: px2rem(36);; 155 | } 156 | 157 | // 删除内容图标 158 | .icon-delete { 159 | display: block; 160 | background: url('/assets/global/images/icon-delete.png') center no-repeat; 161 | width: px2rem(120); 162 | height: px2rem(120); 163 | background-size: px2rem(54) px2rem(54); 164 | } 165 | 166 | .icon-radio { 167 | width: px2rem(72); 168 | height: px2rem(72); 169 | border-radius: 50%; 170 | border: 1px solid #DADADA; 171 | 172 | &.icon-checked { 173 | border: 0; 174 | background: url('/assets/global/images/radio-checked.png') center no-repeat; 175 | background-size: px2rem($bodyPadding); 176 | } 177 | } 178 | 179 | .icon-arrow { 180 | width: px2rem(27); 181 | height: px2rem(17); 182 | background: url('/assets/global/images/icon-popup-arrow.png') center no-repeat; 183 | background-size: px2rem(27) px2rem(17); 184 | } 185 | } 186 | 187 | // 注释 188 | .remark { 189 | opacity: .5; 190 | padding: px2rem(30) 0; 191 | padding-left: px2rem(36); 192 | @include font-dpr(12px); 193 | } 194 | } 195 | 196 | .input-control.sms-control { 197 | input { 198 | width: 50%; 199 | } 200 | .icon { 201 | display: none; 202 | } 203 | } 204 | 205 | .btn-control { 206 | width: px2rem(858); 207 | border-bottom: 0; 208 | margin: px2rem(90) auto 0; 209 | text-align: center; 210 | 211 | // 公共按钮 212 | .button, button { 213 | display: inline-block; 214 | width: px2rem(720); 215 | height: px2rem(114); 216 | border-radius: px2rem(57); 217 | line-height: px2rem(114); 218 | padding: 0; 219 | border: 0; 220 | outline: 0; 221 | color: #fff; 222 | background-color: #18BD91; 223 | @include font-dpr(18px); 224 | text-align: center; 225 | 226 | &.cancel, 227 | &[disabled] { 228 | background-color: #B3B3B3; 229 | } 230 | } 231 | } 232 | 233 | // 公共地址选择器 234 | .address-selector-container { 235 | min-height: px2rem(700); 236 | max-height: px2rem(1280); 237 | overflow: scroll; 238 | } 239 | 240 | .address-selector-wrapper { 241 | padding: 0 px2rem(36); 242 | position: relative; 243 | 244 | .selector-title { 245 | position: relative; 246 | @include font-dpr(16px); 247 | height: px2rem(176); 248 | line-height: px2rem(176); 249 | vertical-align: middle; 250 | text-indent: px2rem(36); 251 | z-index: 2; 252 | background-color: #fff; 253 | margin: 0; 254 | border-bottom: 1px solid rgba(0, 0, 0, .05); 255 | 256 | &::after { 257 | position: absolute; 258 | content: ''; 259 | right: px2rem(36); 260 | top: 50%; 261 | transform: translateY(-50%) rotate(-180deg); 262 | background: url('/assets/global/images/icon-arrow.png') center no-repeat; 263 | background-size: px2rem(46) px2rem(27); 264 | width: px2rem(46); 265 | height: px2rem(27); 266 | } 267 | } 268 | 269 | &:first-child { 270 | .selector-title { 271 | border-top: 1px solid rgba(0, 0, 0, .05); 272 | } 273 | } 274 | 275 | .address-item { 276 | height: 0; 277 | position: relative; 278 | max-height: px2rem(734); 279 | overflow-y: scroll; 280 | color: #262626; 281 | box-sizing: border-box; 282 | display: block; 283 | background: #fff; 284 | 285 | .item-list { 286 | position: relative; 287 | cursor: pointer; 288 | width: inherit; 289 | display: block; 290 | line-height: 1; 291 | box-sizing: border-box; 292 | padding: px2rem(63) 0; 293 | @include font-dpr(14px); 294 | border-bottom: 1px solid rgba(0, 0, 0, .05); 295 | margin: 0 px2rem(36); 296 | } 297 | } 298 | 299 | &.address-selector-wrapper-active { 300 | 301 | .address-item { 302 | height: auto; 303 | } 304 | .selector-title::after { 305 | transform: translateY(-50%) rotate(0); 306 | } 307 | } 308 | 309 | .row-control { 310 | .input-control { 311 | overflow: hidden; 312 | border-bottom: 1px solid rgba(0, 0, 0, .05); 313 | } 314 | } 315 | } 316 | 317 | // 公共数据展示表格 318 | .table-list { 319 | width: 100%; 320 | color: #000; 321 | @include font-dpr(12px); 322 | display: none; 323 | 324 | $childWidth1: 12%; 325 | $childWidth2: 32%; 326 | $childWidth3: 26%; 327 | $childWidth4: 30%; 328 | 329 | tr { 330 | td { 331 | padding: px2rem(36) 0; 332 | text-align: center; 333 | 334 | &:nth-child(1) { 335 | width: $childWidth1; 336 | } 337 | 338 | &:nth-child(2) { 339 | width: $childWidth2; 340 | } 341 | 342 | &:nth-child(3) { 343 | width: $childWidth3; 344 | } 345 | 346 | &:nth-child(4) { 347 | width: $childWidth4; 348 | } 349 | 350 | } 351 | } 352 | 353 | thead { 354 | background-color: #F7F7F7; 355 | 356 | tr { 357 | opacity: .5; 358 | } 359 | } 360 | 361 | tbody { 362 | tr:nth-child(1) { 363 | td { 364 | padding-top: px2rem(72); 365 | } 366 | } 367 | } 368 | } 369 | 370 | // 公共下拉列表 371 | .slide-list { 372 | box-sizing: border-box; 373 | padding: 0 px2rem(36) px2rem(36); 374 | 375 | .list-item { 376 | border-bottom: 1px solid rgba(0, 0, 0, .05); 377 | cursor: pointer; 378 | overflow: hidden; 379 | line-height: 1.6; 380 | @include font-dpr(14px); 381 | 382 | .title { 383 | @include font-dpr(16px); 384 | position: relative; 385 | padding: px2rem(63) px2rem(36); 386 | line-height: 1; 387 | 388 | &::after { 389 | position: absolute; 390 | content: ''; 391 | right: px2rem(27); 392 | top: px2rem(75); 393 | width: px2rem(46); 394 | height: px2rem(27); 395 | background-image: url('assets/global/images/icon-arrow.png'); 396 | background-size: px2rem(46) px2rem(27); 397 | transform-origin: center; 398 | transform: rotate(-180deg); 399 | } 400 | } 401 | 402 | &.list-item-active { 403 | .title { 404 | border-bottom: 1px solid rgba(0, 0, 0, .05); 405 | 406 | &::after { 407 | transform-origin: center; 408 | transform: rotate(0); 409 | } 410 | } 411 | 412 | .table-list { 413 | display: table; 414 | } 415 | } 416 | } 417 | } 418 | 419 | // 公共表单 420 | .form { 421 | @include font-dpr(16px); 422 | line-height: 1.5; 423 | color: #000; 424 | } 425 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es6", "dom"], 7 | "mapRoot": "./", 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "outDir": "../dist/out-tsc", 11 | "sourceMap": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "../node_modules/@types" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | 4 | declare var System: any; 5 | declare module 'jshashes'; 6 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "label-undefined": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-key": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": false, 45 | "no-eval": true, 46 | "no-inferrable-types": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-unused-expression": true, 52 | "no-unused-variable": true, 53 | "no-unreachable": true, 54 | "no-use-before-declare": true, 55 | "no-var-keyword": true, 56 | "object-literal-sort-keys": false, 57 | "one-line": [ 58 | true, 59 | "check-open-brace", 60 | "check-catch", 61 | "check-else", 62 | "check-whitespace" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "radix": true, 69 | "semicolon": [ 70 | "always" 71 | ], 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef-whitespace": [ 77 | true, 78 | { 79 | "call-signature": "nospace", 80 | "index-signature": "nospace", 81 | "parameter": "nospace", 82 | "property-declaration": "nospace", 83 | "variable-declaration": "nospace" 84 | } 85 | ], 86 | "variable-name": false, 87 | "whitespace": [ 88 | true, 89 | "check-branch", 90 | "check-decl", 91 | "check-operator", 92 | "check-separator", 93 | "check-type" 94 | ], 95 | 96 | "directive-selector-prefix": [true, "app"], 97 | "component-selector-prefix": [true, "app"], 98 | "directive-selector-name": [true, "camelCase"], 99 | "component-selector-name": [true, "kebab-case"], 100 | "directive-selector-type": [true, "attribute"], 101 | "component-selector-type": [true, "element"], 102 | "use-input-property-decorator": true, 103 | "use-output-property-decorator": true, 104 | "use-host-property-decorator": true, 105 | "no-input-rename": true, 106 | "no-output-rename": true, 107 | "use-life-cycle-interface": true, 108 | "use-pipe-transform-interface": true, 109 | "component-class-suffix": true, 110 | "directive-class-suffix": true, 111 | "templates-use-public": true, 112 | "invoke-injectable": true 113 | } 114 | } 115 | --------------------------------------------------------------------------------