File

src/app/layout/picture-manager/picture-storehouse/picture-storehouse.component.ts

Implements

OnInit OnDestroy

Metadata

selector app-picture-storehouse
styleUrls ./picture-storehouse.component.less
templateUrl ./picture-storehouse.component.html

Index

Properties
Methods

Constructor

constructor(http: HttpClient, nzImageService: NzImageService, fb: FormBuilder, cookie: CookieService, pictureManagerService: PictureManagerService, message: NzMessageService, router: Router, cache: CacheService, shareService: ShareService)
Parameters :
Name Type Optional
http HttpClient No
nzImageService NzImageService No
fb FormBuilder No
cookie CookieService No
pictureManagerService PictureManagerService No
message NzMessageService No
router Router No
cache CacheService No
shareService ShareService No

Methods

compareTag
compareTag(c1: string, c2: string)
Parameters :
Name Type Optional
c1 string No
c2 string No
Returns : boolean
errorPicture
errorPicture(ev: Event, url: string)
Parameters :
Name Type Optional
ev Event No
url string No
Returns : void
handleCancelPictureProp
handleCancelPictureProp()
Returns : void
Async handleOkPictureProp
handleOkPictureProp()
Returns : any
handleTags
handleTags(tag: string)
Parameters :
Name Type Optional
tag string No
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onScroll
onScroll()
Returns : void
openPicture
openPicture(url: string)
Parameters :
Name Type Optional
url string No
Returns : void
searchPicture
searchPicture()
Returns : void
showIntro
showIntro()
Returns : void
showPictureProp
showPictureProp(picture: any, index: number)
Parameters :
Name Type Optional
picture any No
index number No
Returns : void
Async toEditImage
toEditImage(picture: Picture)
Parameters :
Name Type Optional
picture Picture No
Returns : {}
updateKeywords
updateKeywords()
Returns : void

Properties

isOkLoading
Default value : false
isUpdateLoading
Default value : false
isVisible
Default value : false
limit
Type : number
Default value : 100
loading
Default value : false
page
Type : number
Default value : 1
pics
Type : Picture[]
Default value : []
picture
Type : any
picturePropForm
Type : FormGroup
Default value : null
pictureSearchForm
Type : FormGroup
Default value : null
re
Default value : /#/gi
tags
Type : string[]
Default value : []
tagsApi
Default value : `${environment.host}/picture-manager/tags`
updateKeywordsApi
Default value : `${environment.host}/picture-manager/update-keywords`
userLoading
Default value : false
users
Type : string[]
Default value : []
usersApi
Default value : `${environment.host}/picture-manager/users`
import { HttpClient } from '@angular/common/http';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { NzImageService } from 'ng-zorro-antd/image';
import { CookieService } from 'ngx-cookie-service';
import { PictureManagerService } from '../picture-manager.service';
import * as clipboard from 'clipboard-polyfill/text';
import { NzMessageService } from 'ng-zorro-antd/message';
import { debounceTime, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
import { Picture } from '../picture-manager';
import { Router } from '@angular/router';
import { CacheService } from 'src/app/share/cache.service';
import { environment } from 'src/environments/environment';
import { ShareService } from 'src/app/share/share.service';

@Component({
  selector: 'app-picture-storehouse',
  templateUrl: './picture-storehouse.component.html',
  styleUrls: ['./picture-storehouse.component.less']
})
export class PictureStorehouseComponent implements OnInit, OnDestroy {

  // pics list
  pics: Picture[] = [];

  // 分页
  limit = 100;
  page = 1;
  loading = false;

  // 图片弹出层
  isVisible = false;
  isOkLoading = false;
  isUpdateLoading = false;
  picture: any;

  users: string[] = [];
  tags: string[] = [];
  userLoading = false;

  // 图片属性表单
  picturePropForm: FormGroup = null;
  // 图片搜索表单
  pictureSearchForm: FormGroup = null;

  // 本地图片搜索api
  updateKeywordsApi = `${environment.host}/picture-manager/update-keywords`;
  usersApi = `${environment.host}/picture-manager/users`;
  tagsApi = `${environment.host}/picture-manager/tags`;

  // 替换‘#’
  re = /#/gi;

  constructor(
    private http: HttpClient,
    private nzImageService: NzImageService,
    private fb: FormBuilder,
    private cookie: CookieService,
    private pictureManagerService: PictureManagerService,
    private message: NzMessageService,
    private router: Router,
    private cache: CacheService,
    private shareService: ShareService,
  ) {
    // 图片属性表单
    this.picturePropForm = this.fb.group({
      site: [this.cookie.get('site'), [Validators.required]],
      keywords: ['', [Validators.required]]
    });
    this.picturePropForm.get('site').valueChanges.subscribe((value: string) => {
      this.cookie.set('site', value, new Date('2088-12-12'), undefined, undefined, undefined, 'Lax');
    });

    // 图片搜索表单
    const cacheSearchForm = this.cache.get<FormGroup>('cacheSearchForm');
    this.pictureSearchForm = cacheSearchForm || this.fb.group({
      user: ['', []],
      keywords: ['', []],
      origin: ['haina-int', [Validators.required]]
    });
    this.pictureSearchForm.get('keywords').valueChanges.pipe(
      debounceTime(500),
      switchMap((str: string) => {
        str = str.trim();
        if (str) {
          return this.http.post<string[]>(this.tagsApi, { str });
        } else {
          return of([]);
        }
      })
    ).subscribe(res => {
      // console.log(res);
      this.tags = res;
    });

  }

  openPicture(url: string) {
    url = url.replace('-m.jpg', '.jpg');
    this.nzImageService.preview([{ src: url }]);
  }

  errorPicture(ev: Event, url: string) {
    const errorImg = ev.target as HTMLImageElement;
    if (errorImg.src !== url) {
      errorImg.src = url;
    }
  }

  showPictureProp(picture: any, index: number): void {
    this.isVisible = true;
    this.picture = picture;
    this.picture.index = index;
    this.picturePropForm.setValue({
      site: this.picturePropForm.get('site').value,
      keywords: this.picture.keywords
    });
  }

  async handleOkPictureProp() {
    // this.isOkLoading = true;
    const cdn = await this.pictureManagerService.getPictureCdn();
    const uri: string = cdn[this.picturePropForm.get('site').value];
    if (cdn[this.picturePropForm.get('site').value]) {
      const url = uri + this.picture.name;
      clipboard.writeText(url);
      this.message.success(`图片url已复制到剪贴板:${url}`);
      // this.isOkLoading = false;
      // this.isVisible = false; 不关闭属性框
    }
    // likes+1
    this.pictureManagerService.likes(this.picture.id).subscribe(res => {
      this.picture.likes = res.likes;
    });
  }

  handleCancelPictureProp(): void {
    this.isVisible = false;
  }

  handleTags(tag: string) {
    this.picturePropForm.patchValue({ keywords: tag });
  }

  updateKeywords() {
    for (const key in this.picturePropForm.controls) {
      if (this.picturePropForm.controls.hasOwnProperty(key)) {
        this.picturePropForm.controls[key].markAsDirty();
        this.picturePropForm.controls[key].updateValueAndValidity();
      }
    }
    if (this.picturePropForm.valid) {
      this.isUpdateLoading = true;
      this.http.post(this.updateKeywordsApi, {
        id: this.picture.id,
        keywords: this.picturePropForm.get('keywords').value
      }).subscribe(() => {
        // 更新当前数组中的图片
        this.message.success('图片关键词更新成功。');
        this.picture.keywords = this.picturePropForm.get('keywords').value;
        this.isUpdateLoading = false;
      });
    }
  }

  searchPicture() {
    const str = this.pictureSearchForm.get('keywords').value;
    const user = this.pictureSearchForm.get('user').value;
    const origin = this.pictureSearchForm.get('origin').value;
    this.page = 1;
    this.pics = [];
    this.loading = true;
    this.pictureManagerService.search({
      origin,
      limit: this.limit,
      page: this.page,
      keyword: str,
      user
    }).then((res: Picture[]) => {
      this.page += 1;
      this.pics = res || [];
      this.loading = false;
      // console.log(this.pics);
    }).catch(() => {
      this.loading = false;
    });
  }

  onScroll() {
    const str = this.pictureSearchForm.get('keywords').value;
    const user = this.pictureSearchForm.get('user').value;
    const origin = this.pictureSearchForm.get('origin').value;
    this.loading = true;
    this.pictureManagerService.search({
      origin,
      limit: this.limit,
      page: this.page,
      keyword: str,
      user
    }).then((res: Picture[]) => {
      if (res.length) {
        this.pics = this.pics.concat(res);
        this.page += 1;
      }
      this.loading = false;
    });
  }

  compareTag(c1: string, c2: string): boolean {
    return c2.indexOf(c1) !== -1;
  }

  async toEditImage(picture: Picture) {
    if (picture.outside === 'shetu') {
      let id = this.message.loading('正在获取摄图网图片授权......', { nzDuration: 30000 }).messageId;
      picture.picture.path = await this.pictureManagerService.downloadShetu(picture);
      this.message.remove(id);
      // 可能需要重新登陆。
      if (!picture.picture.path) {
        id = this.message.loading('正在重新下载证书......', { nzDuration: 30000 }).messageId;
        picture.picture.path = await this.pictureManagerService.downloadShetu(picture);
        this.message.remove(id);
      }
    }
    if (!picture.picture.path) {
      alert('摄图网授权失败,请联系LMR');
      return false;
    }
    this.cache.set('outsidePicture', picture);
    this.router.navigate(['/picture-manager/picture-upload']);
    return true;
  }

  showIntro() {
    this.shareService.showIntro().start();
  }

  ngOnInit(): void {

    // 获取所有用户和标签
    this.userLoading = true;
    this.http.post(this.usersApi, null).subscribe((data: any) => {
      this.users = data;
      this.userLoading = false;
      // this.pictureSearchForm.patchValue({ user: this.authService.user.name || '' }); 默认所有人图片
    });

    // 初始化时获取本地图片
    const cachePics = this.cache.get<Picture[]>('cachePictures');
    const cachePage = this.cache.get<number>('cachePage');
    const origin = this.pictureSearchForm.get('origin').value;
    this.loading = true;
    if (!cachePics) {
      this.pictureManagerService.search({
        origin,
        limit: this.limit,
        page: this.page,
        keyword: '',
        user: ''
      }).then((res: Picture[]) => {
        this.pics = res;
        this.page += 1;
        this.loading = false;
      });
    } else {
      setTimeout(() => {
        this.pics = cachePics;
        this.page = cachePage;
        this.loading = false;
      }, 300);
    }

    // 页面引导
    if (!this.cookie.get('intro-picture-storehouse')) {
      setTimeout(() => {
        this.showIntro();
        this.cookie.set('intro-picture-storehouse', 'yes', new Date('2088-12-12'), undefined, undefined, undefined, 'Lax');
      }, 1500);
    }

  }

  ngOnDestroy() {
    this.cache.set('cacheSearchForm', this.pictureSearchForm);
    this.cache.set('cachePictures', this.pics);
    this.cache.set('cachePage', this.page);
  }

}
<nz-space class="sop">
  <a *nzSpaceItem href="https://haina.yuque.com/docs/share/d12df0ad-3ed7-422d-984f-75d6702aca0c?# 《图片仓库》"
    target="_blank" nz-button nzType="primary" nzSize="small" data-title="图片管理 Tips" data-intro="一些提升效率的小Tips。"
    data-step="99">
    <i nz-icon nzType="yuque" nzTheme="outline"></i>Tips
  </a>
  <button *nzSpaceItem nz-button nzType="primary" (click)="showIntro()" nzSize="small" data-title="图片管理 SOP"
    data-intro="最后:忘了怎么用?点这里再看一次。" data-step="100">
    <i nz-icon nzType="bulb" nzTheme="outline"></i>SOP
  </button>
</nz-space>
<!-- search框 -->
<form [formGroup]="pictureSearchForm" nz-form [nzLayout]="'inline'" class="search-picture" (ngSubmit)="searchPicture()">
  <nz-form-item data-title="图片管理SOP" data-intro="第1步:选择用户,通常使用所有用户即可。" data-step="1">
    <nz-form-label>用户</nz-form-label>
    <nz-form-control>
      <nz-select formControlName="user" [nzLoading]="userLoading" class="select-user">
        <nz-option nzValue="" nzLabel="所有人"></nz-option>
        <nz-option [nzValue]="user" [nzLabel]="user" *ngFor="let user of users"></nz-option>
      </nz-select>
    </nz-form-control>
  </nz-form-item>
  <nz-form-item>
    <nz-form-label>关键词</nz-form-label>
    <nz-form-control>
      <nz-input-group nzCompact style="width: 500px;">
        <input nz-input placeholder="例如:熊猫..." formControlName="keywords" [nzAutocomplete]="auto" style="width: 60%;"
          data-title="图片管理SOP" data-intro="第2步:输入关键词,注意不同图源,支持关键词的语种不一样。" data-step="2" />
        <nz-autocomplete [nzDataSource]="tags" [compareWith]="compareTag" nzBackfill #auto></nz-autocomplete>
        <nz-select formControlName="origin" style="width: 40%;" data-title="图片管理SOP"
          data-intro="第3步:选择图源,推荐:国内图片选国际事业部和摄图网,国外图片选Pixabay。" data-step="3">
          <nz-option nzValue="haina-int" nzLabel="图源:国际事业部,推荐"></nz-option>
          <nz-option nzValue="haina-video" nzLabel="图源:多媒体中心,推荐"></nz-option>
          <nz-option nzValue="shetu" nzLabel="图源:摄图网,推荐"></nz-option>
          <nz-option nzValue="pixabay" nzLabel="图源:Pixabay,推荐"></nz-option>
          <nz-option nzValue="unsplash" nzLabel="图源:Unsplash [英文]"></nz-option>
          <nz-option nzValue="pexels" nzLabel="图源:Pexels [英文]"></nz-option>
        </nz-select>
      </nz-input-group>
    </nz-form-control>
  </nz-form-item>
  <nz-form-item data-title="图片管理SOP" data-intro="第4步:点击搜索按钮,下方搜索结果,点击图片预览、点击右下角可以编辑图片属性或再次裁剪。" data-step="4">
    <nz-form-control>
      <button nz-button nzType="primary">搜索</button>
    </nz-form-control>
  </nz-form-item>
  <span nz-tooltip nzTooltipTitle="图源:国际、多媒体、摄图、Pixabay支持中英文,其他仅支持英文。" class="img-tips">
    <i nz-icon nzType="question-circle" nzTheme="twotone"></i>
  </span>
</form>
<nz-divider nzDashed [nzText]="text">
  <ng-template #text><i nz-icon nzType="picture" nzTheme="outline"></i></ng-template>
</nz-divider>
<!-- 图片列表 -->
<section class="card-section" infiniteScroll [infiniteScrollDistance]="2" [infiniteScrollThrottle]="50"
  (scrolled)="onScroll()">
  <div *ngFor="let item of pics;index as i" class="card-item lazyload"
    [ngStyle]="{'width':item.picture.width*250/item.picture.height+'px', 'flex-grow':item.picture.width*250/item.picture.height}"
    data-expand="-20">
    <i [ngStyle]="{'padding-bottom': item.picture.height/item.picture.width*100 + '%'}" class="zhanwei"></i>
    <img loading="lazy" class="card-img lazyload" [attr.data-src]="item.url" (click)="openPicture(item.picture.path)"
      alt="图片加载失败,如果是多媒体中心,那么只能使用办公室有效;如果不是,则请联系LMR。" (error)="errorPicture($event, item.picture.path)" />
    <div class="card-label">
      <p class="card-label-wh" nz-typography nzEllipsis
        [nzContent]="item.picture.width + ' x ' + item.picture.height + ' 🤣 ' + item.picture.user + ' 🐷 ' + item.picture.keywords.replace(re, ' ')">
      </p>
      <span *ngIf="!item.outside" nz-tooltip nzTooltipTitle="再次编辑">
        <i nz-icon [nzType]="'edit'" [nzTheme]="'fill'" class="card-img-edit edit-mr" (click)="toEditImage(item)"></i>
      </span>
      <span *ngIf="!item.outside" nz-tooltip nzTooltipTitle="查看或获取图片">
        <i nz-icon [nzType]="'instagram'" [nzTheme]="'fill'" class="card-img-edit"
          (click)="showPictureProp(item.picture, i)"></i>
      </span>
      <span *ngIf="item.outside" nz-tooltip nzTooltipTitle="编辑图片">
        <i nz-icon [nzType]="'edit'" [nzTheme]="'fill'" class="card-img-edit" (click)="toEditImage(item)"></i>
      </span>
    </div>
    <div class="card-label-top" *ngIf="(pictureSearchForm.get('origin').value === 'haina-int')"
      (click)="showPictureProp(item.picture, i)">
      <i nz-icon [nzType]="'heart'" [nzTheme]="'twotone'" [nzTwotoneColor]="'#eb2f96'"></i>
      +{{item.picture.likes ? item.picture.likes+1 : 1}}
    </div>
  </div>
</section>
<div *ngIf="loading" class="images-spin">
  <nz-spin nzSimple></nz-spin>
</div>
<nz-empty *ngIf="!loading && !pics.length" nzNotFoundContent="无相关图片" class="empty-pics"></nz-empty>
<!-- 弹出层 -->
<nz-modal [(nzVisible)]="isVisible" [nzTitle]="'图片:'+ picture?.name" (nzOnCancel)="handleCancelPictureProp()">
  <ng-template nzModalContent>
    <form nz-form [formGroup]="picturePropForm">
      <nz-form-item>
        <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="site">图片站点</nz-form-label>
        <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="图片站点是必填项!" nzExtra="选择在哪个站点上面显示。">
          <nz-select formControlName="site" id="site">
            <nz-option nzLabel="日语站" nzValue="jp"></nz-option>
            <nz-option nzLabel="西语站" nzValue="vac"></nz-option>
            <nz-option nzLabel="法语站" nzValue="vc"></nz-option>
            <nz-option nzLabel="德语站" nzValue="gm"></nz-option>
            <nz-option nzLabel="俄语站" nzValue="ru"></nz-option>
            <nz-option nzLabel="意语站" nzValue="it"></nz-option>
          </nz-select>
        </nz-form-control>
      </nz-form-item>
      <nz-form-item>
        <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="keywords">关键字</nz-form-label>
        <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="关键字是必填项,推荐中文关键词,方便日后搜索图片。" nzExtra="请输入依次输入关键词,推荐中文关键词。">
          <textarea formControlName="keywords" nz-input rows="3"
            placeholder="推荐中文关键词,使用 “#” 隔开。&#13;&#10;例如:西藏#桂林#雷峰塔#白娘子。" hidden></textarea>
          <app-picture-tag-input [tagString]="picture.keywords" (changeEvent)="handleTags($event)">
          </app-picture-tag-input>
        </nz-form-control>
      </nz-form-item>
    </form>
  </ng-template>
  <div *nzModalFooter>
    <button nz-button nzType="default" (click)="updateKeywords()" [nzLoading]="isUpdateLoading"
      class="fleft">更新关键词</button>
    <button nz-button nzType="default" (click)="handleCancelPictureProp()">关闭</button>
    <button nz-button nzType="primary" (click)="handleOkPictureProp()" [nzLoading]="isOkLoading">获取地址</button>
  </div>
</nz-modal>

./picture-storehouse.component.less

.card-section {
  display: flex;
  flex-wrap: wrap;
}

.card-section::after {
  content: '';
  flex-grow: 999999999;
}

.card-item {
  margin: 8px;
  position: relative;
  border-radius: 2px;
  overflow: hidden;
  box-shadow: 1px 1px 3px #888;

  i {
    display: block;
    transition: all 0.5s;
  }

  i:hover {
    color: aqua;
  }
}

.card-img {
  position: absolute;
  top: 0;
  width: 100%;
  vertical-align: bottom;
  vertical-align: bottom;
  cursor: pointer;
  transform: translateZ(5px);
  transition: all 0.3s;
  background-color: #fff;
}

.zhanwei {
  background: url(https://data.arachina.com/kcfinder/upload/jp/pic/ld4.gif) no-repeat center center;
  background-size: cover;
}

/*
.card-item:hover .card-img {
  transform: scale(1.1);
}
*/

.card-label,
.card-label-top {
  color: #fff;
  position: absolute;
  bottom: -36px;
  height: 36px;
  line-height: 36px;
  padding: 0 12px;
  background: rgba(0, 0, 0, 0.3);
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  transition: all 0.3s;
}

.card-label-top {
  bottom: auto;
  right: 0;
  top: 0;
  opacity: 0;
  width: auto;
  cursor: pointer;
  background: none;
  text-shadow: 1px 1px #ad1818;
}

.card-item:hover .card-label {
  bottom: 0;
}

.card-item:hover .card-label-top {
  opacity: 1;
}

.card-label-wh {
  flex-grow: 2;
  margin: 0;
  color: #fff;
}

.card-img-edit {
  cursor: pointer;
  font-size: 20px;
}

.edit-mr {
  margin-right: 12px;
}

.search-picture {
  padding: 0 6px;
}

.fleft {
  float: left;
}

.empty-pics {
  margin-top: 100px;
}

.images-spin {
  text-align: center;
  background: #f0f2f5;
  border-radius: 4px;
  margin-bottom: 20px;
  padding: 50px 50px;
  margin: 20px 0;
}

.img-tips {
  line-height: 36px;
  position: relative;
  right: 5px;
  width: 16px;
  display: block;

  [nz-icon] {
    font-size: 18px;
  }
}

.sop {
  position: absolute;
  right: 50px;
  top: 80px;
}

.select-user {
  min-width: 88px;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""