├── hotel-front ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── sider │ │ │ ├── sider.component.css │ │ │ ├── sider.component.ts │ │ │ ├── sider.component.html │ │ │ └── sider.component.spec.ts │ │ ├── dto │ │ │ ├── SearchCondition.ts │ │ │ ├── Result.ts │ │ │ ├── Logout.ts │ │ │ ├── Bed.ts │ │ │ ├── Income.ts │ │ │ ├── Customer.ts │ │ │ ├── Charge.ts │ │ │ └── Room.ts │ │ ├── login │ │ │ ├── login.component.css │ │ │ ├── login.component.spec.ts │ │ │ ├── login.component.html │ │ │ └── login.component.ts │ │ ├── room-management-bed │ │ │ ├── room-management-bed.component.css │ │ │ ├── room-management-bed.component.spec.ts │ │ │ ├── room-management-bed.component.html │ │ │ └── room-management-bed.component.ts │ │ ├── const │ │ │ └── MessageConsts.ts │ │ ├── room-no-search │ │ │ ├── room-no-search.component.css │ │ │ ├── room-no-search.component.html │ │ │ ├── room-no-search.component.spec.ts │ │ │ └── room-no-search.component.ts │ │ ├── room-management │ │ │ ├── room-management.component.css │ │ │ ├── room-management.component.spec.ts │ │ │ ├── room-management.component.html │ │ │ └── room-management.component.ts │ │ ├── app.component.ts │ │ ├── app-routing.module.spec.ts │ │ ├── room-management-room │ │ │ ├── room-management-room.component.css │ │ │ ├── room-management-room.component.spec.ts │ │ │ ├── room-management-room.component.html │ │ │ └── room-management-room.component.ts │ │ ├── room.service.spec.ts │ │ ├── customer-management │ │ │ ├── customer-management.component.css │ │ │ ├── customer-management.component.spec.ts │ │ │ ├── customer-management.component.html │ │ │ └── customer-management.component.ts │ │ ├── customer.service.spec.ts │ │ ├── app.component.html │ │ ├── logout │ │ │ ├── logout.component.spec.ts │ │ │ ├── logout.component.css │ │ │ ├── logout.component.ts │ │ │ └── logout.component.html │ │ ├── app.component.css │ │ ├── app-routing.module.ts │ │ ├── app.component.spec.ts │ │ ├── app.module.ts │ │ ├── util │ │ │ └── Utils.ts │ │ ├── customer.service.ts │ │ └── room.service.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── index.html │ ├── tslint.json │ ├── main.ts │ ├── browserslist │ ├── styles.css │ ├── test.ts │ ├── karma.conf.js │ └── polyfills.ts ├── .editorconfig ├── tsconfig.json ├── package.json ├── tslint.json └── angular.json ├── .gitignore ├── .gitattributes ├── .DS_Store ├── hotel-server ├── .DS_Store └── src │ ├── .DS_Store │ ├── main │ ├── java │ │ ├── META-INF │ │ │ └── MANIFEST.MF │ │ └── com │ │ │ └── hotel │ │ │ ├── annotation │ │ │ ├── AddRoomGroup.java │ │ │ ├── LoginGroup.java │ │ │ └── UpdateCustomerGroup.java │ │ │ ├── service │ │ │ ├── ChargeService.java │ │ │ ├── BedService.java │ │ │ ├── impl │ │ │ │ ├── BedServiceImpl.java │ │ │ │ ├── CustomerServiceImpl.java │ │ │ │ └── RoomServiceImpl.java │ │ │ ├── RoomService.java │ │ │ └── CustomerService.java │ │ │ ├── exception │ │ │ ├── CompareException.java │ │ │ └── RestExceptionHandler.java │ │ │ ├── dto │ │ │ ├── BedDto.java │ │ │ ├── RoomSearchCondition.java │ │ │ ├── ResultDto.java │ │ │ ├── IncomeDto.java │ │ │ ├── LogoutDto.java │ │ │ ├── RoomPageableDto.java │ │ │ ├── ChargeDto.java │ │ │ ├── CustomerDto.java │ │ │ └── RoomDto.java │ │ │ ├── repository │ │ │ ├── BedRepository.java │ │ │ ├── ChargeRepository.java │ │ │ ├── RoomRepository.java │ │ │ └── CustomerRepository.java │ │ │ ├── HotelApplication.java │ │ │ ├── entity │ │ │ ├── Income.java │ │ │ ├── Bed.java │ │ │ ├── Customer.java │ │ │ ├── Charge.java │ │ │ └── Room.java │ │ │ ├── constant │ │ │ ├── RoomStatus.java │ │ │ ├── RoomType.java │ │ │ ├── BedType.java │ │ │ └── RetCode.java │ │ │ ├── config │ │ │ └── Swagger2Configuration.java │ │ │ ├── util │ │ │ ├── BeanUtils.java │ │ │ └── Converters.java │ │ │ └── controller │ │ │ ├── CustomerController.java │ │ │ └── RoomController.java │ ├── .DS_Store │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── hotel │ └── service │ └── RoomServiceTest.java └── README.md /hotel-front/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hotel-front/src/app/sider/sider.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target/ 4 | *.xml 5 | dist/ 6 | e2e 7 | node_modules/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cool-Coding/angular-springboot-demo/HEAD/.DS_Store -------------------------------------------------------------------------------- /hotel-front/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /hotel-server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cool-Coding/angular-springboot-demo/HEAD/hotel-server/.DS_Store -------------------------------------------------------------------------------- /hotel-front/src/app/dto/SearchCondition.ts: -------------------------------------------------------------------------------- 1 | export class SearchCondition { 2 | key: string; 3 | value: string; 4 | } 5 | -------------------------------------------------------------------------------- /hotel-server/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cool-Coding/angular-springboot-demo/HEAD/hotel-server/src/.DS_Store -------------------------------------------------------------------------------- /hotel-server/src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.hotel.HotelApplication 3 | 4 | -------------------------------------------------------------------------------- /hotel-front/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cool-Coding/angular-springboot-demo/HEAD/hotel-front/src/favicon.ico -------------------------------------------------------------------------------- /hotel-server/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cool-Coding/angular-springboot-demo/HEAD/hotel-server/src/main/.DS_Store -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Result.ts: -------------------------------------------------------------------------------- 1 | export class Result { 2 | code: string; 3 | message: string; 4 | success: boolean; 5 | data: object; 6 | } 7 | -------------------------------------------------------------------------------- /hotel-front/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | [nz-form] { 2 | margin:20px auto; 3 | max-width: 600px; 4 | } 5 | button { 6 | margin-left: 8px; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-bed/room-management-bed.component.css: -------------------------------------------------------------------------------- 1 | .editable-row-operations a { 2 | margin-right: 8px; 3 | } 4 | 5 | [nz-modal] { 6 | width: 900px; 7 | } 8 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Logout.ts: -------------------------------------------------------------------------------- 1 | export class Logout { 2 | roomNo: string; 3 | type: string; 4 | price: string; 5 | interval: string; 6 | sum: string; 7 | logoutDate: string; 8 | } 9 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/annotation/AddRoomGroup.java: -------------------------------------------------------------------------------- 1 | package com.hotel.annotation; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-28 6 | */ 7 | public interface AddRoomGroup { 8 | } 9 | -------------------------------------------------------------------------------- /hotel-front/src/app/const/MessageConsts.ts: -------------------------------------------------------------------------------- 1 | export const MESSAGETEXTS = { 2 | FETCH_SUCCESS: '获取成功', 3 | UPDATE_SUCCESS: '更新成功', 4 | DELETE_SUCCESS: '删除成功', 5 | LOGIN_SUCCESS: '登记成功', 6 | SEARCH_SUCCESS: '{0}个结果' 7 | }; 8 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/annotation/LoginGroup.java: -------------------------------------------------------------------------------- 1 | package com.hotel.annotation; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-22 6 | * @description 7 | */ 8 | public interface LoginGroup { 9 | } 10 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/ChargeService.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-25 6 | * @description 7 | */ 8 | public class ChargeService { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-no-search/room-no-search.component.css: -------------------------------------------------------------------------------- 1 | p { 2 | text-align: center; 3 | } 4 | 5 | p span:first-child { 6 | float: left; 7 | } 8 | p span:last-child { 9 | float: right; 10 | padding-left: 15px; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /hotel-front/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Bed.ts: -------------------------------------------------------------------------------- 1 | export class Bed { 2 | id: number; 3 | /** 4 | * 床名称 5 | */ 6 | name: string; 7 | 8 | /** 9 | * 床型 10 | */ 11 | type: string; 12 | 13 | /** 14 | * 床大小 15 | */ 16 | size: string; 17 | } 18 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/annotation/UpdateCustomerGroup.java: -------------------------------------------------------------------------------- 1 | package com.hotel.annotation; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-27 6 | * @description 7 | */ 8 | public interface UpdateCustomerGroup { 9 | } 10 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management/room-management.component.css: -------------------------------------------------------------------------------- 1 | [nz-button] { 2 | margin-bottom: 5px; 3 | margin-top: 5px; 4 | } 5 | 6 | .editable-add-btn { 7 | margin-left: 10px; 8 | } 9 | 10 | .wrong_message { 11 | color: #f5222d; 12 | } 13 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Income.ts: -------------------------------------------------------------------------------- 1 | export class Income { 2 | id: number; 3 | /** 4 | * 客房号 5 | */ 6 | roomNo: string; 7 | 8 | /** 9 | * 收入 10 | */ 11 | incoming: number; 12 | 13 | /** 14 | * 结算日期 15 | */ 16 | logoutDate: string; 17 | } 18 | -------------------------------------------------------------------------------- /hotel-front/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'hotel-front'; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /hotel-front/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://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 = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/BedService.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-25 6 | * @description 7 | */ 8 | public interface BedService { 9 | /** 10 | * 删除床铺信息 11 | * @param id 床铺ID 12 | */ 13 | void delete(Integer id); 14 | } 15 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/exception/CompareException.java: -------------------------------------------------------------------------------- 1 | package com.hotel.exception; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-23 6 | * @description 7 | */ 8 | public class CompareException extends RuntimeException { 9 | public CompareException(String message){ 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /hotel-front/src/app/sider/sider.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sider', 5 | templateUrl: './sider.component.html', 6 | styleUrls: ['./sider.component.css'] 7 | }) 8 | export class SiderComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hotel-front/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/BedDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author guangyong.yang 7 | * @date 2019-01-21 8 | * @description 9 | */ 10 | @Data 11 | public class BedDto { 12 | private Integer id; 13 | private String name; 14 | private String type; 15 | private String size; 16 | } 17 | -------------------------------------------------------------------------------- /hotel-front/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HotelFront 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/RoomSearchCondition.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author guangyong.yang 7 | * @date 2019-01-20 8 | * @description 9 | */ 10 | @Data 11 | public class RoomSearchCondition { 12 | private String roomNo; 13 | private String name; 14 | private String idCard; 15 | private String phoneNo; 16 | } 17 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Customer.ts: -------------------------------------------------------------------------------- 1 | export class Customer { 2 | id: number; 3 | 4 | /** 5 | * 姓名 6 | */ 7 | name: string; 8 | 9 | /** 10 | * 身份证号 11 | */ 12 | idCard: string; 13 | 14 | /** 15 | * 手机号 16 | */ 17 | phoneNo: string; 18 | 19 | /** 20 | * 房间号 21 | */ 22 | roomNos: string[]; 23 | 24 | /** 25 | * 备注 26 | */ 27 | comment: string; 28 | } 29 | -------------------------------------------------------------------------------- /hotel-front/src/app/app-routing.module.spec.ts: -------------------------------------------------------------------------------- 1 | import { AppRoutingModule } from './app-routing.module'; 2 | 3 | describe('AppRoutingModule', () => { 4 | let appRoutingModule: AppRoutingModule; 5 | 6 | beforeEach(() => { 7 | appRoutingModule = new AppRoutingModule(); 8 | }); 9 | 10 | it('should create an instance', () => { 11 | expect(appRoutingModule).toBeTruthy(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /hotel-front/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Charge.ts: -------------------------------------------------------------------------------- 1 | export class Charge { 2 | id: number; 3 | 4 | 5 | /** 6 | * 时间数量 7 | */ 8 | count: number; 9 | 10 | /** 11 | * 时间单位 12 | */ 13 | timeUnit: string; 14 | 15 | /** 16 | * 入住费用(单位:元) 17 | */ 18 | money: number; 19 | 20 | /** 21 | * 起始日期 22 | */ 23 | startDate: string; 24 | 25 | /** 26 | * 截止日期 27 | */ 28 | endDate: string; 29 | } 30 | -------------------------------------------------------------------------------- /hotel-front/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/repository/BedRepository.java: -------------------------------------------------------------------------------- 1 | package com.hotel.repository; 2 | 3 | import com.hotel.entity.Bed; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * @author guangyong.yang 9 | * @date 2019-01-25 10 | * @description 11 | */ 12 | @Repository 13 | public interface BedRepository extends JpaRepository { 14 | } 15 | -------------------------------------------------------------------------------- /hotel-front/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/repository/ChargeRepository.java: -------------------------------------------------------------------------------- 1 | package com.hotel.repository; 2 | 3 | import com.hotel.entity.Bed; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | /** 8 | * @author guangyong.yang 9 | * @date 2019-01-25 10 | * @description 11 | */ 12 | @Repository 13 | public interface ChargeRepository extends JpaRepository { 14 | } 15 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-room/room-management-room.component.css: -------------------------------------------------------------------------------- 1 | [nz-row] { 2 | margin: 6px; 3 | } 4 | 5 | [nz-row] div { 6 | margin-right: 10px; 7 | } 8 | 9 | [nz-row] div:first-child { 10 | text-align: right; 11 | } 12 | 13 | [nz-row] div:last-child input { 14 | position: relative; 15 | width: 60%; 16 | } 17 | 18 | [nz-row] div:last-child span{ 19 | margin-left: 10px; 20 | } 21 | 22 | .wrong_message { 23 | color: #f5222d; 24 | } 25 | -------------------------------------------------------------------------------- /hotel-front/src/app/room.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { RoomService } from './room.service'; 4 | 5 | describe('RoomService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [RoomService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([RoomService], (service: RoomService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer-management/customer-management.component.css: -------------------------------------------------------------------------------- 1 | .ant-advanced-search-form { 2 | padding: 24px; 3 | background: #ffffff; 4 | border-radius: 6px; 5 | min-width: 600px; 6 | } 7 | 8 | .search-result-list { 9 | margin:0 5px; 10 | border-radius: 6px; 11 | background-color: #ffffff; 12 | min-height: 200px; 13 | text-align: center; 14 | } 15 | 16 | button { 17 | margin-left: 8px; 18 | } 19 | 20 | a{ 21 | margin:5px; 22 | } 23 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { CustomerService } from './customer.service'; 4 | 5 | describe('CustomerService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [CustomerService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([CustomerService], (service: CustomerService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /hotel-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #web服务端口 2 | server.port=8080 3 | management.security.enabled=false 4 | 5 | #数据库配置 6 | spring.jpa.hibernate.ddl-auto=update 7 | spring.jpa.show-sql=true 8 | 9 | spring.datasource.url=jdbc:mysql://192.168.253.129:3306/hotel?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull 10 | spring.datasource.username=用户名 11 | spring.datasource.password=密码 12 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 13 | -------------------------------------------------------------------------------- /hotel-front/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

旅馆管理系统

5 |
6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | @2019 旅馆管理系统 15 | 16 | 17 | 18 |
19 | 20 | -------------------------------------------------------------------------------- /hotel-front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/ResultDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author guangyong.yang 8 | * @date 2019-01-21 9 | * @description 10 | */ 11 | @Data 12 | @Builder 13 | public class ResultDto { 14 | /** 15 | * 返回码 16 | */ 17 | private String code; 18 | 19 | /** 20 | * 返回消息 21 | */ 22 | private String message; 23 | 24 | /** 25 | * 是否成功 26 | */ 27 | private boolean success; 28 | 29 | 30 | /** 31 | * 返回数据 32 | */ 33 | private Object data; 34 | } 35 | -------------------------------------------------------------------------------- /hotel-front/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | .roomManager_switch .ant-switch-checked { 4 | background-color: rgb(135, 208, 104); 5 | } 6 | 7 | 8 | .room_management .ant-table-body { 9 | margin:5px 5px; 10 | } 11 | 12 | .logout_management .ant-table-tbody>tr>td, 13 | .logout_management .ant-table-thead>tr>th, 14 | .customer_management .ant-table-tbody>tr>td, 15 | .customer_management .ant-table-thead>tr>th, 16 | .room_management .ant-table-tbody>tr>td, 17 | .room_management .ant-table-thead>tr>th { 18 | padding: 8px; 19 | text-align:center; 20 | } 21 | -------------------------------------------------------------------------------- /hotel-front/src/app/sider/sider.component.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/impl/BedServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service.impl; 2 | 3 | import com.hotel.repository.BedRepository; 4 | import com.hotel.service.BedService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | /** 9 | * @author guangyong.yang 10 | * @date 2019-01-25 11 | * @description 12 | */ 13 | @Service 14 | public class BedServiceImpl implements BedService { 15 | @Autowired 16 | private BedRepository bedRepository; 17 | 18 | @Override 19 | public void delete(Integer id) { 20 | bedRepository.delete(id); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/IncomeDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-23 12 | * @description 13 | */ 14 | @Data 15 | public class IncomeDto { 16 | private Integer id; 17 | private String rommNo; 18 | private String incoming; 19 | 20 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 21 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 22 | private Date logoutDate; 23 | } 24 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/HotelApplication.java: -------------------------------------------------------------------------------- 1 | package com.hotel; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | /** 8 | * @author guangyong.yang 9 | * @date 2019-01-20 10 | */ 11 | @SpringBootApplication 12 | @EnableAutoConfiguration(exclude = { 13 | org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class 14 | }) 15 | public class HotelApplication { 16 | public static void main(String[] args){ 17 | SpringApplication.run(HotelApplication.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/entity/Income.java: -------------------------------------------------------------------------------- 1 | package com.hotel.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.*; 6 | import java.io.Serializable; 7 | import java.util.Date; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-21 12 | * @description 13 | */ 14 | @Data 15 | @Entity 16 | @Table(name = "income") 17 | public class Income implements Serializable { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Integer id; 22 | 23 | @ManyToOne 24 | private Room room; 25 | 26 | @Column 27 | private Float incoming; 28 | 29 | @Column 30 | @Temporal(TemporalType.TIMESTAMP) 31 | private Date logoutDate; 32 | } 33 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-no-search/room-no-search.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

{{room.roomNo}}{{room.type}} 5 | 空房 6 | 有客 7 | 不可订 8 |

9 |
10 |
11 | -------------------------------------------------------------------------------- /hotel-front/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /hotel-front/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /hotel-front/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/sider/sider.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SiderComponent } from './sider.component'; 4 | 5 | describe('SiderComponent', () => { 6 | let component: SiderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SiderComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SiderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/LogoutDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import org.springframework.format.annotation.DateTimeFormat; 7 | 8 | import java.util.Date; 9 | 10 | /** 11 | * @author guangyong.yang 12 | * @date 2019-01-21 13 | * @description 14 | */ 15 | @Data 16 | @Builder 17 | public class LogoutDto { 18 | private String roomNo; 19 | private String type; 20 | private String price; 21 | private String interval; 22 | private String sum; 23 | 24 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 25 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 26 | private Date logoutDate; 27 | } 28 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/RoomPageableDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Min; 6 | import javax.validation.constraints.Pattern; 7 | import java.util.List; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-23¡ 12 | * @description 13 | */ 14 | @Data 15 | public class RoomPageableDto { 16 | @Min(value = 1,message = "页码最小为1") 17 | @Pattern(regexp = "^\\d+$",message = "页码必须为整数") 18 | private int pageIndex; 19 | 20 | @Min(value = 1,message = "每页条目数量至少为1") 21 | @Pattern(regexp = "^\\d+$",message = "条目数量必须为整数") 22 | private int pageSize; 23 | 24 | private String sortKey; 25 | private String sortValue; 26 | 27 | private List status; 28 | } 29 | -------------------------------------------------------------------------------- /hotel-front/src/app/logout/logout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LogoutComponent } from './logout.component'; 4 | 5 | describe('LogoutComponent', () => { 6 | let component: LogoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LogoutComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LogoutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-no-search/room-no-search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RoomNoSearchComponent } from './room-no-search.component'; 4 | 5 | describe('RoomNoSearchComponent', () => { 6 | let component: RoomNoSearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RoomNoSearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RoomNoSearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management/room-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RoomManagementComponent } from './room-management.component'; 4 | 5 | describe('RoomManagementComponent', () => { 6 | let component: RoomManagementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RoomManagementComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RoomManagementComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host ::ng-deep .ant-layout-header, 2 | :host ::ng-deep .ant-layout-footer { 3 | background: #fff; 4 | margin-bottom: 5px; 5 | text-align: center; 6 | } 7 | 8 | :host ::ng-deep .ant-layout-footer { 9 | padding: 20px; 10 | line-height: 1.5; 11 | margin-top: 5px; 12 | margin-left: 5px; 13 | text-align: center; 14 | vertical-align: middle; 15 | } 16 | 17 | :host ::ng-deep .ant-layout-sider { 18 | background: #fff; 19 | color: #fff; 20 | } 21 | 22 | :host ::ng-deep .ant-layout-content { 23 | background: #fff; 24 | min-height: 120px; 25 | line-height: 20px; 26 | } 27 | 28 | :host > ::ng-deep .ant-layout { 29 | /*margin-bottom: 48px;*/ 30 | } 31 | 32 | :host ::ng-deep .ant-layout:last-child { 33 | margin: 0; 34 | } 35 | 36 | .content { 37 | min-height: 500px; 38 | margin-left: 5px; 39 | } 40 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-bed/room-management-bed.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RoomManagementBedComponent } from './room-management-bed.component'; 4 | 5 | describe('RoomManagementBedComponent', () => { 6 | let component: RoomManagementBedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RoomManagementBedComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RoomManagementBedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer-management/customer-management.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CustomerManagementComponent } from './customer-management.component'; 4 | 5 | describe('CustomerManagementComponent', () => { 6 | let component: CustomerManagementComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ CustomerManagementComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CustomerManagementComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-room/room-management-room.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RoomManagementRoomComponent } from './room-management-room.component'; 4 | 5 | describe('RoomManagementRoomComponent', () => { 6 | let component: RoomManagementRoomComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RoomManagementRoomComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RoomManagementRoomComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/ChargeDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonFormat; 4 | import lombok.Data; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-25 12 | * @description 13 | */ 14 | @Data 15 | public class ChargeDto { 16 | private Integer id; 17 | private String roomNo; 18 | private Integer count; 19 | private String timeUnit; 20 | private Float money; 21 | 22 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 23 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 24 | private Date startDate; 25 | 26 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 27 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 28 | private Date endDate; 29 | } 30 | -------------------------------------------------------------------------------- /hotel-server/src/test/java/com/hotel/service/RoomServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-25 12 | * @description 13 | */ 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | public class RoomServiceTest { 17 | 18 | @Autowired 19 | private RoomService roomService; 20 | 21 | @Test 22 | public void testDeleteBed(){ 23 | /*Room room = roomService.findOne(1); 24 | List beds = room.getBeds(); 25 | Bed bed = beds.get(1); 26 | bed.setRoom(null); 27 | beds.remove(1); 28 | room.setBeds(beds); 29 | roomService.save(room);*/ 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /hotel-front/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes} from '@angular/router'; 3 | import {LoginComponent} from './login/login.component'; 4 | import {LogoutComponent} from './logout/logout.component'; 5 | import {RoomManagementComponent} from './room-management/room-management.component'; 6 | import {CustomerManagementComponent} from './customer-management/customer-management.component'; 7 | 8 | const routes: Routes = [ 9 | {path: '', redirectTo: '/roomManagement', pathMatch: 'full'}, 10 | {path: 'login', component: LoginComponent}, 11 | {path: 'logout', component: LogoutComponent}, 12 | {path: 'roomManagement', component: RoomManagementComponent}, 13 | {path: 'customerManagement', component: CustomerManagementComponent}, 14 | ] 15 | @NgModule({ 16 | imports: [ RouterModule.forRoot(routes, { useHash: true })], 17 | exports: [ RouterModule ] 18 | }) 19 | 20 | export class AppRoutingModule { } 21 | -------------------------------------------------------------------------------- /hotel-front/src/app/dto/Room.ts: -------------------------------------------------------------------------------- 1 | import {Bed} from './Bed'; 2 | import {Customer} from './Customer'; 3 | import {Income} from './Income'; 4 | import {Charge} from './Charge'; 5 | 6 | export class Room { 7 | id: number; 8 | 9 | /** 10 | * 房间号 11 | */ 12 | roomNo: string; 13 | 14 | 15 | /** 16 | * 类型 17 | */ 18 | type: string; 19 | /** 20 | * 床 21 | */ 22 | beds: Bed[]; 23 | 24 | /** 25 | * 客人 26 | */ 27 | customers: Customer[]; 28 | /** 29 | * 当前房价 30 | */ 31 | money: number; 32 | 33 | 34 | /** 35 | * 入住日期 36 | */ 37 | checkInDate: string; 38 | 39 | /** 40 | * 退房日期 41 | */ 42 | checkOutDate: string; 43 | 44 | /** 45 | * 客房状态(0:空房;1:有客;2:不可订) 46 | */ 47 | status: number; 48 | 49 | /** 50 | * 收入 51 | */ 52 | incoming: Income[]; 53 | 54 | /** 55 | * 定价记录 56 | */ 57 | charges: Charge[]; 58 | 59 | /** 60 | * 是否更新床铺信息 61 | */ 62 | updatingBed: boolean = false; 63 | } 64 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/repository/RoomRepository.java: -------------------------------------------------------------------------------- 1 | package com.hotel.repository; 2 | 3 | import com.hotel.entity.Room; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author guangyong.yang 13 | * @date 2019-01-20 14 | * @description 15 | */ 16 | @Repository 17 | public interface RoomRepository extends JpaRepository { 18 | /** 19 | * 根据房间号模糊查找 20 | * @return 房间列表 21 | */ 22 | @Query("select h from Room h where h.roomNo like %:roomNo%") 23 | List searchRoomsByRoomNo(@Param("roomNo") String roomNo); 24 | 25 | /** 26 | * 根据房间号查询房间信息 27 | * @param roomNo 房间号 28 | * @return 房间信息 29 | */ 30 | List findRoomByRoomNo(String roomNo); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/entity/Bed.java: -------------------------------------------------------------------------------- 1 | package com.hotel.entity; 2 | 3 | import com.hotel.constant.BedType; 4 | import lombok.*; 5 | 6 | import javax.persistence.*; 7 | 8 | /** 9 | * @author guangyong.yang 10 | * @date 2019-01-16 11 | * @description 床 12 | */ 13 | @Data 14 | @Entity 15 | @Table(name = "bed") 16 | public class Bed { 17 | /** 18 | * id 19 | */ 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Integer id; 23 | 24 | 25 | /** 26 | * 床名 27 | */ 28 | @Column(length = 20) 29 | private String name; 30 | 31 | 32 | /** 33 | * 型号 34 | */ 35 | @Column(length = 30) 36 | private String size; 37 | 38 | 39 | /** 40 | * 所属房间 41 | */ 42 | @ManyToOne(optional = false) 43 | private Room room; 44 | 45 | 46 | @Column(nullable = false) 47 | @Enumerated(EnumType.STRING) 48 | private BedType type; 49 | 50 | public Bed() { 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/RoomService.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service; 2 | 3 | import com.hotel.dto.RoomSearchCondition; 4 | import com.hotel.entity.Room; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author guangyong.yang 10 | * @date 2019-01-20 11 | * @description 12 | */ 13 | public interface RoomService { 14 | /** 15 | * 搜索符合条件的房间集合 16 | * @param condition 17 | * @return 18 | */ 19 | List search(RoomSearchCondition condition); 20 | 21 | 22 | /** 23 | * 根据客户ID搜索客房信息 24 | * @param id 25 | * @return 26 | */ 27 | Room findOne(Integer id); 28 | 29 | /** 30 | * 保存客房信息 31 | * @param room 32 | * @return 33 | */ 34 | Room save(Room room); 35 | 36 | /** 37 | * 根据房间号查询房间信息 38 | * @param roomNo 房间号 39 | * @return 房间信息 40 | */ 41 | List findRoomByRoomNo(String roomNo); 42 | 43 | /** 44 | * 更新客房信息 45 | * @param room 客房信息 46 | * @return 客房信息 47 | */ 48 | Room updateRoom(Room room, boolean isUpdatingBed); 49 | } 50 | -------------------------------------------------------------------------------- /hotel-front/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/constant/RoomStatus.java: -------------------------------------------------------------------------------- 1 | package com.hotel.constant; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * @author guangyong.yang 7 | * @date 2019-01-21 8 | * @description 9 | */ 10 | public enum RoomStatus { 11 | /** 12 | * 空房 13 | */ 14 | EMPTY(0), 15 | 16 | /** 17 | * 入住中 18 | */ 19 | CHECKINGIN(1), 20 | 21 | /** 22 | * 不可订 23 | */ 24 | UNUSED(2); 25 | 26 | private Integer code; 27 | 28 | RoomStatus(Integer code) { 29 | this.code = code; 30 | } 31 | 32 | public Integer getCode(){ 33 | return this.code; 34 | } 35 | 36 | public static RoomStatus parse(Integer status) { 37 | if (Objects.isNull(status)) { 38 | return null; 39 | } 40 | switch (status) { 41 | case 0: 42 | return EMPTY; 43 | case 1: 44 | return CHECKINGIN; 45 | case 2: 46 | return UNUSED; 47 | default: 48 | throw new IllegalArgumentException("客房状态不存在"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/constant/RoomType.java: -------------------------------------------------------------------------------- 1 | package com.hotel.constant; 2 | 3 | import com.mysql.jdbc.StringUtils; 4 | 5 | /** 6 | * @author guangyong.yang 7 | * @date 2019-01-16 8 | * @description 房间类型 9 | */ 10 | public enum RoomType { 11 | /** 12 | * 小时房 13 | */ 14 | HOUR("小时房"), 15 | 16 | /** 17 | * 天房 18 | */ 19 | DAY("天房"), 20 | 21 | /** 22 | * 包月房 23 | */ 24 | MONTH("月房"); 25 | 26 | private String name; 27 | RoomType(String name){ 28 | this.name = name; 29 | } 30 | 31 | public String getName(){ 32 | return this.name; 33 | } 34 | 35 | public static RoomType parse(String value) { 36 | if (StringUtils.isNullOrEmpty(value)) { 37 | return null; 38 | } 39 | 40 | switch (value){ 41 | case "小时房": 42 | return HOUR; 43 | case "天房": 44 | return DAY; 45 | case "月房": 46 | return MONTH; 47 | default: 48 | throw new IllegalArgumentException("客房类型不存在"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hotel-front/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'hotel-front'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('hotel-front'); 23 | }); 24 | 25 | it('should render title in a h1 tag', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to hotel-front!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/entity/Customer.java: -------------------------------------------------------------------------------- 1 | package com.hotel.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | import java.util.List; 8 | 9 | /** 10 | * @author guangyong.yang 11 | * @date 2019-01-15 12 | * @description 客人 13 | */ 14 | @Getter 15 | @Setter 16 | @Entity 17 | @Table(name = "customer") 18 | public class Customer { 19 | /** 20 | * id 21 | */ 22 | @Id 23 | @Column 24 | @GeneratedValue(strategy = GenerationType.IDENTITY) 25 | private Integer id; 26 | 27 | /** 28 | * 姓名 29 | */ 30 | @Column(nullable = false,length = 80) 31 | private String name; 32 | 33 | 34 | /** 35 | * 身份证号 36 | */ 37 | @Column(nullable = false,length = 18) 38 | private String idCard; 39 | 40 | /** 41 | * 手机号 42 | */ 43 | @Column(length = 11) 44 | private String phoneNo; 45 | 46 | /** 47 | * 房间 48 | */ 49 | @ManyToMany(cascade = CascadeType.REFRESH,mappedBy = "customers") 50 | private List rooms; 51 | 52 | 53 | @Column(length = 100) 54 | private String comment; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/constant/BedType.java: -------------------------------------------------------------------------------- 1 | package com.hotel.constant; 2 | 3 | import com.mysql.jdbc.StringUtils; 4 | 5 | /** 6 | * @author guangyong.yang 7 | * @date 2019-01-18 8 | * @description 床型 9 | */ 10 | public enum BedType { 11 | /** 12 | * 三人床 13 | */ 14 | THREE_PEOPLE("三人床"), 15 | 16 | /** 17 | * 双人床 18 | */ 19 | TWO_PEOPLE("双人床"), 20 | 21 | /** 22 | * 单人床 23 | */ 24 | ONE_PEOPLE("单人床"); 25 | 26 | 27 | private String name; 28 | BedType(String name){ 29 | this.name = name; 30 | } 31 | 32 | public String getName(){ 33 | return this.name; 34 | } 35 | 36 | public static BedType parse(String name){ 37 | if (StringUtils.isNullOrEmpty(name)) { 38 | return null; 39 | } 40 | switch (name) { 41 | case "三人床": 42 | return THREE_PEOPLE; 43 | case "双人床": 44 | return TWO_PEOPLE; 45 | case "单人床": 46 | return ONE_PEOPLE; 47 | default: 48 | throw new IllegalArgumentException("床类型不正确"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service; 2 | 3 | import com.hotel.entity.Customer; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @author guangyong.yang 9 | * @date 2019-01-21 10 | * @description 11 | */ 12 | public interface CustomerService { 13 | /** 14 | * 根据身份证号查询客人信息 15 | * @param idCard 身份证号 16 | * @return 客人 17 | */ 18 | List findCustomerByIdCard(String idCard); 19 | 20 | 21 | /** 22 | * 搜索客人信息 23 | * @param name 姓名 24 | * @param phoneNo 手机号 25 | * @param idCard 身份证 26 | * @return 客人信息 27 | */ 28 | List findCustomers(String name,String phoneNo,String idCard); 29 | 30 | 31 | /** 32 | * 更新客人信息 33 | * @param customer 客人新信息 34 | * @return 更新后客人信息 35 | */ 36 | Customer update(Customer customer); 37 | 38 | /** 39 | * 删除客人信息 40 | * @param id 客人ID 41 | */ 42 | void delete(Integer id); 43 | 44 | /** 45 | * 查找客人信息 46 | * @param id 客人ID 47 | * @return 客人信息 48 | */ 49 | Customer findOne(Integer id); 50 | } 51 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/repository/CustomerRepository.java: -------------------------------------------------------------------------------- 1 | package com.hotel.repository; 2 | 3 | import com.hotel.entity.Customer; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.query.Param; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author guangyong.yang 13 | * @date 2019-01-20 14 | * @description 15 | */ 16 | @Repository 17 | public interface CustomerRepository extends JpaRepository { 18 | /** 19 | * 根据条件查找客人 20 | * @param name 客人姓名 21 | * @param phoneNo 手机号码 22 | * @param idCard 身份证编号 23 | * @return 客人列表 24 | */ 25 | @Query("select h from Customer h where h.name like %:name% and h.phoneNo like %:phoneNo% and h.idCard like %:idCard%") 26 | List findCustomers(@Param("name") String name, @Param("phoneNo") String phoneNo, @Param("idCard") String idCard); 27 | 28 | 29 | /** 30 | * 根据身份证号查询客人信息 31 | * @param idCard 身份证号 32 | * @return 客人信息 33 | */ 34 | List findCustomersByIdCard(String idCard); 35 | } 36 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/entity/Charge.java: -------------------------------------------------------------------------------- 1 | package com.hotel.entity; 2 | 3 | import com.hotel.constant.RoomType; 4 | import lombok.Data; 5 | 6 | import javax.persistence.*; 7 | import java.io.Serializable; 8 | import java.util.Date; 9 | 10 | /** 11 | * @author guangyong.yang 12 | * @date 2019-01-16 13 | * 定价 14 | */ 15 | @Data 16 | @Entity 17 | @Table(name="charge") 18 | public class Charge implements Serializable { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Integer id; 23 | 24 | 25 | /** 26 | * 客房 27 | */ 28 | @ManyToOne(optional = false) 29 | private Room room; 30 | 31 | 32 | /** 33 | * 时间数量 34 | */ 35 | @Column 36 | private Integer count; 37 | 38 | /** 39 | * 时间单位 40 | */ 41 | @Column 42 | @Enumerated(EnumType.STRING) 43 | private RoomType timeUnit; 44 | 45 | /** 46 | * 金额(单位元) 47 | */ 48 | @Column 49 | private float money; 50 | 51 | /** 52 | * 起始日期 53 | */ 54 | @Column 55 | @Temporal(TemporalType.TIMESTAMP) 56 | private Date startDate; 57 | 58 | /** 59 | * 截止日期 60 | */ 61 | @Column 62 | @Temporal(TemporalType.TIMESTAMP) 63 | private Date endDate; 64 | } 65 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/CustomerDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import com.hotel.annotation.LoginGroup; 4 | import com.hotel.annotation.UpdateCustomerGroup; 5 | import lombok.Data; 6 | import org.hibernate.validator.constraints.Length; 7 | import org.hibernate.validator.constraints.NotEmpty; 8 | 9 | import javax.validation.constraints.Pattern; 10 | import java.util.List; 11 | 12 | /** 13 | * @author guangyong.yang 14 | * @date 2019-01-21 15 | * @description 16 | */ 17 | @Data 18 | public class CustomerDto { 19 | private Integer id; 20 | 21 | @NotEmpty(groups = {LoginGroup.class, UpdateCustomerGroup.class},message = "客人姓名必须填写") 22 | @Length(max = 80) 23 | private String name; 24 | 25 | @NotEmpty(groups = {LoginGroup.class, UpdateCustomerGroup.class},message = "身份证号必须填写") 26 | @Pattern(groups = LoginGroup.class,regexp = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)",message = "请输入正确的身份证号") 27 | @Length(max = 18) 28 | private String idCard; 29 | 30 | @NotEmpty(groups = {LoginGroup.class, UpdateCustomerGroup.class},message = "客人手机号必须填写") 31 | @Pattern(groups = LoginGroup.class,regexp = "^[1][3,4,5,7,8][0-9]{9}$",message = "请输入正确的手机号码") 32 | @Length(max = 11) 33 | private String phoneNo; 34 | 35 | private List roomNos; 36 | private String comment; 37 | } 38 | -------------------------------------------------------------------------------- /hotel-front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hotel-front", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^6.0.3", 15 | "@angular/common": "^6.0.3", 16 | "@angular/compiler": "^6.0.3", 17 | "@angular/core": "^6.0.3", 18 | "@angular/forms": "^6.0.3", 19 | "@angular/platform-browser": "^6.0.3", 20 | "@angular/platform-browser-dynamic": "^6.0.3", 21 | "@angular/router": "^6.0.3", 22 | "core-js": "^2.5.4", 23 | "ng-zorro-antd": "^1.1.1", 24 | "rxjs": "^6.0.0", 25 | "tslib": "^1.9.0", 26 | "zone.js": "~0.8.26" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.6.5", 30 | "@angular/cli": "~6.0.5", 31 | "@angular/compiler-cli": "^6.0.3", 32 | "@angular/language-service": "^6.0.3", 33 | "@types/jasmine": "~2.8.6", 34 | "@types/jasminewd2": "~2.0.3", 35 | "@types/node": "~8.9.4", 36 | "codelyzer": "~4.2.1", 37 | "jasmine-core": "~2.99.1", 38 | "jasmine-spec-reporter": "~4.2.1", 39 | "karma": "^3.1.4", 40 | "karma-chrome-launcher": "~2.2.0", 41 | "karma-coverage-istanbul-reporter": "~1.4.2", 42 | "karma-jasmine": "~1.1.2", 43 | "karma-jasmine-html-reporter": "^0.2.2", 44 | "protractor": "^5.4.0", 45 | "ts-node": "~5.0.1", 46 | "tslint": "~5.9.1", 47 | "typescript": "~2.7.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/config/Swagger2Configuration.java: -------------------------------------------------------------------------------- 1 | package com.hotel.config; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import springfox.documentation.builders.ApiInfoBuilder; 7 | import springfox.documentation.builders.PathSelectors; 8 | import springfox.documentation.builders.RequestHandlerSelectors; 9 | import springfox.documentation.service.ApiInfo; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spring.web.plugins.Docket; 12 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 13 | 14 | /** 15 | * @author guangyong.yang 16 | * @date 2019-01-21 17 | */ 18 | @Configuration 19 | @EnableSwagger2 20 | public class Swagger2Configuration { 21 | 22 | @Bean 23 | public Docket createRestApi() { 24 | return new Docket(DocumentationType.SWAGGER_2) 25 | //协议,http或https 26 | .protocols(Sets.newHashSet("http")) 27 | .apiInfo(apiInfo()) 28 | .select() 29 | //一定要写对,会在这个路径下扫描controller定义 30 | .apis(RequestHandlerSelectors.basePackage("com.hotel.controller")) 31 | .paths(PathSelectors.any()) 32 | .build(); 33 | } 34 | 35 | private ApiInfo apiInfo() { 36 | return new ApiInfoBuilder() 37 | .title("REST接口定义") 38 | .version("1.0") 39 | .description("旅馆RestFul接口文档") 40 | .build(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /hotel-front/src/app/logout/logout.component.css: -------------------------------------------------------------------------------- 1 | .ant-advanced-search-form { 2 | padding: 10px; 3 | background: #ffffff; 4 | border-radius: 6px; 5 | min-width: 600px; 6 | } 7 | 8 | 9 | .search-result-list { 10 | margin:5px 5px; 11 | border-radius: 6px; 12 | background-color: #ffffff; 13 | min-height: 200px; 14 | text-align: center; 15 | } 16 | 17 | [nz-form-label] { 18 | overflow: visible; 19 | } 20 | 21 | button { 22 | margin-left: 8px; 23 | } 24 | 25 | 26 | :host ::ng-deep .ant-table-expanded-row > td:last-child { 27 | padding: 0 48px 0 8px; 28 | } 29 | 30 | :host ::ng-deep .ant-table-expanded-row > td:last-child .ant-table-thead th { 31 | border-bottom: 1px solid #ffffff; 32 | } 33 | 34 | :host ::ng-deep .ant-table-expanded-row > td:last-child .ant-table-thead th:first-child { 35 | padding-left: 0; 36 | } 37 | 38 | :host ::ng-deep .ant-table-expanded-row > td:last-child .ant-table-row td:first-child { 39 | padding-left: 0; 40 | } 41 | 42 | :host ::ng-deep .ant-table-expanded-row .ant-table-row:last-child td { 43 | border: none; 44 | } 45 | 46 | :host ::ng-deep .ant-table-expanded-row .ant-table-thead > tr > th { 47 | background: none; 48 | } 49 | 50 | :host ::ng-deep .table-operation a.operation { 51 | margin-right: 24px; 52 | } 53 | 54 | .logout div { 55 | margin-top:10px; 56 | } 57 | 58 | .logout div.title{ 59 | text-align: right; 60 | font-size: medium; 61 | } 62 | 63 | .logout div div:last-child{ 64 | font-size: small; 65 | margin-left: 20px; 66 | border-bottom: 1px dashed #fbfbfb; 67 | } 68 | 69 | #logout:enabled { 70 | background-color: #40a9ff; 71 | } 72 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/util/BeanUtils.java: -------------------------------------------------------------------------------- 1 | package com.hotel.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.BeanWrapper; 5 | import org.springframework.beans.BeanWrapperImpl; 6 | import org.springframework.util.StringUtils; 7 | 8 | import java.beans.FeatureDescriptor; 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.util.Objects; 12 | import java.util.stream.Stream; 13 | 14 | /** 15 | * @author guangyong.yang 16 | * @date 2019-01-20 17 | * @description 18 | */ 19 | @Slf4j 20 | public class BeanUtils { 21 | 22 | @SuppressWarnings("unchecked") 23 | public static T getFieldValue(Object obj,String filedName) { 24 | if (Objects.isNull(obj) || Objects.isNull(filedName)) { 25 | return null; 26 | } 27 | 28 | String methodName = "get" + StringUtils.capitalize(filedName); 29 | Method method; 30 | try { 31 | method = obj.getClass().getMethod(methodName); 32 | return (T)method.invoke(obj); 33 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 34 | log.error(e.getMessage(),e); 35 | } 36 | return null; 37 | } 38 | 39 | public static String[] getNullPropertyNames(Object source) { 40 | final BeanWrapper wrappedSource = new BeanWrapperImpl(source); 41 | return Stream.of(wrappedSource.getPropertyDescriptors()) 42 | .map(FeatureDescriptor::getName) 43 | .filter(propertyName -> wrappedSource.getPropertyValue(propertyName) == null) 44 | .toArray(String[]::new); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/dto/RoomDto.java: -------------------------------------------------------------------------------- 1 | package com.hotel.dto; 2 | 3 | import com.hotel.annotation.AddRoomGroup; 4 | import com.hotel.annotation.LoginGroup; 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import lombok.Data; 7 | import org.hibernate.validator.constraints.Length; 8 | import org.hibernate.validator.constraints.NotEmpty; 9 | import org.springframework.format.annotation.DateTimeFormat; 10 | 11 | import javax.validation.Valid; 12 | import javax.validation.constraints.NotNull; 13 | import java.util.Date; 14 | import java.util.List; 15 | 16 | /** 17 | * @author guangyong.yang 18 | * @date 2019-01-21 19 | */ 20 | @Data 21 | public class RoomDto { 22 | private Integer id; 23 | 24 | @NotEmpty(groups ={LoginGroup.class, AddRoomGroup.class}, message = "客房号必须填写") 25 | @Length(max = 20) 26 | private String roomNo; 27 | 28 | @NotEmpty(groups =AddRoomGroup.class, message = "客房类型必须填写") 29 | private String type; 30 | 31 | @NotNull(groups =LoginGroup.class, message = "入住日期必须填写") 32 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 33 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 34 | private Date checkInDate; 35 | 36 | @NotNull(groups =LoginGroup.class, message = "退房日期必须填写") 37 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //入参 38 | @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss") //出参 39 | private Date checkOutDate; 40 | 41 | 42 | private Integer status; 43 | 44 | private List beds; 45 | 46 | private List charges; 47 | 48 | @Valid 49 | private List customers; 50 | 51 | private String money; 52 | 53 | private List incomes; 54 | 55 | private Boolean updatingBed; 56 | } 57 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/constant/RetCode.java: -------------------------------------------------------------------------------- 1 | package com.hotel.constant; 2 | 3 | /** 4 | * @author guangyong.yang 5 | * @date 2019-01-21 6 | * @description 7 | */ 8 | public class RetCode { 9 | //成功消息 10 | public static String RETCODE_20001 = "20001"; 11 | public static String RETCODE_20001_MSG = "登记成功"; 12 | 13 | public static String RETCODE_20002 = "20002"; 14 | public static String RETCODE_20002_MSG = "退房成功"; 15 | 16 | public static String RETCODE_20003 = "20003"; 17 | public static String RETCODE_20003_MSG = "查询到数据"; 18 | 19 | public static String RETCODE_20004 = "20004"; 20 | public static String RETCODE_20004_MSG = "更新成功"; 21 | 22 | public static String RETCODE_20005 = "20005"; 23 | public static String RETCODE_20005_MSG = "新增成功"; 24 | //客户端错误 25 | public static String RETCODE_40001 = "40001"; 26 | public static String RETCODE_40002 = "40002"; 27 | public static String RETCODE_40003 = "40003"; 28 | 29 | // 客房状态错误消息 30 | public static String RETCODE_40004 = "40004"; 31 | 32 | public static String RETCODE_40005 = "40005"; 33 | public static String RETCODE_40006 = "40006"; 34 | public static String RETCODE_40007 = "40007"; 35 | public static String RETCODE_40008 = "40008"; 36 | public static String RETCODE_40009 = "40009"; 37 | public static String RETCODE_40010 = "40010"; 38 | public static String RETCODE_40011 = "40011"; 39 | public static String RETCODE_40012 = "40012"; 40 | public static String RETCODE_40013 = "40013"; 41 | public static String RETCODE_40014 = "40014"; 42 | public static String RETCODE_40015 = "40015"; 43 | public static String RETCODE_40016 = "40016"; 44 | public static String RETCODE_40017 = "40017"; 45 | public static String RETCODE_40018 = "40018"; 46 | public static String RETCODE_40099 = "40099"; 47 | //服务端错误 48 | } 49 | -------------------------------------------------------------------------------- /hotel-front/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; 3 | import { NgModule } from '@angular/core'; 4 | import {ReactiveFormsModule, FormsModule} from '@angular/forms'; 5 | import {NgZorroAntdModule, NZ_I18N, zh_CN} from 'ng-zorro-antd'; 6 | import { AppComponent } from './app.component'; 7 | import { SiderComponent } from './sider/sider.component'; 8 | import {HttpClientModule} from '@angular/common/http'; 9 | import { LoginComponent } from './login/login.component'; 10 | import { LogoutComponent } from './logout/logout.component'; 11 | import { RoomNoSearchComponent } from './room-no-search/room-no-search.component'; 12 | import { registerLocaleData } from '@angular/common'; 13 | import zh from '@angular/common/locales/zh'; 14 | import { AppRoutingModule } from './app-routing.module'; 15 | import { RoomManagementComponent } from './room-management/room-management.component'; 16 | import { CustomerManagementComponent } from './customer-management/customer-management.component'; 17 | import { RoomManagementBedComponent } from './room-management-bed/room-management-bed.component'; 18 | import { RoomManagementRoomComponent } from './room-management-room/room-management-room.component'; 19 | 20 | registerLocaleData(zh); 21 | 22 | @NgModule({ 23 | declarations: [ 24 | AppComponent, 25 | SiderComponent, 26 | LoginComponent, 27 | LogoutComponent, 28 | RoomNoSearchComponent, 29 | RoomManagementComponent, 30 | CustomerManagementComponent, 31 | RoomManagementBedComponent, 32 | RoomManagementRoomComponent, 33 | ], 34 | imports: [ 35 | BrowserModule, 36 | FormsModule, 37 | ReactiveFormsModule, 38 | BrowserAnimationsModule, 39 | NgZorroAntdModule, 40 | AppRoutingModule, 41 | HttpClientModule, 42 | ], 43 | providers: [{ provide: NZ_I18N, useValue: zh_CN }], 44 | bootstrap: [AppComponent] 45 | }) 46 | export class AppModule { } 47 | -------------------------------------------------------------------------------- /hotel-front/src/app/util/Utils.ts: -------------------------------------------------------------------------------- 1 | export class Utils { 2 | /** 3 | * 定义原生使用占位符的方法,格式化数据 4 | * @author sky 5 | * @date 2018-07-09 6 | * @returns string 7 | */ 8 | public static format = function (str, values): string { 9 | // 数据长度为空,则直接返回 10 | if (values.length === 0) { 11 | return str; 12 | } 13 | 14 | // 使用正则表达式,循环替换占位符数据 15 | let result = str; 16 | for (let i = 0; i < values.length; i++) { 17 | result = result.replace(new RegExp('\\{' + i + '\\}', 'g'), values[i]); 18 | return result; 19 | } 20 | }; 21 | 22 | /** 23 | * 时间格式化处理 24 | * @param fmt 格式 25 | * @param date 日期 26 | */ 27 | public static dateFormat = function (date, fmt: string = 'yyyy-MM-dd hh:mm:ss'): string { 28 | const o = { 29 | 'M+' : date.getMonth() + 1, // 月份 30 | 'd+' : date.getDate(), // 日 31 | 'h+' : date.getHours(), // 小时 32 | 's+' : date.getSeconds(), // 秒 33 | 'm+' : date.getMinutes(), // 分 34 | 'q+' : Math.floor((date.getMonth() + 3) / 3), // 季度 35 | 'S' : date.getMilliseconds() // 毫秒 36 | }; 37 | 38 | if (/(y+)/.test(fmt)) { 39 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 40 | } 41 | 42 | for (const k in o) { 43 | if (new RegExp('(' + k + ')').test(fmt)) { 44 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 45 | } 46 | } 47 | return fmt; 48 | }; 49 | 50 | 51 | // 字符串转日期格式,strDate要转为日期格式的字符串 52 | public static getDate(strDate): Date { 53 | const st = strDate; 54 | let a = st.split(' '); 55 | let b = a[0].split('-'); 56 | let c = a[1].split(':'); 57 | const date = new Date(b[0], b[1] - 1, b[2], c[0], c[1], c[2]); 58 | return date; 59 | } 60 | 61 | public static isString(str): boolean { 62 | return (typeof str == 'string') && str.constructor === String; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/impl/CustomerServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service.impl; 2 | 3 | import com.hotel.entity.Customer; 4 | import com.hotel.repository.CustomerRepository; 5 | import com.hotel.service.CustomerService; 6 | import com.hotel.util.BeanUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | /** 14 | * @author guangyong.yang 15 | * @date 2019-01-21 16 | * @description 17 | */ 18 | @Service 19 | public class CustomerServiceImpl implements CustomerService { 20 | 21 | @Autowired 22 | private CustomerRepository customerRepository; 23 | 24 | @Override 25 | public List findCustomerByIdCard(String idCard) { 26 | return customerRepository.findCustomersByIdCard(idCard); 27 | } 28 | 29 | @Override 30 | public List findCustomers(String name, String phoneNo, String idCard) { 31 | if (Objects.isNull(name)) { 32 | name = ""; 33 | } 34 | 35 | if (Objects.isNull(phoneNo)) { 36 | phoneNo = ""; 37 | } 38 | 39 | if (Objects.isNull(idCard)) { 40 | idCard = ""; 41 | } 42 | return customerRepository.findCustomers(name,phoneNo,idCard); 43 | } 44 | 45 | @Override 46 | public Customer update(Customer customer) { 47 | Customer currentInstance = customerRepository.findOne(customer.getId()); 48 | //支持部分更新 49 | String[] nullPropertyNames = BeanUtils.getNullPropertyNames(customer); 50 | org.springframework.beans.BeanUtils.copyProperties(customer, currentInstance, nullPropertyNames); 51 | return customerRepository.save(currentInstance); 52 | } 53 | 54 | @Override 55 | public void delete(Integer id) { 56 | customerRepository.delete(id); 57 | } 58 | 59 | 60 | @Override 61 | public Customer findOne(Integer id) { 62 | return customerRepository.findOne(id); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/entity/Room.java: -------------------------------------------------------------------------------- 1 | package com.hotel.entity; 2 | 3 | import com.hotel.constant.RoomStatus; 4 | import com.hotel.constant.RoomType; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import java.util.Date; 10 | import java.util.List; 11 | 12 | /** 13 | * @author guangyong.yang 14 | * @date 2019-01-15 15 | * 房间 16 | */ 17 | @Getter 18 | @Setter 19 | @Entity 20 | @Table(name = "room") 21 | public class Room { 22 | /** 23 | * id 24 | */ 25 | @Id 26 | @Column 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) 28 | private Integer id; 29 | 30 | 31 | /** 32 | * 房间号 33 | */ 34 | @Column(length = 20,unique = true) 35 | private String roomNo; 36 | 37 | 38 | /** 39 | * 房间类型 40 | */ 41 | @Column(nullable = false) 42 | @Enumerated(EnumType.STRING) 43 | private RoomType type; 44 | 45 | 46 | /** 47 | * 入住日期 48 | */ 49 | @Column 50 | @Temporal(TemporalType.TIMESTAMP) 51 | private Date checkInDate; 52 | 53 | 54 | /** 55 | * 退房日期 56 | */ 57 | @Column 58 | @Temporal(TemporalType.TIMESTAMP) 59 | private Date checkOutDate; 60 | 61 | 62 | /** 63 | * 客房状态 64 | */ 65 | @Column 66 | @Enumerated(EnumType.STRING) 67 | private RoomStatus status; 68 | 69 | /** 70 | * 床 71 | */ 72 | 73 | @OneToMany(cascade = CascadeType.ALL,mappedBy = "room") 74 | private List beds; 75 | 76 | 77 | /** 78 | * 顾客 79 | */ 80 | @ManyToMany(cascade = CascadeType.ALL) 81 | @JoinTable(name = "room_customer",inverseJoinColumns = @JoinColumn(name = "customer_id"),joinColumns = @JoinColumn(name = "room_id")) 82 | private List customers; 83 | 84 | 85 | /** 86 | * 房价 87 | */ 88 | @OneToMany(mappedBy = "room",cascade = CascadeType.ALL) 89 | private List charges; 90 | 91 | /** 92 | * 收入记录 93 | */ 94 | @OneToMany(mappedBy = "room",cascade = CascadeType.ALL) 95 | private List incomes; 96 | 97 | } 98 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-no-search/room-no-search.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, forwardRef, Input, OnInit, Output} from '@angular/core'; 2 | import {Observable, Subject} from 'rxjs'; 3 | import {Room} from '../dto/Room'; 4 | import {debounceTime, distinctUntilChanged, switchMap} from 'rxjs/operators'; 5 | import {RoomService} from '../room.service'; 6 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; 7 | 8 | @Component({ 9 | selector: 'app-room-no-search', 10 | templateUrl: './room-no-search.component.html', 11 | styleUrls: ['./room-no-search.component.css'], 12 | providers: [ 13 | { 14 | provide: NG_VALUE_ACCESSOR, 15 | multi: true, 16 | useExisting: forwardRef(() => RoomNoSearchComponent), 17 | } 18 | ] 19 | }) 20 | 21 | export class RoomNoSearchComponent implements OnInit, ControlValueAccessor { 22 | rooms$: Observable; 23 | private searchTerms = new Subject(); 24 | 25 | @Input() private _value: string; 26 | @Input() showUnused: boolean; 27 | 28 | constructor(private roomService: RoomService) { 29 | this.showUnused = true; 30 | } 31 | 32 | 33 | search(term: string): void { 34 | this.propagateChange(term); 35 | this.searchTerms.next(term); 36 | } 37 | 38 | ngOnInit() { 39 | this.rooms$ = this.searchTerms.pipe( 40 | // wait 300ms after each keystroke before considering the term 41 | debounceTime(300), 42 | 43 | // ignore new term if same as previous term 44 | distinctUntilChanged(), 45 | 46 | // switch to new search observable each time the term changes 47 | switchMap( (term: string) => this.roomService.searchRooms([{'key': 'roomNo', 'value': term}])), 48 | ); 49 | 50 | } 51 | 52 | get value(): string { 53 | return this._value; 54 | } 55 | 56 | set value(value: string) { 57 | this._value = value; 58 | this.propagateChange(this._value); 59 | } 60 | 61 | propagateChange = (_: any) => {}; 62 | 63 | registerOnChange(fn: any): void { 64 | this.propagateChange = fn; 65 | } 66 | 67 | registerOnTouched(fn: any): void { 68 | } 69 | 70 | setDisabledState(isDisabled: boolean): void { 71 | } 72 | 73 | writeValue(value: any): void { 74 | if (value !== undefined) { 75 | this.value = value; 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-bed/room-management-bed.component.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 床名称 11 | 床型 12 | 床大小 13 | 操作 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | {{data.name}} 23 | 24 | 25 | 26 | 27 |
28 |
29 | 30 | 31 | 32 | {{data.type}} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {{data.size}} 45 | 46 | 47 | 48 | 49 | 50 | 51 |
52 | 53 | 更改 54 | 55 | 56 | 保存 57 | 58 | 取消 59 | 60 | 61 | 62 | 63 | 64 | 删除 65 | 66 | 67 |
68 | 69 | 70 | 71 |
72 |
73 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-room/room-management-room.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
客房号:
4 |
{{room.roomNo}}
5 |
6 |
7 |
房型
8 |
9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
数量
18 |
19 | 20 | {{charge.timeUnit.substr(0,charge.timeUnit.length-1)}} 21 |
{{message['count']}}
22 |
23 |
24 |
25 |
金额
26 |
27 | 28 | 29 |
{{message['money']}}
30 |
31 |
32 |
33 |
起始日期
34 |
35 | 36 | 37 | 38 |
{{message['startDate']}}
39 |
40 | 41 | 42 | 43 |
{{message['startDate']}}
44 |
45 |
46 |
47 |
48 |
结束日期
49 |
50 | 51 | 52 | 53 |
{{message['endDate']}}
54 |
55 | 56 | 57 |
{{message['endDate']}}
58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/exception/RestExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.hotel.exception; 2 | 3 | import com.hotel.constant.RetCode; 4 | import com.hotel.dto.ResultDto; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.validation.BindingResult; 9 | import org.springframework.validation.ObjectError; 10 | import org.springframework.web.bind.MethodArgumentNotValidException; 11 | import org.springframework.web.bind.annotation.ControllerAdvice; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.ResponseBody; 14 | 15 | import javax.validation.ConstraintViolation; 16 | import javax.validation.ConstraintViolationException; 17 | import java.util.List; 18 | 19 | /** 20 | * @author guangyong.yang 21 | * @date 2019-01-05 22 | */ 23 | 24 | @ControllerAdvice 25 | @Slf4j 26 | public class RestExceptionHandler { 27 | 28 | @ExceptionHandler({ConstraintViolationException.class, MethodArgumentNotValidException.class}) 29 | @ResponseBody 30 | public ResponseEntity handler(Exception ex) { 31 | 32 | StringBuilder sb = new StringBuilder(); 33 | if (ex instanceof ConstraintViolationException) { 34 | ConstraintViolationException ex2 = (ConstraintViolationException)ex; 35 | for (ConstraintViolation violation : ex2.getConstraintViolations()) { 36 | sb.append(violation.getMessage()); 37 | 38 | } 39 | } else if(ex instanceof MethodArgumentNotValidException){ 40 | MethodArgumentNotValidException ex2 = (MethodArgumentNotValidException)ex; 41 | BindingResult bindingResult = ex2.getBindingResult(); 42 | if (bindingResult.hasErrors()){ 43 | List allErrors = bindingResult.getAllErrors(); 44 | for(ObjectError error: allErrors) { 45 | sb.append(error.getDefaultMessage()).append(";"); 46 | } 47 | } 48 | } 49 | 50 | 51 | log.trace(ex.getMessage(),ex); 52 | return new ResponseEntity<>(ResultDto.builder().code(RetCode.RETCODE_40006).message(sb.toString()).success(false).build(),HttpStatus.BAD_REQUEST); 53 | } 54 | 55 | @ExceptionHandler(value = CompareException.class) 56 | @ResponseBody 57 | public ResponseEntity handleCompareException(CompareException e) 58 | { 59 | log.error(e.getMessage(), e); 60 | return new ResponseEntity<>(ResultDto.builder().code(RetCode.RETCODE_40007).message(e.getMessage()).success(false).build(),HttpStatus.BAD_REQUEST); 61 | 62 | } 63 | 64 | @ExceptionHandler(value = Exception.class) 65 | @ResponseBody 66 | public ResponseEntity handleOtherException(Exception e) 67 | { 68 | log.error(e.getMessage(), e); 69 | return new ResponseEntity<>(ResultDto.builder().code(RetCode.RETCODE_40099).message(e.getMessage()).success(false).build(),HttpStatus.BAD_REQUEST); 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /hotel-front/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /hotel-front/src/app/logout/logout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { 3 | FormBuilder, 4 | FormGroup, Validators 5 | } from '@angular/forms'; 6 | import {CustomerService} from '../customer.service'; 7 | import {RoomService} from '../room.service'; 8 | import {SearchCondition} from '../dto/SearchCondition'; 9 | import {Logout} from '../dto/Logout'; 10 | import {NzNotificationService} from 'ng-zorro-antd'; 11 | 12 | @Component({ 13 | selector: 'app-logout', 14 | templateUrl: './logout.component.html', 15 | styleUrls: ['./logout.component.css'] 16 | }) 17 | export class LogoutComponent implements OnInit { 18 | 19 | validateForm: FormGroup; 20 | isCollapse = true; 21 | rooms = []; 22 | isVisible = false; 23 | logoutDto: Logout; 24 | 25 | resetForm(e: MouseEvent): void { 26 | e.preventDefault(); 27 | this.validateForm.reset(); 28 | } 29 | 30 | 31 | toggleCollapse(): void { 32 | this.isCollapse = !this.isCollapse; 33 | } 34 | 35 | submitForm = ($event, value) => { 36 | $event.preventDefault(); 37 | 38 | const conditions = []; 39 | 40 | for (const key in this.validateForm.controls) { 41 | if (this.validateForm.controls[key].value !== null && this.validateForm.controls[key].value.trim() !== '') { 42 | const searchCondition = new SearchCondition(); 43 | searchCondition.key = key; 44 | searchCondition.value = this.validateForm.controls[key].value; 45 | conditions.push(searchCondition); 46 | } 47 | 48 | this.validateForm.controls[ key ].markAsDirty(); 49 | this.validateForm.controls[ key ].updateValueAndValidity(); 50 | } 51 | 52 | this.roomService.searchRooms(conditions).subscribe(rooms => {this.rooms = rooms. 53 | map( (room) => { room['expand'] = false; return room; }); }); 54 | 55 | } 56 | 57 | logout(roomId: number) { 58 | this.roomService.logout(roomId).subscribe((result) => { 59 | if (result !== undefined && result.success !== undefined) { 60 | if (result.success) { 61 | // @ts-ignore 62 | this.logoutDto = result.data; 63 | this.isVisible = true; 64 | this.rooms.forEach((room) => { 65 | if (room.id === roomId) { 66 | room.checkInDate = ''; 67 | room.checkOutDate = ''; 68 | room.customers = []; 69 | } 70 | ; 71 | return room; 72 | }); 73 | this.notification.create('success', '退房成功', '入住时间:' + this.logoutDto.interval 74 | + this.logoutDto.type.substr(0, this.logoutDto.type.length - 1) 75 | + ';总房费:' + this.logoutDto.sum + '元', {nzDuration: 0}); 76 | } else { 77 | this.notification.create('error', '退房失败', result.message, {nzDuration: 0}); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | confirmLogout(){ 84 | this.isVisible = false; 85 | } 86 | 87 | constructor( 88 | private fb: FormBuilder, 89 | private customerService: CustomerService, 90 | private roomService: RoomService, 91 | private notification: NzNotificationService) { 92 | this.validateForm = this.fb.group({ 93 | name: [ ''], 94 | idCard: ['', [Validators.pattern(/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/)]], 95 | roomNo: [''], 96 | phoneNo: ['', [Validators.pattern(/(^[1][3,4,5,7,8][0-9]{9}$)/)]], 97 | }); 98 | } 99 | 100 | ngOnInit() { 101 | 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management/room-management.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 14 | 15 | 16 | 客房号 17 | 状态 18 | 房型 19 | 单价 20 | 入住日期 21 | 退房日期 22 | 床铺数 23 | 客人数 24 | 总收入 25 | 操作 26 | 可预订 27 | 28 | 29 | 30 | 31 | {{data.roomNo}} 32 | 空房 33 | 有客 34 | 不可订 35 | 36 | {{data.type}} 37 | {{data.money}} 38 | {{data.checkInDate | date: (data.type === '小时房' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd')}} 39 | {{data.checkOutDate | date: (data.type === '小时房' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd')}} 40 | {{data.beds.length}} 41 | {{data.customers.length}} 42 | {{data.sumIncoming}} 43 |
44 |
45 | 46 | 48 | 49 | 50 |
51 | 52 | 53 |
54 | 55 | 61 |
62 |
客房号
63 |
64 |
{{message['roomNo']}}
65 |
66 |
67 |
68 |
房型
69 |
70 | 71 | 72 | 73 | 74 | 75 |
{{message['type']}}
76 |
77 |
78 |
79 | -------------------------------------------------------------------------------- /hotel-front/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | import 'core-js/es7/reflect'; 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /hotel-front/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 姓名 4 | 5 | 6 | 7 | 8 | 请填写客人姓名! 9 | 10 | 11 | 客人姓名重复! 12 | 13 | 14 | 校验中.... 15 | 16 | 17 | 18 | 19 | 20 | 21 | 身份证号 22 | 23 | 24 | 25 | 26 | 请填写有效身份证编号! 27 | 28 | 29 | 请填写客人身份证号! 30 | 31 | 32 | 33 | 34 | 35 | 36 | 手机号 37 | 38 | 39 | 40 | 41 | 请填写正确的手机号码! 42 | 43 | 44 | 请填写客人手机号码! 45 | 46 | 47 | 48 | 49 | 50 | 51 | 客房号 52 | 53 | 54 | 55 | 56 | 请填写客房号! 57 | 58 | 59 | 60 | 61 | 62 | 63 | 日期 64 | 65 | 66 | 73 | 74 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 备注 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | -------------------------------------------------------------------------------- /hotel-front/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "hotel-front": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/hotel-front", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets", 24 | { 25 | "glob": "**/*", 26 | "input": "./node_modules/@ant-design/icons-angular/src/inline-svg/", 27 | "output": "/assets/" 28 | } 29 | ], 30 | "styles": [ 31 | "node_modules/ng-zorro-antd/src/ng-zorro-antd.min.css", 32 | "src/styles.css" 33 | ], 34 | "scripts": [] 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "extractCss": true, 48 | "namedChunks": false, 49 | "aot": true, 50 | "extractLicenses": true, 51 | "vendorChunk": false, 52 | "buildOptimizer": true, 53 | "budgets": [ 54 | { 55 | "type": "initial", 56 | "maximumWarning": "2mb", 57 | "maximumError": "5mb" 58 | } 59 | ] 60 | } 61 | } 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "options": { 66 | "browserTarget": "hotel-front:build" 67 | }, 68 | "configurations": { 69 | "production": { 70 | "browserTarget": "hotel-front:build:production" 71 | } 72 | } 73 | }, 74 | "extract-i18n": { 75 | "builder": "@angular-devkit/build-angular:extract-i18n", 76 | "options": { 77 | "browserTarget": "hotel-front:build" 78 | } 79 | }, 80 | "test": { 81 | "builder": "@angular-devkit/build-angular:karma", 82 | "options": { 83 | "main": "src/test.ts", 84 | "polyfills": "src/polyfills.ts", 85 | "tsConfig": "src/tsconfig.spec.json", 86 | "karmaConfig": "src/karma.conf.js", 87 | "styles": [ 88 | "node_modules/ng-zorro-antd/src/ng-zorro-antd.min.css", 89 | "src/styles.css" 90 | ], 91 | "scripts": [], 92 | "assets": [ 93 | "src/favicon.ico", 94 | "src/assets" 95 | ] 96 | } 97 | }, 98 | "lint": { 99 | "builder": "@angular-devkit/build-angular:tslint", 100 | "options": { 101 | "tsConfig": [ 102 | "src/tsconfig.app.json", 103 | "src/tsconfig.spec.json" 104 | ], 105 | "exclude": [ 106 | "**/node_modules/**" 107 | ] 108 | } 109 | } 110 | } 111 | }, 112 | "hotel-front-e2e": { 113 | "root": "e2e/", 114 | "projectType": "application", 115 | "prefix": "", 116 | "architect": { 117 | "e2e": { 118 | "builder": "@angular-devkit/build-angular:protractor", 119 | "options": { 120 | "protractorConfig": "e2e/protractor.conf.js", 121 | "devServerTarget": "hotel-front:serve" 122 | }, 123 | "configurations": { 124 | "production": { 125 | "devServerTarget": "hotel-front:serve:production" 126 | } 127 | } 128 | }, 129 | "lint": { 130 | "builder": "@angular-devkit/build-angular:tslint", 131 | "options": { 132 | "tsConfig": "e2e/tsconfig.e2e.json", 133 | "exclude": [ 134 | "**/node_modules/**" 135 | ] 136 | } 137 | } 138 | } 139 | } 140 | }, 141 | "defaultProject": "hotel-front" 142 | } -------------------------------------------------------------------------------- /hotel-front/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit} from '@angular/core'; 2 | import {FormBuilder, FormControl, FormGroup, ValidationErrors, Validators} from '@angular/forms'; 3 | import {Observable, Observer, Subject} from 'rxjs'; 4 | import {Customer} from '../dto/Customer'; 5 | import { Utils} from '../util/Utils'; 6 | import {Room} from '../dto/Room'; 7 | import {RoomService} from '../room.service'; 8 | import {debounceTime, distinctUntilChanged, switchMap} from 'rxjs/operators'; 9 | 10 | @Component({ 11 | selector: 'app-login', 12 | templateUrl: './login.component.html', 13 | styleUrls: ['./login.component.css'] 14 | }) 15 | 16 | export class LoginComponent implements OnInit { 17 | 18 | validateForm: FormGroup; 19 | room: Room = new Room(); 20 | rooms$: Observable; 21 | private searchTerms = new Subject(); 22 | 23 | submitForm = ($event) => { 24 | $event.preventDefault(); 25 | 26 | const customer = new Customer(); 27 | const room = new Room(); 28 | 29 | for (const key in this.validateForm.controls) { 30 | if (key === 'rangeDate') { 31 | const valueArray = this.validateForm.controls[key].value; 32 | room.checkInDate = Utils.isString(valueArray[0]) ? valueArray[0] : Utils.dateFormat(valueArray[0]); 33 | room.checkOutDate = Utils.isString(valueArray[1]) ? valueArray[1] : Utils.dateFormat(valueArray[1]); 34 | } else if ( key === 'roomNo') { 35 | room.roomNo = this.validateForm.controls[key].value; 36 | } else { 37 | customer[key] = this.validateForm.controls[key].value; 38 | } 39 | 40 | this.validateForm.controls[ key ].markAsDirty(); 41 | this.validateForm.controls[ key ].updateValueAndValidity(); 42 | } 43 | 44 | room.customers = []; 45 | room.customers.push(customer); 46 | 47 | this.roomService.login(room).subscribe((result) => { 48 | if (result !== undefined && result.success) { 49 | this.resetForm($event); 50 | } 51 | }); 52 | 53 | } 54 | 55 | disabledDate = (current: Date): boolean => { 56 | const date = new Date(); 57 | return current < Utils.getDate(date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' 00:00:00'); 58 | } 59 | 60 | resetForm(e: MouseEvent): void { 61 | e.preventDefault(); 62 | this.validateForm.reset(); 63 | for (const key in this.validateForm.controls) { 64 | this.validateForm.controls[key].markAsPristine(); 65 | this.validateForm.controls[key].updateValueAndValidity(); 66 | } 67 | } 68 | 69 | idCardAsyncValidator = (control: FormControl) => Observable.create((observer: Observer) => { 70 | setTimeout( () => { 71 | /**身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X */ 72 | const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; 73 | if (reg.test(control.value) === false) { 74 | observer.next({ error: true, wrong: true }); 75 | } else { 76 | observer.next(null); 77 | } 78 | observer.complete(); 79 | }, 500); 80 | }) 81 | 82 | phoneNoAsyncValidator = (control: FormControl) => Observable.create((observer: Observer) => { 83 | setTimeout( () => { 84 | /**手机号第1位肯定是1,第2位是3,4,5,7,8其中一个,剩余的9位在0-9之间*/ 85 | const reg = /(^[1][3,4,5,7,8][0-9]{9}$)/; 86 | if (reg.test(control.value) === false) { 87 | observer.next({ error: true, wrong: true }); 88 | } else { 89 | observer.next(null); 90 | } 91 | observer.complete(); 92 | }, 500); 93 | }) 94 | 95 | 96 | onRoomNoChange() { 97 | this.room.roomNo = this.validateForm.controls['roomNo'].value; 98 | this.searchTerms.next(this.room.roomNo); 99 | } 100 | 101 | constructor(private fb: FormBuilder, private roomService: RoomService) { 102 | this.validateForm = this.fb.group({ 103 | name: [ '', [ Validators.required ]], 104 | idCard: ['', [ Validators.required] , [this.idCardAsyncValidator]], 105 | roomNo: ['', [ Validators.required]], 106 | phoneNo: ['', [Validators.required], [this.phoneNoAsyncValidator]], 107 | rangeDate: ['', [Validators.required]], 108 | comment: [''] 109 | }); 110 | } 111 | 112 | ngOnInit() { 113 | this.rooms$ = this.searchTerms.pipe( 114 | // wait 300ms after each keystroke before considering the term 115 | debounceTime(300), 116 | 117 | // ignore new term if same as previous term 118 | distinctUntilChanged(), 119 | 120 | // switch to new search observable each time the term changes 121 | switchMap( (term: string) => this.roomService.searchRooms([{'key': 'roomNo', 'value': term}])), 122 | ); 123 | this.rooms$.subscribe(rooms => { 124 | if (rooms != null && rooms.length === 1) { 125 | this.room = rooms[0]; 126 | } 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-bed/room-management-bed.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, forwardRef, Input, OnInit} from '@angular/core'; 2 | import {Bed} from '../dto/Bed'; 3 | import {RoomService} from '../room.service'; 4 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; 5 | import {Room} from '../dto/Room'; 6 | 7 | /** 8 | * 将isVisible进行双向绑定,这样外部组件在使用此组件时,可以控制显示与隐藏,否则在在此组件内隐藏之后,当再次点击时,由于外部组件的属性没有变化,不会将值再次传入此组件内, 9 | * 不会触发再次显示 10 | */ 11 | @Component({ 12 | selector: 'app-room-management-bed', 13 | templateUrl: './room-management-bed.component.html', 14 | styleUrls: ['./room-management-bed.component.css'], 15 | providers: [ 16 | { 17 | provide: NG_VALUE_ACCESSOR, 18 | multi: true, 19 | useExisting: forwardRef(() => RoomManagementBedComponent), 20 | } 21 | ] 22 | }) 23 | 24 | export class RoomManagementBedComponent implements OnInit, ControlValueAccessor { 25 | 26 | @Input() roomId: number; 27 | @Input() beds: Bed[]; 28 | @Input() private _isVisible: boolean; 29 | 30 | dataSet = []; 31 | editCache = {}; 32 | 33 | isOkLoading = false; 34 | 35 | startEdit(key: string): void { 36 | this.editCache[ key ].edit = true; 37 | } 38 | 39 | cancelEdit(key: string): void { 40 | this.editCache[ key ].edit = false; 41 | } 42 | 43 | saveEdit(key: number): void { 44 | const index = this.dataSet.findIndex(item => item.key === key); 45 | Object.assign(this.dataSet[ index ], this.editCache[ key ].data); 46 | this.editCache[ key ].edit = false; 47 | } 48 | 49 | add() { 50 | this.dataSet = [ ...this.dataSet, {key: this.dataSet.length, id: null, name: '', type: '单人床', size: ''}]; 51 | this.updateEditCache(true); 52 | } 53 | 54 | delete(key: number): void { 55 | this.dataSet = this.dataSet.filter(d => d.key !== key); 56 | delete this.editCache[key]; 57 | } 58 | 59 | updateEditCache(edit: boolean = false): void { 60 | this.dataSet.forEach(item => { 61 | if (!this.editCache[ item.key ]) { 62 | this.editCache[ item.key ] = { 63 | edit: edit, 64 | data: { ...item } 65 | }; 66 | } 67 | }); 68 | } 69 | 70 | constructor(private roomService: RoomService) { } 71 | 72 | ngOnInit() { 73 | 74 | } 75 | 76 | confirm() { 77 | // todo: 若没有变化,则无需调用后端 78 | let theSame = true; 79 | if (this.beds.length === this.dataSet.length) { 80 | for(let i = 0; i < this.beds.length; i++ ){ 81 | const bed = this.beds[i]; 82 | const index = this.dataSet.findIndex(data => data.id === bed.id); 83 | if (index !== -1) { 84 | const newBed = this.dataSet[index]; 85 | if (newBed.id === bed.id 86 | && newBed.name === bed.name 87 | && newBed.type === bed.type 88 | && newBed.size === bed.size) { 89 | 90 | } else { 91 | theSame = false; 92 | break; 93 | } 94 | } else { 95 | theSame = false; 96 | break; 97 | } 98 | } 99 | } else { 100 | theSame = false; 101 | } 102 | 103 | if (theSame) { 104 | this.isVisible = false; 105 | return; 106 | } 107 | 108 | this.isOkLoading = true; 109 | this.roomService.updateRoom({id: this.roomId, beds: this.dataSet,updatingBed: true} as Room).subscribe(result => { 110 | if (result !== undefined && result.success !== undefined && result.success) { 111 | this.beds.splice(0, this.beds.length); 112 | if (result.data != null && result.data.beds !== undefined && result.data.beds != null && result.data.beds.length > 0) { 113 | result.data.beds.forEach(bed => this.beds.push(bed)); 114 | } 115 | } 116 | this.isVisible = false; 117 | this.isOkLoading = false; 118 | }); 119 | } 120 | 121 | cancel() { 122 | this.isVisible = false; 123 | } 124 | 125 | get isVisible(): boolean { 126 | return this._isVisible; 127 | } 128 | 129 | set isVisible(value: boolean) { 130 | this._isVisible = value; 131 | this.propagateChange(this._isVisible); 132 | } 133 | 134 | propagateChange = (_: any) => {}; 135 | 136 | registerOnChange(fn: any): void { 137 | this.propagateChange = fn; 138 | } 139 | 140 | registerOnTouched(fn: any): void { 141 | } 142 | 143 | setDisabledState(isDisabled: boolean): void { 144 | } 145 | 146 | writeValue(value: any): void { 147 | if (value !== undefined) { 148 | // 外部组件,点击修改床铺时,value第一次是null,null时变显示了,此时dataset为空 149 | if (value == null || value) { 150 | this.init(); 151 | // 此处要复制,而不是引用,这样内部组件值的改变不会引响到组件外,不能使用this.dataSet = [...this.beds];,因为复制的是引用 152 | this.beds.forEach((item, index) => this.dataSet.push({key: index, ...item})); 153 | this.updateEditCache(); 154 | } 155 | this.isVisible = value; 156 | } 157 | } 158 | 159 | init(): void { 160 | this.dataSet = []; 161 | this.editCache = {}; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前后端分离示例demo-旅馆管理系统 2 | > 【目录】 3 | > [1.模型设计](#模型设计) 4 | > [2.业务流程](#业务流程) 5 | > [3.页面设计](#页面设计) 6 | > [4.功能设计](#功能设计) 7 | > [5.接口设计](#接口设计) 8 | > [6.开发计划](#开发计划) 9 | 10 | ## 模型设计 11 |   旅馆管理系统,主要涉及到登记入住,退房以及客房和客人信息管理;经过分析抽像出涉及到的实体以及各实体之间的关系: 12 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/model.png) 13 |   可以看出整个业务以客房为中心,入住,退房,定价,收费都是以客房为基本单位,所以需要以客房为中心来设计各实体之间的关系。 14 |   我们来看一下,各实体中的字段以及各实体之间的关系图: 15 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/entity-relation.png) 16 | ## 业务流程 17 | ### 登记入住 18 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/flow01.png) 19 | ### 退房 20 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/flow02.png) 21 | ### 管理 22 | #### 客房信息管理 23 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/flow03.png) 24 | #### 客人信息管理 25 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/flow04.png) 26 | ## 页面设计 27 | ### 登记入住 28 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page01.png) 29 | ### 退房 30 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page02.png) 31 | ### 管理 32 | #### 客房信息管理 33 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page03.png) 34 | ##### 增加客房 35 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page04.png) 36 | ##### 更改房型 37 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page05.png) 38 | ##### 床铺管理 39 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page06.png) 40 | ##### 更改可预订状态 41 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page07.png) 42 | #### 客人信息管理 43 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/page08.png) 44 | ## 功能设计 45 | ### 登记入住 46 | **使用背景** 47 |   登记功能是当客人来办理入住时使用,需要填写客人的基本信息,入住客房以及入住日期等信息。 48 | **主要逻辑** 49 | 用户在填写客人信息时,前端会对相应数据的有效性进行校验,主要校验包括: 50 | - 填写过程中需要对填写的信息进行有效性校验,比如身份证号/手机号等; 51 | - 入住日期的选择需要根据所入住的房型来决定是选择到天还是到分钟,而且选择的日期不能晚于当天。 52 | 后端也会对进行有效性以及其它的一些判断: 53 | - 也需要对传入的身份证号/手机号进行相同校验. 54 | - 判断入住的客房是否存在,所选的客房是否是不可订,所选客房是否有床铺和价格 55 | - 身份证号的客人是否存在多个以及相同身份证号不能两次登记入住同一房间等 56 | 还有一些铺助功能,方便用户使用,减少错误,提高效率: 57 | - 前端用户在输入客房号时,会有一个下拉帮助框,显示客房的状态,房型等信息,有助于用户简间明了解所入住客房状态; 58 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/function01.png) 59 | - 当用户选择的是已经有客的客户时,日期则变为灰色,自动显示客房当前的入住和退房时间,不可更改。 60 | - 客房号在登记时,不会显示不可订状态的客房; 61 | 请求到达服务器后,在判断数据合法后,查询入住的客房信息,将其添加进客房信息中,然后保存(客房-客人关系由客房维护); 62 | ### 退房 63 | **使用背景** 64 |   退房是客人登记入住后,到时间后退房使用; 65 | **主要逻辑** 66 |   退房功能首先是用户通过条件查询到需要退的客房,确认信息无误后,点击退房按钮,后端会根据入住,退房时间以及当前房价进行计算,如果退房成功,则返回用户退房结果,包括客人入住的时间以及房费。 67 | ### 管理 68 | #### 客房管理 69 | **使用背景** 70 |   客房管理是当新增客房,修改房型,管理床铺等时使用;不能删除客户,因为客户会参与到一系列的业务中,不允许删除,允许置为不可预订状态; 71 | **主要逻辑** 72 |   首先会分页列出所有客房信息,包括客房当前状态,房型,房价,床铺数,客人数以及总收入,并可进行相应排序与筛选。 73 |   客房当前有客时,不可以修改房型与可预订状态。 74 |   用户点击新增客房按钮时,会弹出对话框,填写客房号以及房型,确认房存,当然前端会要求房号和房型必填,后端也会进行相应判断,而且还会判断客房号是否已存在,如果不存在,则添加客房默认状态,并新增,新增成功后,前端会将后端返回的新客房信息放入列表中。 75 |   用户点击更改房型时,弹出对话框,用户选择房型(小时房、天房、月房),会带出这几个房型当前最新价格以及有效期(没有,则截止日期默认为9999-12-31 23:59:59),当用户选择小时房时,有效期精确到分钟,其它房型到天,用户可以更改当前房价,有效期间也可以不更改,确定保存,浏览器等待服务器返回正确或错误的消息,然后进行相应提示,如果成功,则更改前端客房信息为最新信息。 76 |   用户点击床铺管理时,弹出对话框,用户可以进行新增,修改,删除床铺,如果没有保存,则不会影响前端客房信息,确定时会等待后端处理返回,并给出相应消息提示,成功则个性前端客房信息,失败则不修改; 77 |   用户点击可预订与不可预订状态开关时,会请求服务器,此时前端会等待,当服务器返回成功时,才修改前端客房当前状态,失败则给出消息,不做任何修改; 78 | #### 客人管理 79 | **使用背景** 80 |   客人管理是当需要修改或清理客人信息时使用;不能用于新增客人,因为新增客人通过登记入口添加。 81 | **主要逻辑** 82 |   用户首先根据客人信息模糊查询客人信息,然后进行更改或删除,当客人当前正入住时,则不允许删除(前后端都校验),修改包括修改客人姓名,身份证号,手机号,身份证号和手机号要符合相应的校验规则(前后端都校验),而且在保存时,后端还会判断身份证号不能与现在其它客人身份证号相同,确定无误后进行更新;在请求服务器时,浏览器会等待直到服务器返回结果; 83 | ## 接口设计 84 | ### 接口概览 85 |   目前restful接口类共有两个,一个是客房接口,一个是客人接口 86 | ![](https://github.com/Cool-Coding/photos/blob/master/hotel/function02.png) 87 |   其中客人接口查询,更新,删除,没有单独的增加接口,因为新增客人信息是通过客房的登记接口实现; 88 |   客房的接口包括查询,新增,更改,其中查询有模糊查询和根据客房ID查询,而更新实质上有三个接口,一个是普通的更新接口,客房数据结构中有值的字段更新,null的字段则不更新;其它两个更新一个是登记,一个退房,这两个是特殊的更新,因逻辑与普通更新逻辑相差太大,所以单独拿出来; 89 | ### 返回类型 90 |   返回类型统一为:ResponseEntity 91 |   ResultDto 包括返回成功失败标识,消息码,消息,数据;ResponseEntity是Spring自带的类,使用它可以灵活自定义返回的HttpCode;这样当返回错误消息时,可以设置HttpCode为400或500等,而不是200,使语义一致。 92 |   特殊情况:前端需要直接使用返回的结果情况下(比如自动补全搜索框,由前端请求服务端,直接得到结果,不需要封装),不能使用这样的返回类型; 93 |   前端通过service向服务器发起请求,统一处理服务器返回结果以及http错误消息,不用再到component中处理; 94 | ### 异常处理 95 |   前后端异常都是统一处理;前端是在service中处理,后端在统一的异常处理类中处理;异常处理类可以处理异常,还可以统一处理校验异常,包括controller方法参数的直接校验以及dto中的校验异常,不用在controller每个方法体中判断; 96 | 97 | ## 开发计划 98 | - [ ] 登录,权限管理 99 | - [ ] 自适应手机端 100 | 101 |
102 | **bug反馈及建议:** https://github.com/Cool-Coding/angular-springboot-demo/issues 103 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer-management/customer-management.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 姓名 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 身份证号 15 | 16 | 17 | 18 | 请填写有效身份证编号! 19 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 手机号 27 | 28 | 29 | 30 | 请填写正确的手机号码! 31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 |
46 |
47 | 52 | 53 | 54 | 姓名 55 | 身份证号 56 | 手机号 57 | 客房号 58 | 备注 59 | 操作 60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 | 68 | {{data.name}} 69 | 70 | 71 | 72 | 73 |
74 |
75 | 76 | 77 | 78 | {{data.idCard}} 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {{data.phoneNo}} 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | {{data.roomNos}} 95 | 96 | 97 | 98 | 99 | {{data.comment}} 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | 109 | 更改 110 | 111 | 112 | 保存 113 | 114 | 取消 115 | 116 | 117 | 118 | 119 | 120 | 删除 121 | 122 | 123 | 删除 124 | 125 | 126 |
127 |
128 | 129 | 130 | 131 |
132 |
133 |
134 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Customer} from './dto/Customer'; 3 | import { Observable, of } from 'rxjs'; 4 | import { NzMessageService} from 'ng-zorro-antd'; 5 | import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; 6 | import { catchError, tap} from 'rxjs/operators'; 7 | import {MESSAGETEXTS} from './const/MessageConsts'; 8 | import {SearchCondition} from './dto/SearchCondition'; 9 | import {Result} from './dto/Result'; 10 | 11 | const httpOptions = { 12 | headers: new HttpHeaders({'Content-Type': 'application/json'}) 13 | }; 14 | 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class CustomerService { 19 | private customersUrl = 'http://3s.dkys.org:20750/api/customers'; 20 | 21 | 22 | constructor( 23 | private http: HttpClient, 24 | private message: NzMessageService) { } 25 | 26 | 27 | /** 28 | * 查询所有的客户信息 29 | */ 30 | getCustomers(): Observable { 31 | // TODO: send the message _after_ fetching the customers 32 | return this.http.get(this.customersUrl).pipe( 33 | tap(_ => this.success(MESSAGETEXTS.FETCH_SUCCESS)), 34 | catchError(this.handleError('查询所有客人信息', [])) 35 | ); 36 | } 37 | 38 | /** 39 | * 查询某个客户的信息 40 | * @param id 客户的ID 41 | */ 42 | getCustomer(id: number): Observable { 43 | const url = `${this.customersUrl}/${id}`; 44 | return this.http.get(url).pipe( 45 | tap(() => this.success(MESSAGETEXTS.FETCH_SUCCESS + `:id=${id}`)), 46 | catchError(this.handleError('查询客人信息')) 47 | ); 48 | } 49 | 50 | 51 | /** 52 | * 更新客户信息 53 | * @param customer 54 | */ 55 | updateCustomer(customer: Customer): Observable { 56 | return this.http.put(this.customersUrl, customer, httpOptions).pipe( 57 | tap(() => this.success(MESSAGETEXTS.UPDATE_SUCCESS)), 58 | catchError(this.handleError('更新客人信息')) 59 | ); 60 | } 61 | 62 | /** 63 | * 登记:创建客户信息 64 | * @param customer 客户 65 | */ 66 | addCustomer (customer: Customer): Observable { 67 | return this.http.post(this.customersUrl, customer, httpOptions).pipe( 68 | tap(() => this.success(MESSAGETEXTS.LOGIN_SUCCESS)), 69 | catchError(this.handleError('新增客人信息')) 70 | ); 71 | } 72 | 73 | /** 74 | * 删除客户 75 | * @param customer 客户 76 | */ 77 | deleteCustomer(customer: Customer | number): Observable { 78 | let id; 79 | if (customer instanceof Customer) { 80 | id = customer.id; 81 | } else if (typeof customer === 'number') { 82 | id = customer; 83 | } 84 | 85 | const url = `${this.customersUrl}/${id}`; 86 | 87 | return this.http.delete(url, httpOptions).pipe( 88 | tap((result) => { 89 | if (result.success) { 90 | this.success(result.message); 91 | } else { 92 | this.error(result.message); 93 | } 94 | }), 95 | catchError(this.handleError('删除客人信息')) 96 | ); 97 | } 98 | 99 | /** 100 | * 搜索客户 101 | * @param searchConditions 102 | */ 103 | searchCustomers(searchConditions: SearchCondition[]): Observable { 104 | if (searchConditions === null 105 | || (searchConditions.length === 1 && searchConditions[0].key === undefined) 106 | || (searchConditions.length === 1 && searchConditions[0].key === null) 107 | || (searchConditions.length === 1 && searchConditions[0].value === undefined) 108 | || (searchConditions.length === 1 && searchConditions[0].value === null) 109 | || (searchConditions.length === 1 && !searchConditions[0].value.trim())) { 110 | return of(null); 111 | } 112 | 113 | let httpParams = new HttpParams(); 114 | searchConditions.forEach(condition => { 115 | httpParams = httpParams.append(condition.key, condition.value); 116 | }); 117 | 118 | return this.http.get(`${this.customersUrl}`, {params: httpParams}).pipe( 119 | tap((result) => { 120 | if (result.success) { 121 | this.success(result.message); 122 | } else { 123 | this.error(result.message); 124 | } 125 | }), 126 | catchError(this.handleError('搜索客人信息')) 127 | ); 128 | } 129 | 130 | 131 | private success(message: string) { 132 | this.message.create('success', message); 133 | } 134 | 135 | private error(message: string) { 136 | this.message.create('error', message); 137 | } 138 | 139 | private handleError (operation = 'operation', result?: T) { 140 | 141 | return (error: any): Observable => { 142 | let msg = error.message; 143 | if ( error.error.code !== 'undefined' && (typeof error.error.message === 'string' && error.error.message.constructor === String)) { 144 | msg = error.error.message; 145 | } 146 | 147 | // TODO: send the error to remote logging infrastructure 148 | // console.error(error); 149 | 150 | // TODO: better job of transforming error for user consumption 151 | this.error(`${operation} 失败: ${msg}`); 152 | 153 | // Let the app keep running by returning an empty result 154 | return of(result as T); 155 | }; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management/room-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {RoomService} from '../room.service'; 3 | import {Room} from '../dto/Room'; 4 | import {Bed} from '../dto/Bed'; 5 | 6 | @Component({ 7 | selector: 'app-room-management', 8 | templateUrl: './room-management.component.html', 9 | styleUrls: ['./room-management.component.css'] 10 | }) 11 | export class RoomManagementComponent implements OnInit { 12 | pageIndex = 1; 13 | pageSize = 10; 14 | total = 1; 15 | dataSet = []; 16 | loading = true; 17 | sortValue = null; 18 | sortKey = null; 19 | rooms = []; 20 | unused = 2; 21 | 22 | // 床铺管理 23 | roomId: number; 24 | isVisible = false; 25 | beds: Bed[] = []; 26 | 27 | // 更改房型 28 | isRoomVisible = false; 29 | room: Room; 30 | 31 | // 新增客房 32 | isAddRoomVisible = false; 33 | isOkLoading = false; 34 | message = {}; 35 | 36 | filterRoomStatus = [ 37 | { text: '空房', value: '0' }, 38 | { text: '有客', value: '1' }, 39 | { text: '不可订', value: '2'}, 40 | ]; 41 | 42 | searchRoomStatusList = []; 43 | 44 | sort(sort: { key: string, value: string }): void { 45 | this.sortKey = sort.key; 46 | this.sortValue = sort.value; 47 | this.updateData(true); 48 | } 49 | 50 | constructor(private roomService: RoomService) { 51 | } 52 | 53 | updateData(reset: boolean = false): void { 54 | if (reset) { 55 | this.pageIndex = 1; 56 | } 57 | 58 | this.loading = true; 59 | this.dataSet = this.rooms; 60 | if (this.searchRoomStatusList.length > 0) { 61 | this.dataSet = this.rooms.filter((room) => this.searchRoomStatusList.indexOf(room.status.toString()) !== -1); 62 | } 63 | this.total = this.dataSet.length; 64 | 65 | // 排序不影响条目数量 66 | if (this.sortKey && this.sortValue) { 67 | this.dataSet = this.dataSet.sort((a, b) => (this.sortValue === 'ascend') ? (a[ this.sortKey ] > b[ this.sortKey ] ? 1 : -1) 68 | : (b[ this.sortKey ] > a[ this.sortKey ] ? 1 : -1)); 69 | } 70 | 71 | this.dataSet = this.dataSet.slice((this.pageIndex - 1) * this.pageSize, (this.pageIndex - 1) * this.pageSize + this.pageSize); 72 | this.loading = false; 73 | } 74 | 75 | updateFilter(value: string[]): void { 76 | this.searchRoomStatusList = value; 77 | this.updateData(true); 78 | } 79 | 80 | showAddRoomDialog() { 81 | this.room = new Room(); 82 | this.room.type = '天房'; 83 | this.isAddRoomVisible = true; 84 | } 85 | 86 | confirmAddRoom() { 87 | if (!this.check()) { 88 | return; 89 | } 90 | 91 | this.isOkLoading = true; 92 | this.roomService.addRoom({'roomNo': this.room.roomNo, 'type': this.room.type} as Room).subscribe(result => { 93 | if (result !== undefined && result.success !== undefined && result.success) { 94 | this.rooms.push(result.data); 95 | this.calcIncomingAndSwitchValue(); 96 | this.updateData(true); 97 | } 98 | 99 | this.isOkLoading = false; 100 | this.isAddRoomVisible = false; 101 | }); 102 | } 103 | 104 | cancelAddRoom() { 105 | this.isAddRoomVisible = false; 106 | } 107 | 108 | show_bed_management_dialog(roomId: number, beds: Bed[]): void { 109 | this.isVisible = true; 110 | this.roomId = roomId; 111 | if (beds == null) { 112 | beds = []; 113 | } 114 | this.beds = beds; 115 | } 116 | 117 | show_room_management_dialog(room: Room) { 118 | this.isRoomVisible = true; 119 | this.room = room; 120 | } 121 | 122 | /** 123 | * 更改客房状态 124 | * @param data 125 | */ 126 | clickSwitch(data: any): void { 127 | // 如果是有客状态点击不起作用 128 | if (data.status === 1) { 129 | return; 130 | } 131 | 132 | if (!data.statusLoading) { 133 | data.statusLoading = true; 134 | 135 | if (data.switchValue) { 136 | data.status = this.unused; 137 | } else { 138 | data.status = 0; 139 | } 140 | 141 | this.roomService.updateRoom({id: data.id, status: data.status} as Room).subscribe((result) => { 142 | if (result !== undefined && result.success !== undefined && result.success) { 143 | data.status = result.data.status; 144 | data.switchValue = !data.switchValue; 145 | } 146 | // 修改成功与否,都应关闭正在加载状态 147 | data.statusLoading = false; 148 | }); 149 | } 150 | 151 | } 152 | 153 | check(): boolean { 154 | let right = true; 155 | 156 | if (this.room.roomNo == null || this.room.roomNo.toString().trim().length === 0) { 157 | this.message['roomNo'] = '客房号不能为空'; 158 | right = false; 159 | } 160 | 161 | if (this.room.type == null || this.room.type.toString().trim().length === 0) { 162 | this.message['type'] = '客房类型不能为空'; 163 | right = false; 164 | } 165 | 166 | return right; 167 | } 168 | 169 | ngOnInit() { 170 | this.loading = true; 171 | this.roomService.getRooms().subscribe((rooms) => { 172 | this.rooms = rooms; 173 | this.calcIncomingAndSwitchValue(); 174 | this.updateData(); 175 | }); 176 | } 177 | 178 | calcIncomingAndSwitchValue() { 179 | this.rooms.forEach((room) => { 180 | const incomes = room.incomes; 181 | let sum = 0; 182 | if (incomes !== undefined && incomes != null) { 183 | incomes.forEach((income) => sum += Number.parseFloat(income.incoming)); 184 | } 185 | 186 | room.sumIncoming = sum.toFixed(2); 187 | 188 | room.switchValue = room.status !== this.unused; 189 | 190 | room.statusLoading = false; 191 | return room; 192 | }); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /hotel-front/src/app/logout/logout.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 房间号 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | 16 | 姓名 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 | 身份证号 26 | 27 | 28 | 29 | 请填写有效身份证编号! 30 | 31 | 32 | 33 |
34 | 35 |
36 | 37 | 手机号 38 | 39 | 40 | 41 | 请填写正确的手机号码! 42 | 43 | 44 | 45 |
46 | 47 |
48 | 49 |
50 |
51 | 52 | 53 | 54 | 更多筛选条件 55 | 56 | 57 | 58 |
59 |
60 |
61 |
62 |
63 | 64 | 65 | 66 | 67 | 客房号 68 | 房型 69 | 房价 70 | 床位数 71 | 客人数 72 | 入住日期 73 | 退房日期 74 | 操作 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {{data.roomNo}} 84 | {{data.type}} 85 | {{data.money}} 86 | {{data.beds.length}} 87 | {{data.customers.length}} 88 | {{data.checkInDate | date: (data.type === '小时房' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd')}} 89 | {{data.checkOutDate | date: (data.type === '小时房' ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd')}} 90 | 91 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 姓名 103 | 身份证号 104 | 手机号码 105 | 备注 106 | 107 | 108 | 109 | 110 | {{data.name}} 111 | {{data.idCard}} 112 | {{data.phoneNo}} 113 | {{data.comment}} 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 |
124 | 125 | 155 | -------------------------------------------------------------------------------- /hotel-front/src/app/customer-management/customer-management.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {FormBuilder, FormGroup, Validators} from '@angular/forms'; 3 | import {CustomerService} from '../customer.service'; 4 | import {SearchCondition} from '../dto/SearchCondition'; 5 | import {Customer} from '../dto/Customer'; 6 | import {NzMessageService} from 'ng-zorro-antd'; 7 | import {RoomService} from '../room.service'; 8 | 9 | @Component({ 10 | selector: 'app-customer-management', 11 | templateUrl: './customer-management.component.html', 12 | styleUrls: ['./customer-management.component.css'] 13 | }) 14 | export class CustomerManagementComponent implements OnInit { 15 | validateForm: FormGroup; 16 | customers: Customer[]; 17 | dataSet = []; 18 | editCache = {}; 19 | 20 | constructor(private fb: FormBuilder, 21 | private customerService: CustomerService, 22 | private roomService: RoomService, 23 | private message: NzMessageService) { 24 | this.validateForm = this.fb.group({ 25 | name: [''], 26 | idCard: ['', [Validators.pattern(/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/)]], 27 | phoneNo: ['', [Validators.pattern(/(^[1][3,4,5,7,8][0-9]{9}$)/)]], 28 | }); 29 | } 30 | 31 | updateEditCache(edit: boolean = false): void { 32 | this.dataSet.forEach(item => { 33 | if (!this.editCache[ item.key ]) { 34 | this.editCache[ item.key ] = { 35 | edit: edit, 36 | data: { ...item } 37 | }; 38 | } 39 | }); 40 | } 41 | 42 | startEdit(key: string): void { 43 | this.editCache[ key ].edit = true; 44 | } 45 | 46 | cancelEdit(key: string): void { 47 | this.editCache[ key ].edit = false; 48 | } 49 | 50 | delete(key: number): void { 51 | const index = this.dataSet.findIndex(item => item.key === key); 52 | const customer = this.dataSet[index]; 53 | customer.isSpinning = true; 54 | // 调用删除服务 55 | this.customerService.deleteCustomer(this.editCache[ key ].data.id).subscribe(result => { 56 | if (result !== undefined && result.success !== undefined && result.success) { 57 | this.dataSet = this.dataSet.filter(d => d.key !== key); 58 | delete this.editCache[key]; 59 | } 60 | customer.isSpinning = false; 61 | }); 62 | } 63 | 64 | saveEdit(key: number): void { 65 | 66 | if (!this.check(this.editCache[ key ].data)) { 67 | return; 68 | } 69 | 70 | // 判断有没有更新 71 | const index = this.dataSet.findIndex(item => item.key === key); 72 | const customer = this.dataSet[index]; 73 | const editCustomer = this.editCache[ key ].data; 74 | if (customer.id === editCustomer.id 75 | && customer.name === editCustomer.name 76 | && customer.idCard === editCustomer.idCard 77 | && customer.phoneNo === editCustomer.phoneNo 78 | && customer.comment === editCustomer.comment) { 79 | this.editCache[ key ].edit = false; 80 | return; 81 | } 82 | 83 | // 调用修改服务 84 | customer.isSpinning = true; 85 | this.customerService.updateCustomer(this.editCache[ key ].data as Customer).subscribe(result => { 86 | if (result !== undefined && result.success !== undefined && result.success) { 87 | const index = this.dataSet.findIndex(item => item.key === key); 88 | Object.assign(this.dataSet[ index ], result.data); 89 | this.editCache[ key ].edit = false; 90 | } 91 | customer.isSpinning = false; 92 | }); 93 | } 94 | 95 | check(data: any): boolean { 96 | // 姓名 97 | if (data.name == null || !data.name.trim()) { 98 | this.message.error('姓名不能为空'); 99 | return false; 100 | } 101 | 102 | // 校验身份证号 103 | if (data.idCard == null || !data.idCard.trim()) { 104 | this.message.error('身份证号不能为空'); 105 | return false; 106 | } 107 | 108 | let regex = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/; 109 | if (!regex.test(data.idCard)) { 110 | this.message.error('身份证号不正确'); 111 | return false; 112 | } 113 | 114 | // 校验手机号 115 | if (data.phoneNo == null || !data.phoneNo.trim()) { 116 | this.message.error('手机号不能为空'); 117 | return false; 118 | } 119 | 120 | regex = /(^[1][3,4,5,7,8][0-9]{9}$)/; 121 | if (!regex.test(data.phoneNo)) { 122 | this.message.error('手机号不正确'); 123 | return false; 124 | } 125 | 126 | return true; 127 | } 128 | 129 | resetForm(e: MouseEvent): void { 130 | e.preventDefault(); 131 | this.validateForm.reset(); 132 | } 133 | 134 | submitForm = ($event) => { 135 | $event.preventDefault(); 136 | 137 | const conditions = []; 138 | 139 | for (const key in this.validateForm.controls) { 140 | if (this.validateForm.controls[key].value !== null && this.validateForm.controls[key].value.trim() !== '') { 141 | const searchCondition = new SearchCondition(); 142 | searchCondition.key = key; 143 | searchCondition.value = this.validateForm.controls[key].value; 144 | conditions.push(searchCondition); 145 | } 146 | 147 | this.validateForm.controls[ key ].markAsDirty(); 148 | this.validateForm.controls[ key ].updateValueAndValidity(); 149 | } 150 | 151 | this.customerService.searchCustomers(conditions).subscribe(result => { 152 | if (result !== undefined && result.success !== undefined && result.success) { 153 | // @ts-ignore 154 | this.customers = result.data; 155 | this.init(); 156 | // 此处要复制,而不是引用,这样内部组件值的改变不会引响到组件外,不能使用this.dataSet = [...this.beds];,因为复制的是引用 157 | this.customers.forEach((item, index) => { 158 | this.dataSet.push({key: index, ...item}); 159 | }); 160 | this.updateEditCache(); 161 | 162 | } 163 | }); 164 | } 165 | 166 | init(): void { 167 | this.dataSet = []; 168 | this.editCache = {}; 169 | } 170 | 171 | ngOnInit() { 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /hotel-front/src/app/room.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Room} from './dto/Room'; 3 | import {Observable, of} from 'rxjs'; 4 | import { NzMessageService} from 'ng-zorro-antd'; 5 | import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http'; 6 | import { catchError, tap} from 'rxjs/operators'; 7 | import {MESSAGETEXTS} from './const/MessageConsts'; 8 | import {SearchCondition} from './dto/SearchCondition'; 9 | import {Result} from './dto/Result'; 10 | 11 | const httpOptions = { 12 | headers: new HttpHeaders({'Content-Type': 'application/json'}) 13 | }; 14 | 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class RoomService { 19 | private roomsUrl = 'http://3s.dkys.org:20750/api/rooms'; 20 | 21 | 22 | constructor( 23 | private http: HttpClient, 24 | private message: NzMessageService) { } 25 | 26 | 27 | /** 28 | * 查询所有的客房信息 29 | */ 30 | getRooms(): Observable { 31 | // TODO: send the message _after_ fetching the rooms 32 | return this.http.get(this.roomsUrl).pipe( 33 | tap(_ => this.success(MESSAGETEXTS.FETCH_SUCCESS)), 34 | catchError(this.handleError('查询所有客房信息', [])) 35 | ); 36 | } 37 | 38 | /** 39 | * 查询某个客房的信息 40 | * @param id 客房的ID 41 | */ 42 | getRoom(id: number): Observable { 43 | const url = `${this.roomsUrl}/${id}`; 44 | return this.http.get(url).pipe( 45 | tap(() => this.success(MESSAGETEXTS.FETCH_SUCCESS + `:id=${id}`)), 46 | catchError(this.handleError('查询客房信息')) 47 | ); 48 | } 49 | 50 | 51 | /** 52 | * 更新客房信息 53 | * @param Room 54 | */ 55 | updateRoom(room: Room): Observable { 56 | return this.http.put(this.roomsUrl, room, httpOptions).pipe( 57 | tap(() => this.success(MESSAGETEXTS.UPDATE_SUCCESS)), 58 | catchError(this.handleError('更新客房信息')) 59 | ); 60 | } 61 | 62 | /** 63 | * 登记:创建客房信息 64 | * @param room 客房 65 | */ 66 | addRoom (room: Room): Observable { 67 | return this.http.post(this.roomsUrl, room, httpOptions).pipe( 68 | tap((result) => { 69 | if (result.success) { 70 | this.success(result.message); 71 | } else { 72 | this.error(result.message); 73 | } 74 | }), 75 | catchError(this.handleError('新增客房')) 76 | ); 77 | } 78 | 79 | /** 80 | * 删除客房 81 | * @param room 客房 82 | */ 83 | deleteRoom(room: Room | number): Observable { 84 | let id; 85 | if (room instanceof Room) { 86 | id = room.id; 87 | } else if (typeof Room === 'number') { 88 | id = room; 89 | } 90 | 91 | const url = `${this.roomsUrl}/${id}`; 92 | 93 | return this.http.delete(url, httpOptions).pipe( 94 | tap(() => this.success(MESSAGETEXTS.DELETE_SUCCESS)), 95 | catchError(this.handleError('删除客房信息')) 96 | ); 97 | } 98 | 99 | 100 | /** 101 | * 搜索客房 102 | * @param term 103 | */ 104 | searchRooms(conditions: SearchCondition[]): Observable { 105 | if (conditions === null 106 | || (conditions.length === 1 && conditions[0].key === undefined) 107 | || (conditions.length === 1 && conditions[0].key === null) 108 | || (conditions.length === 1 && conditions[0].value === undefined) 109 | || (conditions.length === 1 && conditions[0].value === null)) { 110 | return of([]); 111 | }; 112 | 113 | let parameter = '?'; 114 | conditions.forEach((condition) => { 115 | parameter += condition.key + '=' + condition.value + '&'; 116 | }); 117 | 118 | parameter = parameter.substr(0, parameter.length - 1); 119 | 120 | return this.http.get(`${this.roomsUrl}/${parameter}`).pipe( 121 | // tap((rooms) => this.success(Utils.format(MESSAGETEXTS.SEARCH_SUCCESS, [rooms.length]))), 122 | catchError(this.handleError('搜索客房信息', [])) 123 | ); 124 | } 125 | 126 | /** 127 | * 登记 128 | * @param room 129 | */ 130 | login(room: Room): Observable { 131 | if (room === null ) { 132 | return of(null); 133 | } 134 | 135 | const url = `${this.roomsUrl}/login`; 136 | 137 | return this.http.put(url, room, httpOptions).pipe( 138 | tap((result) => { 139 | if (result.success) { 140 | this.success(result.message); 141 | } else { 142 | this.error(result.message); 143 | } 144 | }), 145 | catchError(this.handleError('登记')) 146 | ); 147 | } 148 | 149 | /** 150 | * 退房 151 | * @param roomId 152 | */ 153 | logout(roomId: number): Observable { 154 | if (roomId === null || roomId <= 0) { 155 | return of(null); 156 | } 157 | 158 | const url = `${this.roomsUrl}/logout`; 159 | return this.http.put(url, roomId, httpOptions).pipe( 160 | /**tap((result) => { 161 | if (result.success) { 162 | this.success(result.message); 163 | } else { 164 | this.error(result.message); 165 | } 166 | }),*/ 167 | catchError(this.handleError('退房')) 168 | ); 169 | } 170 | 171 | 172 | private success(message: string) { 173 | this.message.create('success', message); 174 | } 175 | 176 | private error(message: string) { 177 | this.message.create('error', message); 178 | } 179 | 180 | private handleError (operation = 'operation', result?: T) { 181 | 182 | return (error: any): Observable => { 183 | let msg = error.message; 184 | if ( error.error.code !== 'undefined' && (typeof error.error.message === 'string' && error.error.message.constructor === String)) { 185 | msg = error.error.message; 186 | } 187 | 188 | // TODO: send the error to remote logging infrastructure 189 | // console.error(error); 190 | 191 | // TODO: better job of transforming error for user consumption 192 | this.error(`${operation}失败: ${msg}`); 193 | 194 | // Let the app keep running by returning an empty result 195 | return of(result as T); 196 | }; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/controller/CustomerController.java: -------------------------------------------------------------------------------- 1 | package com.hotel.controller; 2 | 3 | import com.hotel.annotation.UpdateCustomerGroup; 4 | import com.hotel.constant.RetCode; 5 | import com.hotel.dto.CustomerDto; 6 | import com.hotel.dto.ResultDto; 7 | import com.hotel.entity.Customer; 8 | import com.hotel.entity.Room; 9 | import com.hotel.service.CustomerService; 10 | import com.hotel.util.Converters; 11 | import io.swagger.annotations.Api; 12 | import io.swagger.annotations.ApiOperation; 13 | import io.swagger.annotations.ApiParam; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.validation.annotation.Validated; 19 | import org.springframework.web.bind.annotation.*; 20 | 21 | import javax.transaction.Transactional; 22 | import javax.validation.constraints.NotNull; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /** 27 | * @author guangyong.yang 28 | * @date 2019-01-27 29 | * 客人ResultFul接口 30 | */ 31 | @RestController 32 | @Slf4j 33 | @CrossOrigin 34 | @RequestMapping("api") 35 | @Api(description = "客人RestFul接口") 36 | public class CustomerController { 37 | 38 | private CustomerService customerService; 39 | 40 | @Autowired 41 | public CustomerController(CustomerService customerService) { 42 | this.customerService = customerService; 43 | } 44 | 45 | @GetMapping("/customers") 46 | @ApiOperation("查询客人信息") 47 | public ResponseEntity findCustomers(@ApiParam("客人姓名") @RequestParam(value = "name",required = false) String name, 48 | @ApiParam("客人手机号") @RequestParam(value = "phoneNo",required = false) String phoneNo, 49 | @ApiParam("客人身份证号") @RequestParam(value = "idCard",required = false) String idCard) { 50 | List customers = customerService.findCustomers(name, phoneNo, idCard); 51 | if (customers == null || customers.size() == 0) { 52 | return new ResponseEntity<>(ResultDto.builder() 53 | .success(false) 54 | .code(RetCode.RETCODE_40010) 55 | .message("没有查询到客人信息") 56 | .build(), HttpStatus.NOT_FOUND); 57 | } 58 | 59 | return new ResponseEntity<>(ResultDto.builder() 60 | .success(true) 61 | .code(RetCode.RETCODE_20003) 62 | .message(RetCode.RETCODE_20003_MSG) 63 | .data(customer2Dto(customers)) 64 | .build(),HttpStatus.OK); 65 | 66 | } 67 | 68 | @PutMapping("/customers") 69 | @ApiOperation("更新客人信息") 70 | @Transactional(rollbackOn = Exception.class) 71 | public ResponseEntity updateCustomer(@Validated(UpdateCustomerGroup.class) @RequestBody CustomerDto customerDto) { 72 | // 判断客人是否存在 73 | Integer id = customerDto.getId(); 74 | ResponseEntity result = checkCustomerExist(id); 75 | if (result != null ) { 76 | return result; 77 | } 78 | 79 | // 判断身份证号相同的客人是否存在 80 | List customerByIdCard = customerService.findCustomerByIdCard(customerDto.getIdCard()); 81 | if (isDuplicated(customerByIdCard,customerDto)) { 82 | return new ResponseEntity<>(ResultDto.builder() 83 | .success(false) 84 | .code(RetCode.RETCODE_40015) 85 | .message("身份证号:"+customerDto.getIdCard()+"的客人已经存在") 86 | .build(),HttpStatus.BAD_REQUEST); 87 | } 88 | 89 | 90 | return new ResponseEntity<>(ResultDto.builder() 91 | .success(true) 92 | .code(RetCode.RETCODE_20004) 93 | .message(RetCode.RETCODE_20004_MSG) 94 | .data(Converters.customer2Dto(customerService.update(Converters.dto2Customer(customerDto)))) 95 | .build(),HttpStatus.OK); 96 | } 97 | 98 | @DeleteMapping("/customers/{id}") 99 | @ApiOperation("删除客人信息") 100 | @Transactional(rollbackOn = Exception.class) 101 | public ResponseEntity deleteCustomer(@NotNull(message ="客人ID号不能为空") @PathVariable("id") Integer id) { 102 | 103 | ResponseEntity result = checkCustomerExist(id); 104 | if (result != null ) { 105 | return result; 106 | } 107 | 108 | Customer customer = customerService.findOne(id); 109 | List rooms = customer.getRooms(); 110 | if (rooms != null && rooms.size() > 0) { 111 | return new ResponseEntity<>(ResultDto.builder() 112 | .success(false) 113 | .code(RetCode.RETCODE_40014) 114 | .message("客人当前正在入住,不能删除") 115 | .build(),HttpStatus.BAD_REQUEST); 116 | } 117 | 118 | customerService.delete(id); 119 | return new ResponseEntity<>(ResultDto.builder() 120 | .success(true) 121 | .code(RetCode.RETCODE_20004) 122 | .message(RetCode.RETCODE_20004_MSG) 123 | .build(),HttpStatus.OK); 124 | } 125 | 126 | /** 127 | * 判断客人是否存在 128 | * @param id 客人ID 129 | * @return 失败的话返回HTTP响应内容,成功返回null 130 | */ 131 | private ResponseEntity checkCustomerExist(Integer id) { 132 | Customer customer = customerService.findOne(id); 133 | if (customer == null) { 134 | return new ResponseEntity<>(ResultDto.builder() 135 | .success(false) 136 | .code(RetCode.RETCODE_40010) 137 | .message("没有查询到客人信息") 138 | .build(),HttpStatus.NOT_FOUND); 139 | } 140 | 141 | return null; 142 | } 143 | 144 | private boolean isDuplicated(List customerByIdCard ,CustomerDto customerDto){ 145 | return (customerByIdCard != null && customerByIdCard.size() > 1) 146 | ||(customerByIdCard.size() == 1 && !customerByIdCard.get(0).getId().equals(customerDto.getId())); 147 | } 148 | 149 | 150 | private List customer2Dto(List customers){ 151 | if (customers != null ) { 152 | List customerDtos = new ArrayList<>(); 153 | for (Customer customer : customers) { 154 | customerDtos.add(Converters.customer2Dto(customer)); 155 | } 156 | return customerDtos; 157 | } 158 | return null; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/service/impl/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.hotel.service.impl; 2 | 3 | import com.hotel.dto.RoomSearchCondition; 4 | import com.hotel.entity.Bed; 5 | import com.hotel.entity.Customer; 6 | import com.hotel.entity.Room; 7 | import com.hotel.repository.BedRepository; 8 | import com.hotel.repository.CustomerRepository; 9 | import com.hotel.repository.RoomRepository; 10 | import com.hotel.service.RoomService; 11 | import com.hotel.util.BeanUtils; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.ArrayList; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | import java.util.Objects; 19 | 20 | 21 | /** 22 | * @author guangyong.yang 23 | * @date 2019-01-20 24 | */ 25 | @Service 26 | public class RoomServiceImpl implements RoomService { 27 | 28 | private RoomRepository roomRepository; 29 | private CustomerRepository customerRepository; 30 | private BedRepository bedRepository; 31 | 32 | @Autowired 33 | public RoomServiceImpl(RoomRepository roomRepository,CustomerRepository customerRepository,BedRepository bedRepository) { 34 | this.roomRepository = roomRepository; 35 | this.customerRepository = customerRepository; 36 | this.bedRepository = bedRepository; 37 | } 38 | 39 | @Override 40 | public List search(RoomSearchCondition condition) { 41 | List rooms = null; 42 | boolean allConditionNull = true; 43 | 44 | if (Objects.nonNull(condition)) { 45 | //如果搜索条件中房间号有值,则根据房间号搜索 46 | if (Objects.nonNull(condition.getRoomNo())) { 47 | allConditionNull = false; 48 | rooms = roomRepository.searchRoomsByRoomNo(condition.getRoomNo()); 49 | } 50 | 51 | //如果搜索条件中客户信息至少有一个有值 52 | if (Objects.nonNull(condition.getName()) || 53 | Objects.nonNull(condition.getIdCard()) || 54 | Objects.nonNull(condition.getPhoneNo())) { 55 | allConditionNull = false; 56 | //如果客户号查询条件有值,,则根据房间找到房间里的客人信息,然后再与搜索条件匹配 57 | //否则,查询出客户信息,再查询出客房信息 58 | if (Objects.nonNull(condition.getRoomNo())) { 59 | //如果没有查询到客房信息,说明客房号不正确,则没必要继续查询 60 | if (rooms == null) { 61 | return null; 62 | } 63 | 64 | Iterator iterator = rooms.iterator(); 65 | while (iterator.hasNext()) { 66 | Room room = iterator.next(); 67 | List customers = room.getCustomers(); 68 | 69 | //客房中的客人有一个满足查询条件,则不排除此客房 70 | boolean exist = false; 71 | for (Customer customer : customers) { 72 | if (containField("name", condition, customer) 73 | && containField("phoneNo", condition, customer) 74 | && (containField("idCard", condition, customer))) { 75 | 76 | exist = true; 77 | } 78 | } 79 | 80 | if (!exist) { 81 | iterator.remove(); 82 | } 83 | 84 | } 85 | } else { 86 | if (Objects.isNull(condition.getName())) { 87 | condition.setName(""); 88 | } 89 | 90 | if (Objects.isNull(condition.getPhoneNo())) { 91 | condition.setPhoneNo(""); 92 | } 93 | 94 | if (Objects.isNull(condition.getIdCard())) { 95 | condition.setIdCard(""); 96 | } 97 | 98 | List customers = customerRepository.findCustomers(condition.getName(), condition.getPhoneNo(), condition.getIdCard()); 99 | rooms = new ArrayList<>(); 100 | for (Customer customer : customers) { 101 | rooms.addAll(customer.getRooms()); 102 | } 103 | } 104 | } 105 | } 106 | 107 | //如果没有查询条件或所有查询条件为空,则查询所有客房 108 | if (Objects.isNull(condition) || allConditionNull) { 109 | rooms = roomRepository.findAll(); 110 | } 111 | 112 | return rooms; 113 | } 114 | 115 | @Override 116 | public Room findOne(Integer id) { 117 | return roomRepository.findOne(id); 118 | } 119 | 120 | 121 | @Override 122 | public Room save(Room room) { 123 | return roomRepository.save(room); 124 | } 125 | 126 | 127 | @Override 128 | public List findRoomByRoomNo(String roomNo) { 129 | return roomRepository.findRoomByRoomNo(roomNo); 130 | 131 | } 132 | 133 | @Override 134 | public Room updateRoom(Room room, boolean isUpdatingBed) { 135 | Room currentInstance = roomRepository.findOne(room.getId()); 136 | //删除床铺 137 | if (isUpdatingBed) { 138 | List beds = currentInstance.getBeds(); 139 | if (beds != null && beds.size() > 0) { 140 | List newBeds = room.getBeds(); 141 | for (Bed bed : beds) { 142 | boolean exist = false; 143 | if (newBeds != null) { 144 | for (Bed newBed : newBeds) { 145 | if (newBed.getId().equals(bed.getId())) { 146 | exist = true; 147 | break; 148 | } 149 | } 150 | } 151 | 152 | if (!exist) { 153 | bedRepository.delete(bed.getId()); 154 | } 155 | } 156 | } 157 | 158 | //如果新客房中无床铺,而客房原来有床铺,则将原来客房床铺置null,因为下面复制时会跳过null属性 159 | if (room.getBeds() == null && beds != null && beds.size() > 0) { 160 | currentInstance.setBeds(null); 161 | } 162 | } 163 | 164 | //支持部分更新 165 | String[] nullPropertyNames = BeanUtils.getNullPropertyNames(room); 166 | org.springframework.beans.BeanUtils.copyProperties(room, currentInstance, nullPropertyNames); 167 | return roomRepository.save(currentInstance); 168 | } 169 | 170 | 171 | private boolean containField(String filedName, RoomSearchCondition condition, Customer customer){ 172 | String conditionValue = BeanUtils.getFieldValue(condition, filedName); 173 | String entityValue = BeanUtils.getFieldValue(customer, filedName); 174 | return Objects.isNull(conditionValue) || entityValue.contains(conditionValue); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /hotel-front/src/app/room-management-room/room-management-room.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, forwardRef, Input, OnInit} from '@angular/core'; 2 | import {Room} from '../dto/Room'; 3 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; 4 | import {Charge} from '../dto/Charge'; 5 | import {RoomService} from '../room.service'; 6 | import {Utils} from '../util/Utils'; 7 | 8 | @Component({ 9 | selector: 'app-room-management-room', 10 | templateUrl: './room-management-room.component.html', 11 | styleUrls: ['./room-management-room.component.css'], 12 | providers: [ 13 | { 14 | provide: NG_VALUE_ACCESSOR, 15 | multi: true, 16 | useExisting: forwardRef(() => RoomManagementRoomComponent), 17 | } 18 | ] 19 | }) 20 | export class RoomManagementRoomComponent implements OnInit, ControlValueAccessor { 21 | 22 | @Input() private _isVisible: boolean; 23 | @Input() room: Room; 24 | 25 | charge: Charge; 26 | 27 | message = {}; 28 | 29 | constructor(private roomService: RoomService) { } 30 | 31 | ngOnInit() { 32 | } 33 | 34 | confirm() { 35 | if (this.charge.startDate as any instanceof Date) { 36 | this.charge.startDate = Utils.dateFormat(this.charge.startDate); 37 | } 38 | 39 | if (this.charge.endDate as any instanceof Date) { 40 | this.charge.endDate = Utils.dateFormat(this.charge.endDate); 41 | } 42 | 43 | let theSame = false; 44 | if (this.charge.id !== null) { 45 | const index = this.room.charges.findIndex(c => c.id === this.charge.id); 46 | const charge = this.room.charges[index]; 47 | if (charge.timeUnit === this.charge.timeUnit 48 | && charge.count === this.charge.count 49 | && charge.money === this.charge.money 50 | && charge.startDate === this.charge.startDate 51 | && charge.endDate === this.charge.endDate) { 52 | theSame = true; 53 | } 54 | } 55 | 56 | if (!theSame) { 57 | // 检查输入的数据有效性 58 | if (!this.check()) { 59 | return; 60 | } 61 | 62 | // 如果要保存的房型与系统当前房型最新区间有重叠,则将当前区间截止日期修改为要保存起始日期减1秒 63 | // 如果用户只是更改了数量或价格而没有更改日期,则将原记录的截止日期等于开始日期 64 | const charge = this.findLatestCharge(this.charge.timeUnit); 65 | if (charge !== null && Utils.getDate(charge.endDate) >= Utils.getDate(this.charge.startDate)) { 66 | let date = Utils.getDate(this.charge.startDate).getTime() - 1; 67 | const startDate = Utils.getDate(charge.startDate).getTime(); 68 | if (date < startDate) { 69 | date = startDate; 70 | } 71 | charge.endDate = Utils.dateFormat(new Date(date)); 72 | } 73 | this.charge.id = null; 74 | this.room.charges.push(this.charge); 75 | } 76 | // 更改房间类型 77 | this.roomService.updateRoom({id: this.room.id, type: this.charge.timeUnit, charges: this.room.charges, updatingBed: false} as Room).subscribe(result => { 78 | if (result !== undefined && result.success !== undefined && result.success) { 79 | this.room.type = result.data.type; 80 | this.room.money = result.data.money; 81 | this.room.charges.splice(0, this.room.charges.length); 82 | if (result.data != null && result.data.charges !== undefined && result.data.charges != null && result.data.charges.length > 0) { 83 | result.data.charges.forEach(charge => this.room.charges.push(charge)); 84 | } 85 | } else { 86 | this.room.charges = this.room.charges.filter(c => c.id !== null); 87 | } 88 | this.isVisible = false; 89 | }); 90 | 91 | } 92 | 93 | // 找到某一房型最新的定价记录 94 | findLatestCharge(type: string): Charge { 95 | let index = -1; 96 | const charges = this.room.charges.filter(charge => charge.timeUnit === type); 97 | if (charges !== null && charges.length > 0) { 98 | index = 0; 99 | for (let i = 0; i < charges.length; i++ ) { 100 | if (Utils.getDate(charges[i].endDate) > Utils.getDate(charges[index].endDate)) { 101 | index = i; 102 | } 103 | } 104 | } 105 | return index === -1 ? null : charges[index]; 106 | } 107 | 108 | timeUnitChange() { 109 | const charge = this.findLatestCharge(this.charge.timeUnit); 110 | if (charge !== null ) { 111 | // 使用 {...charge} 是因为返回的是引用,如果界面上值改变时,也会影响room中原有的charge值 112 | this.charge = {...charge}; 113 | } else { 114 | this.charge = {id: null, timeUnit: this.charge.timeUnit, count: 1, money: null, startDate: '', endDate: '9999-12-31 23:59:59'}; 115 | } 116 | } 117 | 118 | cancel() { 119 | this.isVisible = false; 120 | } 121 | 122 | get isVisible(): boolean { 123 | return this._isVisible; 124 | } 125 | 126 | set isVisible(value: boolean) { 127 | this._isVisible = value; 128 | this.propagateChange(this._isVisible); 129 | } 130 | 131 | propagateChange = (_: any) => {}; 132 | 133 | registerOnChange(fn: any): void { 134 | this.propagateChange = fn; 135 | } 136 | 137 | registerOnTouched(fn: any): void { 138 | } 139 | 140 | setDisabledState(isDisabled: boolean): void { 141 | } 142 | 143 | writeValue(value: any): void { 144 | if (value !== undefined) { 145 | if (value == null || value) { 146 | this.charge = null; 147 | if (this.room.charges != null) { 148 | const charge = this.findLatestCharge(this.room.type); 149 | if (charge !== null ) { 150 | this.charge = {...charge}; 151 | } else { 152 | this.charge = {id: null, timeUnit: this.room.type, count: 1, money: null, startDate: '', endDate: '9999-12-31 23:59:59'}; 153 | } 154 | } 155 | } 156 | this.isVisible = value; 157 | } 158 | } 159 | 160 | check(): boolean { 161 | let right = true; 162 | 163 | if (this.charge.count == null || this.charge.count === 0 || this.charge.count.toString().trim().length === 0) { 164 | this.message['count'] = '数量不能为空'; 165 | right = false; 166 | } 167 | 168 | if (this.charge.money == null || this.charge.money === 0 || this.charge.money.toString().trim().length === 0) { 169 | this.message['money'] = '金额不能为空'; 170 | right = false; 171 | } 172 | 173 | if (this.charge.timeUnit == null || this.charge.timeUnit.trim().length === 0) { 174 | this.message['timeUnit'] = '房型不能为空'; 175 | right = false; 176 | } 177 | 178 | if (this.charge.startDate == null || this.charge.startDate.trim().length === 0) { 179 | this.message['startDate'] = '起始日期不能为空'; 180 | right = false; 181 | } 182 | 183 | if (this.charge.endDate == null || this.charge.endDate.trim().length === 0) { 184 | this.message['endDate'] = '截止日期不能为空'; 185 | right = false; 186 | } 187 | 188 | if (this.charge.startDate != null && this.charge.endDate !== null 189 | && Utils.getDate(this.charge.startDate) > Utils.getDate(this.charge.endDate)) { 190 | this.message['startDate'] = '起始日期不能晚于截止日期'; 191 | right = false; 192 | } 193 | 194 | // 判断日期区间是否与现有日期区间是否有重叠 195 | /*if (this.charge.startDate != null && this.charge.endDate !== null) { 196 | const charges = this.room.charges.filter(charge => charge.timeUnit === this.charge.timeUnit); 197 | for (let i = 0; i < charges.length; i++ ){ 198 | const charge = charges[i]; 199 | if (!(Utils.getDate(charge.startDate) > Utils.getDate(this.charge.endDate) 200 | || Utils.getDate(charge.endDate) < Utils.getDate(this.charge.startDate))) { 201 | this.message['endDate'] = '设置的日期区间与系统中价格日期区间[' + charge.startDate + '-' + charge.endDate + ']有重叠'; 202 | right = false; 203 | break; 204 | } 205 | } 206 | }*/ 207 | return right; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/util/Converters.java: -------------------------------------------------------------------------------- 1 | package com.hotel.util; 2 | 3 | import com.hotel.constant.BedType; 4 | import com.hotel.constant.RoomStatus; 5 | import com.hotel.constant.RoomType; 6 | import com.hotel.dto.*; 7 | import com.hotel.entity.*; 8 | 9 | import java.text.DecimalFormat; 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | /** 15 | * @author guangyong.yang 16 | * @date 2019-01-27 17 | * dto与entity之间转换 18 | */ 19 | public class Converters { 20 | 21 | public static CustomerDto customer2Dto(Customer customer) { 22 | if (customer == null) { 23 | return null; 24 | } 25 | 26 | CustomerDto customerDto = new CustomerDto(); 27 | customerDto.setId(customer.getId()); 28 | customerDto.setName(customer.getName()); 29 | customerDto.setIdCard(customer.getIdCard()); 30 | customerDto.setPhoneNo(customer.getPhoneNo()); 31 | customerDto.setComment(customer.getComment()); 32 | 33 | List rooms = customer.getRooms(); 34 | if (rooms != null) { 35 | List roomNos = new ArrayList<>(); 36 | for (Room room2 : rooms) { 37 | roomNos.add(room2.getRoomNo()); 38 | } 39 | customerDto.setRoomNos(roomNos); 40 | } 41 | return customerDto; 42 | } 43 | 44 | public static Customer dto2Customer(CustomerDto customerDto) { 45 | if (customerDto == null ){ 46 | return null; 47 | } 48 | 49 | Customer customer = new Customer(); 50 | customer.setId(customerDto.getId()); 51 | customer.setName(customerDto.getName()); 52 | customer.setIdCard(customerDto.getIdCard()); 53 | customer.setPhoneNo(customerDto.getPhoneNo()); 54 | customer.setComment(customerDto.getComment()); 55 | return customer; 56 | } 57 | 58 | 59 | public static Room roomDto2Room(RoomDto roomDto){ 60 | Room room = new Room(); 61 | room.setId(roomDto.getId()); 62 | room.setRoomNo(roomDto.getRoomNo()); 63 | room.setType(RoomType.parse(roomDto.getType())); 64 | room.setStatus(RoomStatus.parse(roomDto.getStatus())); 65 | room.setCheckInDate(roomDto.getCheckInDate()); 66 | room.setCheckOutDate(roomDto.getCheckOutDate()); 67 | 68 | //房中现住客人 69 | List customerDtos = roomDto.getCustomers(); 70 | if (customerDtos != null && customerDtos.size() > 0) { 71 | List customers = new ArrayList<>(); 72 | for (CustomerDto customerDto : customerDtos) { 73 | customers.add(Converters.dto2Customer(customerDto)); 74 | } 75 | room.setCustomers(customers); 76 | } 77 | 78 | //房中床铺信息(床铺与床关系由床铺维护,所以需要指定床铺所在的床) 79 | List bedDtos = roomDto.getBeds(); 80 | if (bedDtos !=null && bedDtos.size() > 0) { 81 | List beds = new ArrayList<>(); 82 | for (BedDto bedDto : bedDtos) { 83 | Bed bed = new Bed(); 84 | bed.setId(bedDto.getId()); 85 | bed.setName(bedDto.getName()); 86 | bed.setSize(bedDto.getSize()); 87 | bed.setType(BedType.parse(bedDto.getType())); 88 | bed.setRoom(room); 89 | beds.add(bed); 90 | } 91 | room.setBeds(beds); 92 | } 93 | 94 | List chargeDtos = roomDto.getCharges(); 95 | if (chargeDtos != null && chargeDtos.size() > 0){ 96 | List charges = new ArrayList<>(); 97 | for (ChargeDto chargeDto : chargeDtos){ 98 | Charge charge = new Charge(); 99 | charge.setId(chargeDto.getId()); 100 | charge.setCount(chargeDto.getCount()); 101 | charge.setMoney(chargeDto.getMoney()); 102 | charge.setTimeUnit(RoomType.parse(chargeDto.getTimeUnit())); 103 | charge.setStartDate(chargeDto.getStartDate()); 104 | charge.setEndDate(chargeDto.getEndDate()); 105 | charge.setRoom(room); 106 | charges.add(charge); 107 | } 108 | room.setCharges(charges); 109 | } 110 | return room; 111 | } 112 | 113 | 114 | public static RoomDto room2RoomDto(Room room){ 115 | RoomDto roomDto = new RoomDto(); 116 | roomDto.setId(room.getId()); 117 | roomDto.setRoomNo(room.getRoomNo()); 118 | roomDto.setType(room.getType().getName()); 119 | roomDto.setStatus(room.getStatus().getCode()); 120 | roomDto.setCheckInDate(room.getCheckInDate()); 121 | roomDto.setCheckOutDate(room.getCheckOutDate()); 122 | 123 | //现今房价 124 | roomDto.setMoney(getStringPrice(getCurrentMoney(room))); 125 | 126 | //房中现住客人 127 | List customers = room.getCustomers(); 128 | List customerDtos = new ArrayList<>(); 129 | if (customers != null ) { 130 | for (Customer customer : customers) { 131 | customerDtos.add(Converters.customer2Dto(customer)); 132 | } 133 | } 134 | roomDto.setCustomers(customerDtos); 135 | 136 | 137 | //房中床铺信息 138 | List beds = room.getBeds(); 139 | List bedDtos = new ArrayList<>(); 140 | if (beds != null ){ 141 | for (Bed bed : beds) { 142 | BedDto bedDto = new BedDto(); 143 | bedDto.setId(bed.getId()); 144 | bedDto.setName(bed.getName()); 145 | bedDto.setSize(bed.getSize()); 146 | bedDto.setType(bed.getType().getName()); 147 | bedDtos.add(bedDto); 148 | } 149 | } 150 | roomDto.setBeds(bedDtos); 151 | 152 | // 客房定价历史 153 | List charges = room.getCharges(); 154 | List chargeDtos = new ArrayList<>(); 155 | if (charges != null) { 156 | for (Charge charge : charges) { 157 | ChargeDto chargeDto = new ChargeDto(); 158 | chargeDto.setId(charge.getId()); 159 | chargeDto.setCount(charge.getCount()); 160 | chargeDto.setMoney(charge.getMoney()); 161 | chargeDto.setTimeUnit(charge.getTimeUnit().getName()); 162 | chargeDto.setStartDate(charge.getStartDate()); 163 | chargeDto.setEndDate(charge.getEndDate()); 164 | chargeDtos.add(chargeDto); 165 | } 166 | } 167 | roomDto.setCharges(chargeDtos); 168 | 169 | //客房收入历史记录 170 | List incomes = room.getIncomes(); 171 | List incomeDtos = new ArrayList<>(); 172 | if (incomes != null ) { 173 | for (Income income : incomes) { 174 | IncomeDto incomeDto = new IncomeDto(); 175 | incomeDto.setId(income.getId()); 176 | incomeDto.setRommNo(income.getRoom().getRoomNo()); 177 | incomeDto.setIncoming(getStringPrice(income.getIncoming())); 178 | incomeDto.setLogoutDate(income.getLogoutDate()); 179 | incomeDtos.add(incomeDto); 180 | } 181 | } 182 | roomDto.setIncomes(incomeDtos); 183 | return roomDto; 184 | } 185 | 186 | public static String getStringPrice(float price) { 187 | if (price == 0) { 188 | return "0.00"; 189 | } 190 | 191 | DecimalFormat df = new DecimalFormat("#.00"); 192 | return df.format(price); 193 | } 194 | 195 | public static float getCurrentMoney(Room room){ 196 | List charges = room.getCharges(); 197 | if (charges == null) { 198 | return 0; 199 | } 200 | 201 | Date date = new Date(); 202 | 203 | float price = 0; 204 | for(Charge charge : charges){ 205 | if (charge.getTimeUnit() == room.getType() && charge.getStartDate().getTime() <= date.getTime() && charge.getEndDate().getTime() >= date.getTime()) { 206 | price = charge.getMoney() / (charge.getCount() == 0 ? 1 : charge.getCount()); 207 | break; 208 | } 209 | } 210 | 211 | return price; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /hotel-server/src/main/java/com/hotel/controller/RoomController.java: -------------------------------------------------------------------------------- 1 | package com.hotel.controller; 2 | 3 | import com.hotel.annotation.AddRoomGroup; 4 | import com.hotel.annotation.LoginGroup; 5 | import com.hotel.constant.RetCode; 6 | import com.hotel.constant.RoomStatus; 7 | import com.hotel.constant.RoomType; 8 | import com.hotel.dto.*; 9 | import com.hotel.entity.*; 10 | import com.hotel.service.CustomerService; 11 | import com.hotel.service.RoomService; 12 | import com.hotel.util.Converters; 13 | import io.swagger.annotations.*; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.util.StringUtils; 19 | import org.springframework.validation.annotation.Validated; 20 | import org.springframework.web.bind.annotation.*; 21 | 22 | import javax.transaction.Transactional; 23 | import javax.validation.constraints.NotNull; 24 | import java.util.ArrayList; 25 | import java.util.Date; 26 | import java.util.List; 27 | 28 | /** 29 | * @author guangyong.yang 30 | * @date 2019-01-20 31 | * 客房restful接口 32 | */ 33 | @RestController 34 | @Slf4j 35 | @CrossOrigin 36 | @RequestMapping("api") 37 | @Api(description = "客房RestFul接口") 38 | public class RoomController { 39 | 40 | private RoomService roomService; 41 | private CustomerService customerService; 42 | 43 | 44 | @Autowired 45 | public RoomController(RoomService roomService, CustomerService customerService) { 46 | this.roomService = roomService; 47 | this.customerService = customerService; 48 | } 49 | 50 | /** 51 | * 搜索客房 52 | * @param condition 搜索条件 53 | * @return 客房列表 54 | */ 55 | @GetMapping("/rooms") 56 | @ResponseStatus(HttpStatus.OK) 57 | @ApiOperation("搜索客房") 58 | @ApiImplicitParams({@ApiImplicitParam(name="roomNo",dataType = "string",value="客房号",paramType = "form"), 59 | @ApiImplicitParam(name="userName",dataType = "String",value="客人姓名",paramType = "form"), 60 | @ApiImplicitParam(name="idCard",dataType = "String",value="客人身份证号",paramType = "form"), 61 | @ApiImplicitParam(name="phoneNo",dataType = "String",value="客人手机号",paramType = "form")}) 62 | public List searchRooms(RoomSearchCondition condition) { 63 | //查询客房信息 64 | List rooms = roomService.search(condition); 65 | 66 | 67 | //将room信息转化为roomDto 68 | List roomDtos = new ArrayList<>(); 69 | for(Room room: rooms) { 70 | roomDtos.add(Converters.room2RoomDto(room)); 71 | } 72 | 73 | return roomDtos; 74 | } 75 | 76 | 77 | @PostMapping("/rooms") 78 | @ApiOperation("新增客房") 79 | @Transactional(rollbackOn = Exception.class) 80 | public ResponseEntity create(@RequestBody @Validated(AddRoomGroup.class) RoomDto roomDto) { 81 | String roomNo = roomDto.getRoomNo(); 82 | List roomByRoomNo = roomService.findRoomByRoomNo(roomNo); 83 | if (roomByRoomNo != null && roomByRoomNo.size() > 0) { 84 | return new ResponseEntity<>(ResultDto.builder() 85 | .success(false) 86 | .code(RetCode.RETCODE_40012) 87 | .message("已经存在"+roomNo+"客房") 88 | .build(),HttpStatus.BAD_REQUEST); 89 | } 90 | 91 | //客房初始状态为"空房"状态 92 | roomDto.setStatus(RoomStatus.EMPTY.getCode()); 93 | Room room = roomService.save(Converters.roomDto2Room(roomDto)); 94 | return new ResponseEntity<>(ResultDto.builder() 95 | .success(true) 96 | .code(RetCode.RETCODE_20005) 97 | .message(RetCode.RETCODE_20005_MSG) 98 | .data(Converters.room2RoomDto(room)) 99 | .build(),HttpStatus.OK); 100 | } 101 | 102 | @PutMapping("/rooms/login") 103 | @ApiOperation("登记") 104 | @Transactional(rollbackOn = Exception.class) 105 | public ResponseEntity login(@RequestBody @Validated(LoginGroup.class) RoomDto roomDto) { 106 | 107 | List customerDtos = roomDto.getCustomers(); 108 | if (customerDtos == null) { 109 | return new ResponseEntity<>(ResultDto.builder() 110 | .success(false) 111 | .code(RetCode.RETCODE_40011) 112 | .message("客人信息不能为空") 113 | .build(),HttpStatus.BAD_REQUEST); 114 | } 115 | 116 | //处理客人信息 117 | List customers = new ArrayList<>(); 118 | for (CustomerDto customerDto : customerDtos) { 119 | List customerByIdCard = customerService.findCustomerByIdCard(customerDto.getIdCard()); 120 | if (customerByIdCard.size() > 1) { 121 | return new ResponseEntity<>(ResultDto.builder().success(false) 122 | .code(RetCode.RETCODE_40001) 123 | .message(customerDto.getIdCard() + "身份证存在多个客人信息").build(), HttpStatus.BAD_REQUEST); 124 | } 125 | 126 | Customer customer; 127 | if (customerByIdCard.size() == 1) { 128 | customer = customerByIdCard.get(0); 129 | List rooms = customer.getRooms(); 130 | if (rooms != null) { 131 | for (Room room : rooms) { 132 | if (room.getRoomNo().equals(roomDto.getRoomNo())) { 133 | return new ResponseEntity<>(ResultDto.builder().success(false) 134 | .code(RetCode.RETCODE_40005) 135 | .message(customer.getIdCard() + "身份证号的客人已登记过" + room.getRoomNo() + "客房:").build(), 136 | HttpStatus.BAD_REQUEST); 137 | } 138 | } 139 | } 140 | } else { 141 | customer = new Customer(); 142 | customer.setIdCard(customerDto.getIdCard()); 143 | } 144 | 145 | customer.setComment(customerDto.getComment()); 146 | customer.setName(customerDto.getName()); 147 | customer.setPhoneNo(customerDto.getPhoneNo()); 148 | customers.add(customer); 149 | } 150 | 151 | List rooms = roomService.findRoomByRoomNo(roomDto.getRoomNo()); 152 | if (rooms == null || rooms.size() == 0) { 153 | return new ResponseEntity<>(ResultDto.builder().success(false) 154 | .code(RetCode.RETCODE_40002) 155 | .message(roomDto.getRoomNo() +"号客房不存在").build(),HttpStatus.BAD_REQUEST); 156 | } 157 | 158 | if (rooms.size() > 1) { 159 | return new ResponseEntity<>(ResultDto.builder().success(false) 160 | .code(RetCode.RETCODE_40003) 161 | .message(roomDto.getRoomNo() +"号客房存在多个房间").build(),HttpStatus.BAD_REQUEST); 162 | } 163 | 164 | Room room = rooms.get(0); 165 | if (room.getStatus() == RoomStatus.UNUSED) { 166 | return new ResponseEntity<>(ResultDto.builder().success(false) 167 | .code(RetCode.RETCODE_40004) 168 | .message(roomDto.getRoomNo() +"号客房为不可订状态").build(),HttpStatus.BAD_REQUEST); 169 | } 170 | 171 | List beds = room.getBeds(); 172 | if (beds == null || beds.size() == 0) { 173 | return new ResponseEntity<>(ResultDto.builder().success(false) 174 | .code(RetCode.RETCODE_40016) 175 | .message(roomDto.getRoomNo() +"号客房没有维护床铺信息,不可登记").build(),HttpStatus.BAD_REQUEST); 176 | } 177 | 178 | float currentMoney = Converters.getCurrentMoney(room); 179 | if ( currentMoney == 0) { 180 | return new ResponseEntity<>(ResultDto.builder().success(false) 181 | .code(RetCode.RETCODE_40017) 182 | .message(roomDto.getRoomNo() +"号客房没有维护当前房价或房价为0,不可登记").build(),HttpStatus.BAD_REQUEST); 183 | } 184 | 185 | room.setCheckInDate(roomDto.getCheckInDate()); 186 | room.setCheckOutDate(roomDto.getCheckOutDate()); 187 | room.setStatus(RoomStatus.CHECKINGIN); 188 | room.getCustomers().addAll(customers); 189 | roomService.save(room); 190 | 191 | return new ResponseEntity<>(ResultDto.builder() 192 | .success(true) 193 | .code(RetCode.RETCODE_20001) 194 | .message(RetCode.RETCODE_20001_MSG) 195 | .build(),HttpStatus.OK); 196 | } 197 | 198 | 199 | @PutMapping("/rooms/logout") 200 | @ApiOperation("退房") 201 | @Transactional(rollbackOn = Exception.class) 202 | public ResponseEntity logout(@NotNull @RequestBody @ApiParam("客房ID") Integer roomId) { 203 | log.trace("退房,id:{}",roomId); 204 | 205 | Room room = roomService.findOne(roomId); 206 | if (room == null) { 207 | return new ResponseEntity<>(ResultDto.builder().success(false) 208 | .code(RetCode.RETCODE_40018) 209 | .message("没有查询到ID为"+roomId+"的客房信息").build(),HttpStatus.BAD_REQUEST); 210 | } 211 | 212 | //计算入住费用 213 | int interval = calculateInterval(room.getType(),room.getCheckInDate(),room.getCheckOutDate()); 214 | float price = Converters.getCurrentMoney(room); 215 | String sum = Converters.getStringPrice(interval*price); 216 | 217 | Income income = new Income(); 218 | income.setIncoming(Float.valueOf(sum)); 219 | income.setRoom(room); 220 | Date date = new Date(); 221 | income.setLogoutDate(date); 222 | 223 | List incomes = room.getIncomes(); 224 | incomes.add(income); 225 | 226 | // 清空客户的入住、退房日期、客人以及修改状态 227 | room.setCustomers(null); 228 | room.setCheckInDate(null); 229 | room.setCheckOutDate(null); 230 | room.setStatus(RoomStatus.EMPTY); 231 | 232 | roomService.save(room); 233 | 234 | //构造返回数据 235 | return new ResponseEntity<>(ResultDto.builder().success(true) 236 | .code(RetCode.RETCODE_20002) 237 | .message(RetCode.RETCODE_20002_MSG) 238 | .data(LogoutDto.builder().logoutDate(date) 239 | .interval(String.valueOf(interval)) 240 | .type(room.getType().getName()) 241 | .price(String.valueOf(price)) 242 | .sum(sum) 243 | .roomNo(room.getRoomNo()) 244 | .build()).build(),HttpStatus.OK); 245 | } 246 | 247 | 248 | 249 | @PutMapping("/rooms") 250 | @ApiOperation("更新客房信息") 251 | @ResponseStatus(HttpStatus.CREATED) 252 | @Transactional(rollbackOn = Exception.class) 253 | public ResponseEntity updateRoom(@RequestBody RoomDto roomDto){ 254 | if ( roomDto == null || StringUtils.isEmpty(roomDto.getId())){ 255 | return new ResponseEntity<>(ResultDto.builder() 256 | .success(false) 257 | .code(RetCode.RETCODE_40008) 258 | .message("客房信息不能为空") 259 | .build(),HttpStatus.BAD_REQUEST); 260 | } 261 | 262 | log.trace("更新客房,id:{}",roomDto.getId()); 263 | 264 | // 客房状态判断 265 | Room room = roomService.findOne(roomDto.getId()); 266 | if (room != null) { 267 | if (room.getStatus() == RoomStatus.CHECKINGIN 268 | && !StringUtils.isEmpty(roomDto.getStatus()) 269 | && RoomStatus.parse(roomDto.getStatus()) != RoomStatus.CHECKINGIN) { 270 | return new ResponseEntity<>(ResultDto.builder() 271 | .success(false) 272 | .code(RetCode.RETCODE_40004) 273 | .message("客房" + room.getRoomNo() + "当前有客,不能更改为其它状态") 274 | .build(), HttpStatus.BAD_REQUEST); 275 | } 276 | 277 | if (room.getStatus() == RoomStatus.CHECKINGIN 278 | && !StringUtils.isEmpty(roomDto.getType()) 279 | && RoomType.parse(roomDto.getType()) != room.getType()) { 280 | return new ResponseEntity<>(ResultDto.builder() 281 | .success(false) 282 | .code(RetCode.RETCODE_40004) 283 | .message("客房" + room.getRoomNo() + "当前有客,不能更改房型") 284 | .build(), HttpStatus.BAD_REQUEST); 285 | } 286 | 287 | if (room.getStatus() == RoomStatus.EMPTY 288 | && !StringUtils.isEmpty(roomDto.getStatus()) 289 | && RoomStatus.parse(roomDto.getStatus()) != RoomStatus.EMPTY 290 | && RoomStatus.parse(roomDto.getStatus()) != RoomStatus.UNUSED) { 291 | return new ResponseEntity<>(ResultDto.builder() 292 | .success(false) 293 | .code(RetCode.RETCODE_40004) 294 | .message("客房" + room.getRoomNo() + "目前是空房,只能更改为不可订状态") 295 | .build(), HttpStatus.BAD_REQUEST); 296 | } 297 | 298 | if (room.getStatus() == RoomStatus.UNUSED 299 | && !StringUtils.isEmpty(roomDto.getStatus()) 300 | && RoomStatus.parse(roomDto.getStatus()) != RoomStatus.UNUSED 301 | && RoomStatus.parse(roomDto.getStatus()) != RoomStatus.EMPTY) { 302 | return new ResponseEntity<>(ResultDto.builder() 303 | .success(false) 304 | .code(RetCode.RETCODE_40004) 305 | .message("客房" + room.getRoomNo() + "目前不可订,只能更改为空房状态") 306 | .build(), HttpStatus.BAD_REQUEST); 307 | } 308 | } else { 309 | return new ResponseEntity<>(ResultDto.builder() 310 | .success(false) 311 | .code(RetCode.RETCODE_40013) 312 | .message("客房ID:"+roomDto.getId()+"错误") 313 | .build(),HttpStatus.BAD_REQUEST); 314 | } 315 | 316 | 317 | RoomDto roomDto1 = Converters.room2RoomDto(roomService.updateRoom(Converters.roomDto2Room(roomDto),roomDto.getUpdatingBed())); 318 | return new ResponseEntity<>(ResultDto.builder() 319 | .success(true) 320 | .code(RetCode.RETCODE_20004) 321 | .message(RetCode.RETCODE_20004_MSG) 322 | .data(roomDto1).build(),HttpStatus.OK); 323 | } 324 | 325 | 326 | /** 327 | * 根据客房ID号获取客房信息 328 | * @param id 客房ID 329 | * @return 客房信息 330 | */ 331 | @GetMapping("/rooms/{id}") 332 | @ApiOperation("根据客房ID号获取客房信息") 333 | public ResponseEntity getRoom(@ApiParam(value = "客房ID",required = true) @PathVariable("id") Integer id){ 334 | log.trace("根据客房id获取客房信息;id:{}",id); 335 | Room room = roomService.findOne(id); 336 | if ( room == null ) { 337 | return new ResponseEntity<>(ResultDto.builder().success(false).code(RetCode.RETCODE_40009).message("没有查询到客房信息").build(),HttpStatus.NOT_FOUND); 338 | } 339 | return new ResponseEntity<>(ResultDto.builder().success(true).code(RetCode.RETCODE_20003).message(RetCode.RETCODE_20003).data(Converters.room2RoomDto(room)).build(),HttpStatus.OK); 340 | } 341 | 342 | 343 | private int calculateInterval(RoomType type,Date in,Date out) { 344 | if (in == null || out == null) { 345 | throw new IllegalArgumentException("没有入住或退房日期"); 346 | } 347 | 348 | int result; 349 | switch (type) { 350 | case HOUR: 351 | result = (int)Math.ceil((out.getTime() - in.getTime()) / 3600.0f / 1000.0f); 352 | break; 353 | case DAY: 354 | result = (int)Math.ceil((out.getTime() - in.getTime()) / 24.0f /3600.0f / 1000.0f); 355 | break; 356 | case MONTH: 357 | result = (int)Math.ceil((out.getTime() - in.getTime()) / 30.0f / 24.0f / 3600.0f / 1000.0f); 358 | break; 359 | default: 360 | throw new IllegalArgumentException("房间类型不正确"); 361 | } 362 | 363 | return result; 364 | } 365 | } 366 | --------------------------------------------------------------------------------