建立账号管理,论坛模块,重构登陆注册组件

更新接口地址环境变量
change
panqihua 5 years ago
parent 261f0b58a5
commit 2d788991b3
  1. 4
      package.json
  2. 20
      src/app/account/account/account.module.ts
  3. 29
      src/app/account/login/login.component.html
  4. 6
      src/app/account/login/login.component.spec.ts
  5. 17
      src/app/account/login/login.component.ts
  6. 16
      src/app/account/login/login.service.spec.ts
  7. 59
      src/app/account/login/login.service.ts
  8. 32
      src/app/account/register/register.component.html
  9. 6
      src/app/account/register/register.component.spec.ts
  10. 17
      src/app/account/register/register.component.ts
  11. 16
      src/app/account/register/register.service.spec.ts
  12. 22
      src/app/account/register/register.service.ts
  13. 22
      src/app/account/resetpwd/resetpwd.component.html
  14. 6
      src/app/account/resetpwd/resetpwd.component.spec.ts
  15. 11
      src/app/account/resetpwd/resetpwd.component.ts
  16. 11
      src/app/app-routing.module.ts
  17. 3
      src/app/app.component.html
  18. 6
      src/app/app.component.spec.ts
  19. 37
      src/app/app.component.ts
  20. 30
      src/app/app.module.ts
  21. 23
      src/app/commons.ts
  22. 1
      src/app/error/error.component.html
  23. 0
      src/app/error/error.component.scss
  24. 25
      src/app/error/error.component.spec.ts
  25. 18
      src/app/error/error.component.ts
  26. 47
      src/app/forum/forum.component.html
  27. 42
      src/app/forum/forum.component.scss
  28. 6
      src/app/forum/forum.component.spec.ts
  29. 5
      src/app/forum/forum.component.ts
  30. 16
      src/app/forum/forum/forum.module.ts
  31. 5
      src/app/interface/Http.ts
  32. 26
      src/app/message/Message.ts
  33. 7
      src/app/message/message.component.html
  34. 0
      src/app/message/message.component.scss
  35. 25
      src/app/message/message.component.spec.ts
  36. 22
      src/app/message/message.component.ts
  37. 16
      src/app/message/message.service.spec.ts
  38. 48
      src/app/message/message.service.ts
  39. 6
      src/environments/environment.prod.ts
  40. 7
      src/environments/environment.ts
  41. 76
      yarn.lock

@ -23,9 +23,9 @@
"@ng-bootstrap/ng-bootstrap": "^6.0.0-rc.0",
"@ngx-translate/core": "^12.1.1",
"@ngx-translate/http-loader": "^4.0.0",
"angular2-cookie": "^1.2.6",
"bootstrap": "^4.4.1",
"jquery": "^3.4.1",
"ngx-cookie-service": "^2.3.0",
"popper.js": "^1.16.1",
"rxjs": "~6.5.4",
"rxjs-compat": "^6.5.4",
@ -37,9 +37,9 @@
"@angular/cli": "~9.0.1",
"@angular/compiler-cli": "~9.0.0",
"@angular/language-service": "~9.0.0",
"@types/node": "^12.11.1",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~4.2.1",

@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// 登陆组件
import {LoginComponent} from '../login/login.component';
// 注册组件
import {RegisterComponent} from '../register/register.component';
// 重置密码组件
import {ResetpwdComponent} from '../resetpwd/resetpwd.component';
/**
*
*/
@NgModule({
declarations: [LoginComponent, RegisterComponent, ResetpwdComponent],
imports: [
CommonModule
]
})
export class AccountModule { }

@ -2,39 +2,38 @@
<div [style]="height" class="d-flex align-items-center justify-content-center">
<form [formGroup]="loginForm" class="col-6">
<!-- 管理员名-->
<!-- 管理员名-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'login.manager_name' | translate }}</span>
</div>
<input autocomplete="managerName" type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
<input autocomplete="managerName" type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
</div>
<!-- 管理员密码-->
<!-- 管理员密码-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'login.password' | translate }}</span>
</div>
<input autocomplete="new-password" type="password" class="form-control text-center" [placeholder]="('tip.input' | translate)+('login.password' | translate)" formControlName="password">
<input autocomplete="new-password" type="password" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('login.password' | translate)" formControlName="password">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" [routerLink]="['/reset_pwd']">{{ 'button.forget_pwd' | translate }}</button>
<button class="btn btn-outline-secondary" type="button"
[routerLink]="['/reset_pwd']">{{ 'button.forget_pwd' | translate }}</button>
</div>
</div>
<div class="col-5 mx-auto text-center mb-3">
<!-- 登陆按钮-->
<!-- 登陆按钮-->
<button type="button" (click)="login()" class="btn btn-primary btn-lg">{{ 'button.login' | translate }}</button>
<!-- 注册按钮-->
<button type="button" class="btn btn-info btn-lg ml-3" [routerLink]="['/register']">{{ 'button.register' | translate }}</button>
<!-- 注册按钮 -->
<button type="button" class="btn btn-info btn-lg ml-3"
[routerLink]="['/register']">{{ 'button.register' | translate }}</button>
</div>
<div *ngIf="message!==null" [class]="'alert alert-'+messageStyle+' alert-dismissible fade show'" role="alert">
<div class="text-center">{{message}}</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close" (click)="clearError()" >
<span aria-hidden="true" >&times;</span>
</button>
</div>
<app-message></app-message>
</form>
</div>

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { LoginComponent } from './login.component';
import {LoginComponent} from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
@ -8,7 +8,7 @@ describe('LoginComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
declarations: [LoginComponent]
})
.compileComponents();
}));

@ -2,6 +2,8 @@ import {Component, OnInit} from '@angular/core';
import {FormBuilder} from '@angular/forms';
import {Commons} from '../../commons';
import {Router} from '@angular/router';
import {LoginService} from './login.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
@ -16,28 +18,25 @@ export class LoginComponent extends Commons implements OnInit {
});
constructor(private fb: FormBuilder,private router: Router) {
constructor(private fb: FormBuilder, private router: Router, private loginService: LoginService) {
super();
}
ngOnInit(): void {
}
// 登陆方法
login() {
console.debug(this.loginForm.value);
this.request('http://localhost:8080/api/manager/login', JSON.stringify(this.loginForm.value),
res => {
this.message = res.message;
if (res.result === 'OK') {
this.messageStyle = 'info';
// 发送登陆请求
if (this.loginService.checkToken()) {
this.router.navigateByUrl('/forum');
} else {
this.messageStyle = 'warning';
this.loginService.request(JSON.stringify(this.loginForm.value));
}
});
}
}

@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {LoginService} from './login.service';
describe('LoginService', () => {
let service: LoginService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LoginService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,59 @@
import {Injectable} from '@angular/core';
import {CookieService} from 'ngx-cookie-service';
import {Router} from '@angular/router';
import {HttpInterface} from '../../interface/Http';
import {MessageService} from '../../message/message.service';
@Injectable({
providedIn: 'root'
})
export class LoginService implements HttpInterface {
constructor(
private cookieService: CookieService,
private router: Router,
private messageService: MessageService
) {
}
/**
*
*/
checkToken(): boolean {
return false;
}
/**
*
*/
login(body: string) {
if (this.checkToken()) {
this.router.navigateByUrl('/forum');
} else {
this.request(body);
}
}
/**
*
* @param body
*/
request(body: string) {
// res => {
// this.message = res.message;
// if (res.result === 'OK') {
// this.messageStyle = 'info';
// this.cookieService.set(environment.tokenKey, res.body.token, 3600);
// this.router.navigateByUrl('/forum');
// } else {
// this.messageStyle = 'warning';
// }
// }
this.messageService.danger('登陆失败');
return false;
}
url(): string {
return '/api/manager/login';
}
}

@ -7,7 +7,8 @@
<div class="input-group-prepend">
<span class="input-group-text">{{ 'login.manager_name' | translate }}</span>
</div>
<input type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
<input type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
</div>
<!-- 管理员密码-->
@ -15,48 +16,49 @@
<div class="input-group-prepend">
<span class="input-group-text">{{ 'login.password' | translate }}</span>
</div>
<input autocomplete="password" type="password" class="form-control text-center" [placeholder]="('tip.input' | translate)+('login.password' | translate)" formControlName="password">
<input autocomplete="password" type="password" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('login.password' | translate)" formControlName="password">
</div>
<!-- 管理员确认密码-->
<!-- 管理员确认密码-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'register.confirm_pwd' | translate }}</span>
</div>
<input autocomplete="confirmPassword" type="password" class="form-control text-center" [placeholder]="('tip.input' | translate)+('register.confirm_pwd' | translate)" formControlName="confirmPassword">
<input autocomplete="confirmPassword" type="password" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('register.confirm_pwd' | translate)"
formControlName="confirmPassword">
</div>
<!-- 手机号-->
<!-- 手机号-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'register.mobile' | translate }}</span>
</div>
<input type="number" class="form-control text-center" [placeholder]="('tip.input' | translate)+('register.mobile' | translate)" formControlName="mobile">
<input type="number" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('register.mobile' | translate)" formControlName="mobile">
</div>
<!-- 邮箱-->
<!-- 邮箱-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'register.email' | translate }}</span>
</div>
<input type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('register.email' | translate)" formControlName="email">
<input type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('register.email' | translate)" formControlName="email">
</div>
<div class="col-7 mx-auto text-center mb-3">
<!-- 注册按钮-->
<button type="button" (click)="register()" class="btn btn-primary btn-lg">{{ 'button.register' | translate }}</button>
<button type="button" (click)="register()"
class="btn btn-primary btn-lg">{{ 'button.register' | translate }}</button>
<!-- 返回登陆按钮-->
<button type="button" [routerLink]="['/login']"
class="btn btn-info btn-lg ml-3">{{ 'button.backLogin' | translate }}</button>
</div>
<div *ngIf="message!==null" [class]="'alert alert-'+messageStyle+' alert-dismissible fade show'" role="alert">
<div class="text-center">{{message}}</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close" (click)="clearError()" >
<span aria-hidden="true" >&times;</span>
</button>
</div>
<app-message></app-message>
</form>
</div>

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { RegisterComponent } from './register.component';
import {RegisterComponent} from './register.component';
describe('RegisterComponent', () => {
let component: RegisterComponent;
@ -8,7 +8,7 @@ describe('RegisterComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RegisterComponent ]
declarations: [RegisterComponent]
})
.compileComponents();
}));

@ -1,6 +1,9 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit} from '@angular/core';
import {FormBuilder} from '@angular/forms';
import {Commons} from '../../commons';
import {Router} from '@angular/router';
import {RegisterService} from './register.service';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
@ -17,7 +20,11 @@ export class RegisterComponent extends Commons implements OnInit {
email: []
});
constructor(private fb: FormBuilder) {
constructor(
private fb: FormBuilder,
private router: Router,
private registerService: RegisterService
) {
super();
}
@ -25,10 +32,6 @@ export class RegisterComponent extends Commons implements OnInit {
}
register() {
this.request('http://localhost:8080/api/manager/register', JSON.stringify(this.registerForm.value),
res => {
this.message = res.message;
this.messageStyle = (res.result === 'OK' ? 'info' : 'warning');
});
this.registerService.register(JSON.stringify(this.registerForm.value));
}
}

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { RegisterService } from './register.service';
describe('RegisterService', () => {
let service: RegisterService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(RegisterService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import {HttpInterface} from '../../interface/Http';
@Injectable({
providedIn: 'root'
})
export class RegisterService implements HttpInterface {
constructor() { }
/**
*
* @param body
*/
register(body: string) {
}
url(): string {
return '/api/manager/register';
}
}

@ -1,36 +1,42 @@
<!--设置高度,添加bootstrap样式类d-flex align-items-center 使表单垂直居中,justify-content-center水平居中-->
<div [style]="height" class="d-flex align-items-center justify-content-center">
<form [formGroup]="resetForm" class="col-6">
<!-- 管理员名-->
<!-- 管理员名-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'login.manager_name' | translate }}</span>
</div>
<input type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
<input type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('login.manager_name' | translate)" formControlName="managerName">
</div>
<!-- 邮箱-->
<!-- 邮箱 -->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'register.email' | translate }}</span>
</div>
<input type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('register.email' | translate)" formControlName="email">
<input type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('register.email' | translate)" formControlName="email">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" (click)="sendCode()">{{ 'button.send_code' | translate }}</button>
<button class="btn btn-outline-secondary" type="button"
(click)="sendCode()">{{ 'button.send_code' | translate }}</button>
</div>
</div>
<!-- 验证码-->
<!-- 验证码-->
<div class="input-group mb-3 mx-auto col-7">
<div class="input-group-prepend">
<span class="input-group-text">{{ 'reset_pwd.code' | translate }}</span>
</div>
<input type="text" class="form-control text-center" [placeholder]="('tip.input' | translate)+('reset_pwd.code' | translate)" formControlName="verificationCode">
<input type="text" class="form-control text-center"
[placeholder]="('tip.input' | translate)+('reset_pwd.code' | translate)"
formControlName="verificationCode">
</div>
<div class="col-7 mx-auto text-center">
<!-- 重置密码按钮 -->
<button type="button" (click)="resetPwd()" class="btn btn-primary btn-lg">{{ 'button.reset_pwd' | translate }}</button>
<button type="button" (click)="resetPwd()"
class="btn btn-primary btn-lg">{{ 'button.reset_pwd' | translate }}</button>
<!-- 返回登陆按钮-->
<button type="button" [routerLink]="['/login']"
class="btn btn-info btn-lg ml-3">{{ 'button.backLogin' | translate }}</button>

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { ResetpwdComponent } from './resetpwd.component';
import {ResetpwdComponent} from './resetpwd.component';
describe('ResetpwdComponent', () => {
let component: ResetpwdComponent;
@ -8,7 +8,7 @@ describe('ResetpwdComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ResetpwdComponent ]
declarations: [ResetpwdComponent]
})
.compileComponents();
}));

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import {Component, OnInit} from '@angular/core';
import {FormBuilder} from '@angular/forms';
import {Commons} from '../../commons';
@Component({
@ -21,14 +21,17 @@ export class ResetpwdComponent extends Commons implements OnInit {
super();
}
//
ngOnInit(): void {
}
sendCode(){
// 发送验证码
sendCode() {
alert('发送验证码');
}
resetPwd(){
// 重置密码
resetPwd() {
alert('重置密码');
}

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {LoginComponent} from './account/login/login.component';
import {RegisterComponent} from './account/register/register.component';
import {ResetpwdComponent} from './account/resetpwd/resetpwd.component';
@ -14,11 +14,14 @@ export const routes: Routes = [
// 重置密码
{path: 'reset_pwd', component: ResetpwdComponent},
// 论坛管理'
{path: 'forum', component: ForumComponent}
{path: 'forum', component: ForumComponent},
// 自动重定向到登陆
{path: '', redirectTo: 'login', pathMatch: 'full'}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
export class AppRoutingModule {
}

@ -6,7 +6,8 @@
<a [routerLink]="['/forum']" *ngIf="isLogin">{{ 'forum.name' | translate }}</a>
<div class="dropdown">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
{{ 'language' | translate }}
</a>

@ -1,6 +1,6 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import {async, TestBed} from '@angular/core/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {AppComponent} from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {

@ -1,6 +1,15 @@
import {Component} from '@angular/core';
import {Router, ActivatedRoute} from '@angular/router';
// 路由
import {NavigationStart, Router} from '@angular/router';
// 国际化服务
import {TranslateService} from '@ngx-translate/core';
// 路由事件
import {Observable} from 'rxjs';
import {filter} from 'rxjs/operators';
// cookie操作
import {CookieService} from 'ngx-cookie-service';
// 环境变量
import {environment} from './../environments/environment';
@Component({
selector: 'app-root',
@ -10,13 +19,35 @@ import {TranslateService} from '@ngx-translate/core';
export class AppComponent {
// 登陆状态
isLogin;
isLogin = this.cookieService.check(environment.tokenKey);
constructor(public route: ActivatedRoute, public translate: TranslateService, private router: Router) {
navStart: Observable<NavigationStart>;
/**
*
* @param translate
* @param router
* @param cookieService cookie管理服务
*/
constructor(public translate: TranslateService, private router: Router, private cookieService: CookieService) {
this.navStart = router.events.pipe(
filter(evt => evt instanceof NavigationStart)
) as Observable<NavigationStart>;
}
public async ngOnInit() {
this.navStart.subscribe(evt => {
if (evt.url === '/' && this.isLogin) {
this.router.navigateByUrl('/forum');
} else if (evt.url === '/') {
this.router.navigateByUrl('/login');
} else {
}
});
// 语言初始化(若未设置语言, 则取浏览器语言)
const currentLanguage = await localStorage.getItem('currentLanguage') || this.translate.getBrowserCultureLang();
// 当在assets/i18n中找不到对应的语言翻译时,使用'zh-CN'作为默认语言

@ -4,33 +4,41 @@ import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
// 主框架
import {AppComponent} from './app.component';
// 登陆
import {LoginComponent} from './account/login/login.component';
// 响应式表单
import {ReactiveFormsModule} from '@angular/forms';
// 国际化支持
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import { RegisterComponent } from './account/register/register.component';
import { ResetpwdComponent } from './account/resetpwd/resetpwd.component';
import { ForumComponent } from './forum/forum.component';
// cookie
import {CookieService} from 'ngx-cookie-service';
// 提示框组件
import {MessageComponent} from './message/message.component'
// 异常处理组件
import {ErrorComponent} from './error/error.component';
// 账号管理模块
import {AccountModule} from './account/account/account.module';
import {ForumModule} from './forum/forum/forum.module';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http);
}
/**
*
*/
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
ResetpwdComponent,
ForumComponent
MessageComponent,
ErrorComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule,
AccountModule,
ForumModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
@ -40,7 +48,7 @@ export function HttpLoaderFactory(http: HttpClient) {
}
})
],
providers: [],
providers: [CookieService],
bootstrap: [AppComponent]
})
export class AppModule {

@ -1,21 +1,24 @@
import {environment} from './../environments/environment';
// 组件通用配置
export class Commons {
// 页面高度=屏幕的高度/2
height = 'height:' + screen.height / 2 + 'px';
message = null;
messageStyle = 'warning';
// 清空警告信息
clearError() {
this.message = null;
}
// post请求
request(url, b, res) {
const header = new Headers();
header.append('Content-Type', 'application/json');
const req = new Request(url,
const req = new Request(environment.apiServer + url,
{method: 'POST', body: b, headers: header});
fetch(req).then(r => r.json()).then(res);
fetch(req).then(r => {
if (r.status === 200) {
return r.json();
} else {
throw new Error('666');
}
}).then(res).catch(err => {
console.error(err);
});
}
}

@ -0,0 +1 @@
<p>error works!</p>

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ErrorComponent} from './error.component';
describe('ErrorComponent', () => {
let component: ErrorComponent;
let fixture: ComponentFixture<ErrorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ErrorComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ErrorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,18 @@
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-error',
templateUrl: './error.component.html',
styleUrls: ['./error.component.scss']
})
export class ErrorComponent implements OnInit {
private message;
constructor() {
}
ngOnInit(): void {
}
}

@ -1,16 +1,16 @@
<!--公告栏-->
<div class="notice bg-light col-5 mx-auto">
<!-- 公告标志-->
<!-- 公告标志-->
<h1 class="text-center mb-3 mt-3">{{ 'forum.notice' | translate }}</h1>
<!-- 公告内容-->
<!-- 公告内容-->
<div class="notice-content border-success mt-3 mb-3 position-relative">
<!-- 公告标题-->
<!-- 公告标题-->
<h2 class="text-center mb-3 mt-3">公告标题</h2>
<!-- 公告正文-->
<!-- 公告正文-->
<div class="notice-body overflow-auto">
公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容公告内容
</div>
<!-- 公告底部-->
<!-- 公告底部-->
<div class="notice-footer border border-info text-right align-text-bottom position-absolute">
<div class="col-12">{{ 'forum.sender' | translate }} :admin<br>{{ 'forum.time' | translate }}:2020-01-01</div>
</div>
@ -19,7 +19,7 @@
<!--审核贴-->
<div class="row border-warning check p-3">
<!-- 审核帖标识-->
<!-- 审核帖标识-->
<h1 class="text-info col-12 text-center">{{ 'forum.check' | translate }}</h1>
@ -27,7 +27,7 @@
<!--帖子列表-->
<ul class="list-group">
<!-- 帖子列表-->
<!-- 帖子列表-->
<li class="list-group-item active">Cras justo odio</li>
<li class="list-group-item">Dapibus ac facilisis in</li>
<li class="list-group-item">Morbi leo risus</li>
@ -40,7 +40,7 @@
<li class="list-group-item">Vestibulum at eros</li>
</ul>
<!-- 分页按钮-->
<!-- 分页按钮-->
<ul class="pagination ml-5 mt-3">
<li class="page-item"><a class="page-link" href="#">Previous</a></li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
@ -51,13 +51,13 @@
</div>
<!-- 帖子内容-->
<!-- 帖子内容-->
<div class="col-9 post border-secondary p-3 position-relative">
<!-- 活动帖子-->
<!-- 活动帖子-->
<ng-container *ngIf="true;else elseBlock">
<!-- 发件人信息-->
<!-- 发件人信息-->
<div class="position-absolute sender">
<!-- 发帖人头像-->
<img src="/" class="headimg mb-3"/>
@ -71,7 +71,7 @@
</div>
</div>
<!-- 帖子框架-->
<!-- 帖子框架-->
<div>
<!-- 标题-->
<h1 class="text-info col-12 text-center mb-3 forum-title">活动名</h1>
@ -81,13 +81,16 @@
</div>
<!-- 帖子底部-->
<div class="forum-footer border-info p-3 mb-3">
<h3 class="text-center">{{ 'forum.active_date' | translate:{startDate:'2020-01-01',endDate:'2020-02-01'} }}</h3>
<h3 class="text-center">{{ 'forum.active_score' | translate:{score:10} }}</h3>
<h3 class="text-center">{{ 'forum.active_date' | translate:{
startDate: '2020-01-01',
endDate: '2020-02-01'
} }}</h3>
<h3 class="text-center">{{ 'forum.active_score' | translate:{score: 10} }}</h3>
</div>
</div>
</ng-container>
<!-- 投诉帖子-->
<!-- 投诉帖子-->
<ng-template #elseBlock>
<div class="complaint row p-3">
@ -95,7 +98,7 @@
<!-- 投诉人信息-->
<div class="col-6 border-info l-label">
<!-- 头像-->
<!-- 头像-->
<div class="d-flex justify-content-center mb-3 m-headimg">
<img src="/" class="headimg">
</div>
@ -103,10 +106,10 @@
<div class="text-center border-info l-label">信用分</div>
</div>
<!-- 被投诉人信息-->
<!-- 被投诉人信息-->
<div class="col-6 border-info l-label">
<div class="d-flex justify-content-center mb-3 m-headimg">
<!-- 头像-->
<!-- 头像-->
<img src="/" class="headimg">
</div>
<div class="text-center border-info mb-3 l-label">投诉人</div>
@ -118,14 +121,14 @@
<!-- 投诉内容-->
<div class="col-7 border-success l-label h-100">
<!-- 投诉项-->
<!-- 投诉项-->
<h1 class="text-center">投诉项</h1>
<!-- 投诉内容-->
<!-- 投诉内容-->
<div class="forum-content border-info overflow-auto mb-3">
投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容投诉内容
</div>
<!-- 投诉时间-->
<h1 class="text-center">{{ 'forum.complaint_time' | translate:{time:'2020-01-01'} }}</h1>
<!-- 投诉时间-->
<h1 class="text-center">{{ 'forum.complaint_time' | translate:{time: '2020-01-01'} }}</h1>
</div>
</div>
</ng-template>

@ -1,41 +1,47 @@
//公告栏
.notice{
.notice {
height: 300px;
}
//公告正文边框
.notice-content{
.notice-content {
height: 200px;
border-style: solid;
border-width: 3px;
}
//公告正文
.notice-body{
.notice-body {
max-height: 100px;
}
//公告底部
.notice-footer{
.notice-footer {
top: 145px;
bottom: 0;
width: 100%;
}
//帖子正文
.post{
.post {
border-style: solid;
border-width: 3px;
}
//公告编辑信息
.author{
width:100px
.author {
width: 100px
}
//待审核帖子父边框
.check{
.check {
border-style: solid;
border-width: 3px;
}
//帖子内容
.forum-content{
.forum-content {
border-style: solid;
border-width: 3px;
height: 300px;
@ -44,52 +50,54 @@
//活动贴start
// 帖子标题
.forum-title{
.forum-title {
z-index: 0;
}
//发帖人信息
.sender{
.sender {
margin-top: 65px;
margin-left: 45px;
}
//头像
.headimg{
.headimg {
height: 100px;
width: 100px;
z-index: 1;
}
//发帖人信息
.forum-label{
.forum-label {
border-style: solid;
border-width: 3px;
width: 100px;
height: 30px;
}
//帖子底部
.forum-footer{
.forum-footer {
border-style: solid;
border-width: 3px;
height: 100px;
}
//活动贴end
//投诉帖start
.complaint{
.complaint {
height: 460px;
}
//名称边框
.l-label{
.l-label {
border-style: solid;
border-width: 3px;
}
//头像顶部距离
.m-headimg{
.m-headimg {
margin-top: 50px;
}

@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import { ForumComponent } from './forum.component';
import {ForumComponent} from './forum.component';
describe('ForumComponent', () => {
let component: ForumComponent;
@ -8,7 +8,7 @@ describe('ForumComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ForumComponent ]
declarations: [ForumComponent]
})
.compileComponents();
}));

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-forum',
@ -7,7 +7,8 @@ import { Component, OnInit } from '@angular/core';
})
export class ForumComponent implements OnInit {
constructor() { }
constructor() {
}
ngOnInit(): void {
}

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
// 论坛组件
import {ForumComponent} from '../forum.component';
/**
*
*/
@NgModule({
declarations: [ForumComponent],
imports: [
CommonModule
]
})
export class ForumModule { }

@ -0,0 +1,5 @@
// 请求接口
export interface HttpInterface {
// 接口地址
url(): string;
}

@ -0,0 +1,26 @@
export class Message {
// 提示框信息
// tslint:disable-next-line:variable-name
private _message = null;
// 提示框样式
// tslint:disable-next-line:variable-name
private _messageStyle;
get message() {
return this._message;
}
set message(value) {
this._message = value;
}
get messageStyle() {
return this._messageStyle;
}
set messageStyle(value) {
this._messageStyle = value;
}
}

@ -0,0 +1,7 @@
<div *ngIf="message.message" [class]="'alert alert-'+message.messageStyle+' alert-dismissible fade show'"
role="alert">
<div class="text-center">{{message.message}}</div>
<button type="button" class="close" data-dismiss="alert" aria-label="Close" (click)="message.message=null">
<span aria-hidden="true">&times;</span>
</button>
</div>

@ -0,0 +1,25 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MessageComponent} from './message.component';
describe('MessageComponent', () => {
let component: MessageComponent;
let fixture: ComponentFixture<MessageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MessageComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MessageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,22 @@
import {Component, OnInit} from '@angular/core';
import {MessageService} from './message.service';
@Component({
selector: 'app-message',
templateUrl: './message.component.html',
styleUrls: ['./message.component.scss']
})
export class MessageComponent implements OnInit {
message;
constructor(
private messageService: MessageService
) {
}
ngOnInit(): void {
this.message = this.messageService;
}
}

@ -0,0 +1,16 @@
import {TestBed} from '@angular/core/testing';
import {MessageService} from './message.service';
describe('MessageService', () => {
let service: MessageService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(MessageService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,48 @@
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class MessageService {
// 提示框信息
// tslint:disable-next-line:variable-name
private _message = null;
// 提示框样式
// tslint:disable-next-line:variable-name
private _messageStyle;
get message() {
return this._message;
}
set message(value) {
this._message = value;
}
get messageStyle() {
return this._messageStyle;
}
set messageStyle(value) {
this._messageStyle = value;
}
info(message: string) {
this.messageStyle = 'info';
this.message = message;
}
danger(message: string) {
this.messageStyle = 'danger';
this.message = message;
}
constructor() {
}
}

@ -1,3 +1,7 @@
export const environment = {
production: true
production: true,
// 服务端地址
apiServer: 'http://localhost',
// token key
tokenKey: 'token'
};

@ -3,7 +3,12 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
// 服务端地址
apiServer: 'http://localhost:8080',
// token key
tokenKey: 'token'
};
/*

@ -1368,11 +1368,6 @@ alphanum-sort@^1.0.0:
resolved "https://registry.npm.taobao.org/alphanum-sort/download/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
angular2-cookie@^1.2.6:
version "1.2.6"
resolved "https://registry.npm.taobao.org/angular2-cookie/download/angular2-cookie-1.2.6.tgz#8fa845531e777adb042fe2f339c0040f6ddbf09d"
integrity sha1-j6hFUx53etsEL+LzOcAED23b8J0=
ansi-colors@4.1.1:
version "4.1.1"
resolved "https://registry.npm.taobao.org/ansi-colors/download/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@ -3030,11 +3025,6 @@ deep-equal@^1.0.1:
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
deep-extend@^0.6.0:
version "0.6.0"
resolved "https://registry.npm.taobao.org/deep-extend/download/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha1-xPp8lUBKF6nD6Mp+FTcxK3NjMKw=
default-gateway@^4.2.0:
version "4.2.0"
resolved "https://registry.npm.taobao.org/default-gateway/download/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@ -3145,11 +3135,6 @@ destroy@~1.0.4:
resolved "https://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
detect-libc@^1.0.2:
version "1.0.3"
resolved "https://registry.npm.taobao.org/detect-libc/download/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
detect-node@^2.0.4:
version "2.0.4"
resolved "https://registry.npm.taobao.org/detect-node/download/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
@ -4540,7 +4525,7 @@ humanize-ms@^1.2.1:
dependencies:
ms "^2.0.0"
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
version "0.4.24"
resolved "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1579333981154&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=
@ -4669,7 +4654,7 @@ inherits@2.0.3:
resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@1.3.5, ini@^1.2.0, ini@^1.3.2, ini@^1.3.4, ini@~1.3.0:
ini@1.3.5, ini@^1.2.0, ini@^1.3.2, ini@^1.3.4:
version "1.3.5"
resolved "https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=
@ -6195,15 +6180,6 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
needle@^2.2.1:
version "2.3.2"
resolved "https://registry.npm.taobao.org/needle/download/needle-2.3.2.tgz?cache=0&sync_timestamp=1580746953901&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fneedle%2Fdownload%2Fneedle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528"
integrity sha1-M0LeoQC3FglgpFDcjCIWCscSpSg=
dependencies:
debug "^3.2.6"
iconv-lite "^0.4.4"
sax "^1.2.4"
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@ -6224,6 +6200,11 @@ netrc@^0.1.4:
resolved "https://registry.npm.taobao.org/netrc/download/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444"
integrity sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=
ngx-cookie-service@^2.3.0:
version "2.3.0"
resolved "https://registry.npm.taobao.org/ngx-cookie-service/download/ngx-cookie-service-2.3.0.tgz#290e6f55047ad9e9f1aa4ddf30d1ab8d41927949"
integrity sha1-KQ5vVQR62enxqk3fMNGrjUGSeUk=
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.npm.taobao.org/nice-try/download/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@ -6272,22 +6253,6 @@ node-libs-browser@^2.2.1:
util "^0.11.0"
vm-browserify "^1.0.1"
node-pre-gyp@*:
version "0.14.0"
resolved "https://registry.npm.taobao.org/node-pre-gyp/download/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
integrity sha1-mgWWUzuHcom8rU4UOYLKPZBN3IM=
dependencies:
detect-libc "^1.0.2"
mkdirp "^0.5.1"
needle "^2.2.1"
nopt "^4.0.1"
npm-packlist "^1.1.6"
npmlog "^4.0.2"
rc "^1.2.7"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^4.4.2"
node-releases@^1.1.44, node-releases@^1.1.49:
version "1.1.49"
resolved "https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.49.tgz#67ba5a3fac2319262675ef864ed56798bb33b93e"
@ -6295,7 +6260,7 @@ node-releases@^1.1.44, node-releases@^1.1.49:
dependencies:
semver "^6.3.0"
nopt@^4.0.0, nopt@^4.0.1:
nopt@^4.0.0:
version "4.0.1"
resolved "https://registry.npm.taobao.org/nopt/download/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
@ -6393,7 +6358,7 @@ npm-package-arg@^7.0.0:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"
npm-packlist@^1.1.12, npm-packlist@^1.1.6:
npm-packlist@^1.1.12:
version "1.4.8"
resolved "https://registry.npm.taobao.org/npm-packlist/download/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
integrity sha1-Vu5swTW5+YrT1Rwcldoiu7my7z4=
@ -6466,7 +6431,7 @@ npmconf@^2.1.2:
semver "2 || 3 || 4"
uid-number "0.0.5"
"npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0, npmlog@^4.0.2:
"npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0:
version "4.1.2"
resolved "https://registry.npm.taobao.org/npmlog/download/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=
@ -7643,16 +7608,6 @@ raw-loader@3.1.0:
loader-utils "^1.1.0"
schema-utils "^2.0.1"
rc@^1.2.7:
version "1.2.8"
resolved "https://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
integrity sha1-zZJL9SAKB1uDwYjNa54hG3/A0+0=
dependencies:
deep-extend "^0.6.0"
ini "~1.3.0"
minimist "^1.2.0"
strip-json-comments "~2.0.1"
read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/read-cache/download/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@ -8018,7 +7973,7 @@ rimraf@3.0.0:
dependencies:
glob "^7.1.3"
rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
version "2.7.1"
resolved "https://registry.npm.taobao.org/rimraf/download/rimraf-2.7.1.tgz?cache=0&sync_timestamp=1581229865753&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Frimraf%2Fdownload%2Frimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha1-NXl/E6f9rcVmFCwp1PB8ytSD4+w=
@ -8122,7 +8077,7 @@ saucelabs@^1.5.0:
dependencies:
https-proxy-agent "^2.2.1"
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
sax@>=0.6.0, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.npm.taobao.org/sax/download/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha1-KBYjTiN4vdxOU1T6tcqold9xANk=
@ -8863,11 +8818,6 @@ strip-indent@^2.0.0:
resolved "https://registry.npm.taobao.org/strip-indent/download/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
style-loader@1.0.0:
version "1.0.0"
resolved "https://registry.npm.taobao.org/style-loader/download/style-loader-1.0.0.tgz?cache=0&sync_timestamp=1579301707625&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstyle-loader%2Fdownload%2Fstyle-loader-1.0.0.tgz#1d5296f9165e8e2c85d24eee0b7caf9ec8ca1f82"
@ -8963,7 +8913,7 @@ tapable@^1.0.0, tapable@^1.1.3:
resolved "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha1-ofzMBrWNth/XpF2i2kT186Pme6I=
tar@^4.4.10, tar@^4.4.2:
tar@^4.4.10:
version "4.4.13"
resolved "https://registry.npm.taobao.org/tar/download/tar-4.4.13.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftar%2Fdownload%2Ftar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha1-Q7NkvFKIjVVSmGN7ENYHkCVKtSU=

Loading…
Cancel
Save