改造在线竞拍六

改造在线竞拍六
auction_server.ts

/**
 * Created by Administrator on 2019/1/4/004.
 */
import * as express from 'express'
import {Server} from "ws";
/*app :声明服务器端提供的http服务的*/
const app = express();
export class Product {
    constructor(
        public id: number,
        public title: string,
        public price: number,
        public rating: number,
        public desc: string,
        public categories: Array<string>
    ) {
    }
}
export class Comment {
    constructor(
        public id: number,
        public productId: number,
        public timestamp: string,
        public user: string,
        public rating: number,
        public content: string
    ) {
    }
}
const products: Product[] = [
    new Product(1, '第一个商品', 1.99, 2.5, '这是第一个商品', ['电子产品', '硬件设备']),
    new Product(2, '第二个商品', 2.99, 3.5, '这是第二个商品', ['图书']),
    new Product(3, '第三个商品', 3.99, 4.5, '这是第三个商品', ['硬件设备']),
    new Product(4, '第四个商品', 4.99, 3.5, '这是第四个商品', ['电子产品', '硬件设备']),
    new Product(5, '第五个商品', 5.99, 2.5, '这是第五个商品', ['电子产品']),
    new Product(6, '第六个商品', 6.99, 3.5, '这是第六个商品', ['图书'])
];
const comments: Comment[] = [
    new Comment(1, 1, '2017-02-02 22:22:22', '张三', 3, '东西不错'),
    new Comment(2, 1, '2017-03-02 22:22:22', '李四', 4, '东西挺不错'),
    new Comment(3, 1, '2017-04-02 22:22:22', '王五', 2, '东西还行'),
    new Comment(4, 2, '2017-04-02 22:22:22', '赵六', 3, '东西挺好'),
    new Comment(5, 2, '2017-05-02 22:22:22', '孙七', 2, '东西一般'),
];
app.get('/',(req, res) => {
   res.send("Hello Express!")
});
app.get('/api/products',(req, res) => {
    res.json(products)
});
app.get('/api/product/:id',(req, res) => {
    res.json(products.find((product) => product.id == req.params.id))
});
app.get('/api/product/:id/comments',(req,res) => {
    res.json(comments.filter((comment: Comment) => comment.productId == req.params.id))
});
const server = app.listen(8000,"localhost",() => {
    console.log("服务器已经启动,地址是:http://localhost:8000");
});
const wsServer = new Server({port:8085});
wsServer.on("connection",websocket => {
    websocket.send("这个消息是服务器主动推送的");
    websocket.on("message",message => {
        console.log("接收到消息:"+message);
    })
});
/*定时推送*/
setInterval(() => {
    if(wsServer.clients){
        wsServer.clients.forEach(client => {
            client.send("这是定时推送");
        })
    }
},2000);


在这里插入图片描述
product.service.ts

import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
@Injectable({
  providedIn: 'root'
})
export class ProductService {
  /*  初始化服务数据*/
  constructor(private http: HttpClient) { }
  getProducts(): Observable<any> {
    /*return this.products;*/
    return this.http.get('/api/products');
  }
  getProduct(id: number): Observable<any> {
    /*return this.products.find((product) => product.id == id );*/
    return this.http.get('/api/product/' + id);
  }
  getCommentsForProductId(id: number): Observable<any> {
    /*return this.comments.filter((comment: Comment) => comment.productId == id);*/
    return this.http.get('/api/product/' + id + '/comments');
  }
  getAllCategories(): string[] {
    return ['电子产品', '硬件设备', '图书'];
  }
}
export class Product {
  constructor(
    public id: number,
    public title: string,
    public price: number,
    public rating: number,
    public desc: string,
    public categories: Array<string>
  ) {
  }
}
export class Comment {
  constructor(
    public id: number,
    public productId: number,
    public timestamp: string,
    public user: string,
    public rating: number,
    public content: string
  ) {
  }
}

在这里插入图片描述
product.component.ts

在这里插入图片描述
product.component.html

在这里插入图片描述
product-detail.component.ts

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Comment, Product, ProductService} from '../shared/product.service';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
  product: Product;
  comments: Comment[];
  /* 星级评论*/
  newRating = 5;
  newComment = '默认好评';
  isCommentHidden = true;
  constructor(private  routeInfo: ActivatedRoute,
              private  productService: ProductService) { }
  ngOnInit() {
    const productId: number = this.routeInfo.snapshot.params['productId'];
    /*http服务:异步服务,服务器数据也许未返回,属性可能undefined
    * 解决:模板加 ?
    * */
    /*3.调用方法获得商品*/
     /* this.product = this.productService.getProduct(productId);*/
     /*模板多处使用product,此处手动订阅*/
     this.productService.getProduct(productId).subscribe(
       product => this.product = product
     );
    /*4.调用方法获取评论*/
    /*使用数组的方法comments,此处手动订阅*/
    this.productService.getCommentsForProductId(productId).subscribe(
      comments => this.comments = comments
    );
  }
  ...
}

在这里插入图片描述
product-detail.component.html

<div class="thumbnail">
  <img src="http://placehold.it/820x230"/>
  <div>
    <h4 class="pull-right">{{product?.price}}</h4>
    <h4>{{product?.title}}</h4>
    <h4>{{product?.desc}}</h4>
  </div>
  <div>
    <p class="pull-right">{{comments?.length}}</p>
    <p>
      <app-stars [rating]="product?.rating"></app-stars>
    </p>
  </div>
</div>
<div class="well">
  <div>
    <button class="btn btn-success" (click)="isCommentHidden = !isCommentHidden">发表评论</button>
  </div>
  <!--星级评论-->
  <div [hidden]="isCommentHidden">
    <div>
    <app-stars [(rating)]="newRating" [readonly]="false"></app-stars>
    </div>
    <div>
      <textarea [(ngModel)]="newComment"></textarea>
    </div>
    <div>
      <button class="btn" (click)="addComment()">提交</button>
    </div>
  </div>
  <div class="row" *ngFor="let comment of comments">
    <hr>
    <div class="col-md-12">
      <app-stars [rating]="comment.rating"></app-stars>
      <span>{{comment.user}}</span>
      <span class="pull-right">{{comment.timestamp}}</span>
      <p></p>
      <p>{{comment.content}}</p>
    </div>
  </div>
</div>

在这里插入图片描述

效果图

在这里插入图片描述
在这里插入图片描述

商品搜索功能

服务器端:

...
app.get('/api/products',(req,res) => {
    let result = products;
    let params = req.query;
    if(JSON.stringify(params)!="{}"){
        if(params.title) {
            result = result.filter((p) => p.title.indexOf(params.title) != -1);
        }
        if(params.price && result.length>0) {
            result = result.filter((p) => p.price <= parseInt(params.price));
        }
        if(params.category !== "-1" && result.length>0) {
            console.log("3");
            result =  result.filter((p) => p.categories.indexOf(params.category) != -1);
        }
    }
    res.json(result);
});
...

在这里插入图片描述
客户端:
app.module.ts

import {HttpClientModule} from '@angular/common/http';
...
imports: [
    ...
    HttpClientModule
  ],
  ...

在这里插入图片描述

search.component.ts

import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import {ProductService} from '../shared/product.service';

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.css']
})
export class SearchComponent implements OnInit {

  /*建数据模型*/
  formModel: FormGroup;
  categories: string[];
  constructor(private productService: ProductService, fb: FormBuilder) {
    fb = new FormBuilder();
    this.formModel = fb.group({
      title: ['', Validators.minLength(3)],
      price: [null, this.positiveNumberValidator],
      category: ['-1']
    });
  }

  ngOnInit() {
    this.categories = this.productService.getAllCategories();
  }
  /*正数校验器---》商品价格搜索框*/
  positiveNumberValidator(control: FormControl): any {
    if (!control.value) {
      return null;
    }
    const price = parseInt (control.value, 10);
    if (price > 0) {
      return null;
    } else {
      return { positiveNumber: true};
    }
  }
  onSearch() {
    if (this.formModel.valid) {
      console.log(this.formModel.value);
      this.productService.searchEvent.emit(this.formModel.value);
    }
  }
}

在这里插入图片描述

product.service.ts

import { Component, OnInit } from '@angular/core';
import {Product, ProductService} from '../shared/product.service';
import {Observable} from 'rxjs/index';
@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  /*搜索商品*/
  /*关键字*/
  /*private keyword: string;
  private titleFilter: FormControl = new FormControl();*/
  private products: Observable<Product[]>;
  private imgUrl = 'http://placehold.it/320x150';
  /*注入服务*/
  constructor(private productService: ProductService) {
    /*this.titleFilter.valueChanges
      .subscribe(
        value => this.keyword = value
      );*/
  }
  /*调用方法获得商品*/
  ngOnInit() {
    this.products = this.productService.getProducts();
    /*订阅流,事件发送到流*/
    this.productService.searchEvent.subscribe(
      params => this.products = this.productService.search(params)
    );
  }
}

在这里插入图片描述
product.service.ts

import {EventEmitter, Injectable} from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import {Observable} from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  /*EventEmitter:既可以作为事件订阅者,也可以作为事件发送者*//*搜索事件的流*/
  searchEvent: EventEmitter<ProductSearchParams> = new EventEmitter();
  /* 初始化服务数据*/
  constructor(private http: HttpClient) { }
  getProducts(): Observable<any> {/*return this.products;*/
    return this.http.get('/api/products');
  }
  getProduct(id: number): Observable<any> {/*return this.products.find((product) => product.id == id );*/
    return this.http.get('/api/product/' + id);
  }
  getCommentsForProductId(id: number): Observable<any> {/*return this.comments.filter((comment: Comment) => comment.productId == id);*/
    return this.http.get('/api/product/' + id + '/comments');
  }
  getAllCategories(): string[] {
    return ['电子产品', '硬件设备', '图书'];
  }
  search(params: ProductSearchParams): Observable<any> {
    return this.http.get('/api/products', {params: this.encodeParams(params)});
  }
  private encodeParams(params: ProductSearchParams) {
    return Object.keys(params)
      .filter(key => params[key])
      .reduce((sum: HttpParams, key: string) => {
        sum = sum.append(key, params[key]);
        return sum;
      }, new HttpParams());
  }
}
export class ProductSearchParams {
  constructor(
    public title: string,
    public price: number,
    public category: string
  ) {
  }
}
export class Product {
  constructor(
    public id: number,
    public title: string,
    public price: number,
    public rating: number,
    public desc: string,
    public categories: Array<string>
  ) {
  }
}
export class Comment {
  constructor(
    public id: number,
    public productId: number,
    public timestamp: string,
    public user: string,
    public rating: number,
    public content: string
  ) {
  }
}

在这里插入图片描述

效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

商品关注功能

在这里插入图片描述

点击关注按钮->创建连接

product-detail.component.html

<!--商品关注-->
<div class="thumbnail">
  <button class="btn btn-default btn-lg"
    [class.active] = "isWatched"
    (click)="watchProduct()">
    {{isWatched?'取消关注':'关注'}}
  </button>
  <label>最新出价:{{currentBid}}</label>
</div>

在这里插入图片描述
web-socket.service.ts

import { Injectable } from '@angular/core';
import {Observable} from 'rxjs/index';
@Injectable({
  providedIn: 'root'
})
export class WebSocketService {

  ws: WebSocket;
  constructor() { }
  createObservableSocket(url: string, id: number): Observable<any> {
    this.ws = new WebSocket(url);
    return new Observable(
      observable => {
        this.ws.onmessage = (event) => observable.next(event.data);
        this.ws.onerror = (event) => observable.error(event);
        this.ws.onclose = (event) => observable.complete();
        this.ws.onopen = (event) => this.sendMessage({productId: id});
      }
    );
  }
  sendMessage(message: any) {
    this.ws.send(JSON.stringify(message));
  }
}

在这里插入图片描述
app.module.ts

import {WebSocketService} from './shared/web-socket.service';
...
providers: [ProductService, WebSocketService],
...

在这里插入图片描述
product-detail.component.ts

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {Comment, Product, ProductService} from '../shared/product.service';
import {WebSocketService} from "../shared/web-socket.service";

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html',
  styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
  product: Product;
  comments: Comment[];
  newRating = 5;
  newComment = '默认好评';
  isCommentHidden = true;
  /* 商品关注*/
  isWatched = false;
  currentBid: number;
  constructor(private  routeInfo: ActivatedRoute,
              private  productService: ProductService,
              private  wsService: WebSocketService) { }
  ngOnInit() {
    const productId: number = this.routeInfo.snapshot.params['productId'];
     this.productService.getProduct(productId).subscribe(
       product => {
         this.product = product;
         this.currentBid = product.price;
       }
     );
    this.productService.getCommentsForProductId(productId).subscribe(
      comments => this.comments = comments
    );
  }
  addComment () {
    const comment = new Comment(0, this.product.id, new Date().toISOString(), 'someone', this.newRating, this.newComment);
    this.comments.unshift(comment);
    const sum = this.comments.reduce((sum, comment) => sum + comment.rating, 0);
    this.product.rating = sum / this.comments.length;
    this.newComment = '默认好评';
    this.newRating = 5;
    this.isCommentHidden = true;
  }
  /* 商品关注*/
  watchProduct () {
    this.isWatched = !this.isWatched;
    /*给一个服务器地址,createObservableSocket返回可观测的流,
    客户端订阅流接收服务端发送的消息*/
    this.wsService.createObservableSocket('ws://localhost:8085',this.product.id)
      .subscribe();
  }
}

在这里插入图片描述

将productId放入集合,生成新的商品出价,定时推送给客户端

look this :https://ask.csdn.net/questions/696871

服务器端

...
/*将productId放入一个集合*/
/*subscription:客户端,关注商品*/
const subscription = new Map<any, number[]>();
const wsServer = new Server({port:8085});
wsServer.on("connection",websocket => {
    /*websocket.send("这个消息是服务器主动推送的");*/
    websocket.on("message",message => {
        console.log("接收到消息:"+message);
        /*接收到的消息转换,字符串-->javascript对象*/
        let messageObj = JSON.parse(<string>message);
        let productIds = subscription.get(websocket) || [];
        subscription.set(websocket, [...productIds, messageObj.productId]);
    })
});
/**/
const currentBids = new Map<number, number>();
/*定时推送给客户端*/
setInterval(() => {
    /*生成新的商品出价*/
    products.forEach(p => {
        let currentBid = currentBids.get(p.id) || p.price;
        let newBid = currentBid + Math.random()*5;
        currentBids.set(p.id, newBid);
    })
    /*循环客户端,推送感兴趣商品价格*/
    subscription.forEach((productIds: number[], ws) => {
        if(ws.readyState === 1){
            let newBids = productIds.map( pid => ({
                productId: pid,
                bid: currentBids.get(pid)
            }));
            ws.send(JSON.stringify(newBids));
        }else{/*客户端断开连接*/
            subscription.delete(ws);
        }
    });
},2000);

在这里插入图片描述
客户端

product-detail.component.ts

...
watchProduct () {
    this.isWatched = !this.isWatched;
    /*给一个服务器地址,createObservableSocket返回可观测的流,
    客户端订阅流接收服务端发送的消息*/
    this.wsService.createObservableSocket('ws://localhost:8085', this.product.id)
      .subscribe(
        products => {
          const product = products.find( p => p.productId = this.product.id);
          this.currentBid = product.bid;
        }
      );
  }
...

在这里插入图片描述
web-socket.service.ts

import {map} from 'rxjs/internal/operators';
...
createObservableSocket(url: string, id: number): Observable<any> {
    this.ws = new WebSocket(url);
    return new Observable<string>(
      observable => {
        this.ws.onmessage = (event) => observable.next(event.data);
        this.ws.onerror = (event) => observable.error(event);
        this.ws.onclose = (event) => observable.complete();
        this.ws.onopen = (event) => this.sendMessage({productId: id});
      }
    ).pipe(map(message => JSON.parse(<string>message)));
  }
...

在这里插入图片描述

效果图

在这里插入图片描述
在这里插入图片描述

商品取消关注功能

客户端

product-detail.component.ts

...
/* 当前订阅的流:返回的值*/
  subscription: Subscription;
...
watchProduct () {
    if (this.subscription) {
      this.subscription.unsubscribe();
      this.isWatched = false;
      this.subscription = null;
    } else {
      this.isWatched = true;
      this.subscription = this.wsService.createObservableSocket('ws://localhost:8085', this.product.id)
        .subscribe(
          products => {
            const product = products.find( p => p.productId = this.product.id);
            this.currentBid = product.bid;
          }
        );
    }
  }
...

在这里插入图片描述

效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值