src/app/layout/picture-manager/picture-storehouse/picture-storehouse.component.ts
selector | app-picture-storehouse |
styleUrls | ./picture-storehouse.component.less |
templateUrl | ./picture-storehouse.component.html |
Properties |
Methods |
constructor(http: HttpClient, nzImageService: NzImageService, fb: FormBuilder, cookie: CookieService, pictureManagerService: PictureManagerService, message: NzMessageService, router: Router, cache: CacheService, shareService: ShareService)
|
||||||||||||||||||||||||||||||
Parameters :
|
compareTag |
compareTag(c1: string, c2: string)
|
Returns :
boolean
|
errorPicture |
errorPicture(ev: Event, url: string)
|
Returns :
void
|
handleCancelPictureProp |
handleCancelPictureProp()
|
Returns :
void
|
Async handleOkPictureProp |
handleOkPictureProp()
|
Returns :
any
|
handleTags | ||||||
handleTags(tag: string)
|
||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onScroll |
onScroll()
|
Returns :
void
|
openPicture | ||||||
openPicture(url: string)
|
||||||
Parameters :
Returns :
void
|
searchPicture |
searchPicture()
|
Returns :
void
|
showIntro |
showIntro()
|
Returns :
void
|
showPictureProp |
showPictureProp(picture: any, index: number)
|
Returns :
void
|
Async toEditImage | ||||||
toEditImage(picture: Picture)
|
||||||
Parameters :
Returns :
{}
|
updateKeywords |
updateKeywords()
|
Returns :
void
|
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="推荐中文关键词,使用 “#” 隔开。 例如:西藏#桂林#雷峰塔#白娘子。" 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;
}