- 参考NG-ZORRO源码
目录
- breadcrumb 面包屑
- page 分页
- tag 标签组件
- rate 评分组件
注意 ⚠️
ZORRO的CSS样式直接无法修改,若要修改,需在前面加入:host ::ng-deep
.ant-input-affix-wrapper .ant-input:not(:first-child){
padding-left: 30px;
}
//1 2 3 修改上面就正常了
:host ::ng-deep .ant-input-affix-wrapper .ant-input:not(:first-child){
padding-left: 30px;
}
1. 面包屑
breadcrumb.component
| string = '>';
constructor() { }
ngOnInit(): void {}
}
//=============================================
import {ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef, ViewEncapsulation} from '@angular/core';
@Component({
selector: 'xm-breadcrumb',
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class BreadcrumbComponent implements OnInit {
//父组件把数据传递给子组件,孙子组件获取子组件数据,只传一次
//因为若直接将数据父传给孙,使用一次孙子组件需要传递一次数据
@Input() xmSeparator: TemplateRef
breadcrumb-item.component.ts
加入该子组件的原因是:
解决每使用一次breadcrumb组件则需要传递一次参数的问题,把breadcrumb当成中间组件,用来传递参数
//===============================================================
import {Component, OnInit, ChangeDetectionStrategy, Input, TemplateRef, Optional} from '@angular/core';
import {BreadcrumbComponent} from '../breadcrumb.component';
@Component({
selector: 'xm-breadcrumb-item',
templateUrl: './breadcrumb-item.component.html',
styleUrls: ['./breadcrumb-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BreadcrumbItemComponent implements OnInit {
myContext = { $implicit: 'World', my: 'svet' }; //补充,可填入变量
constructor(@Optional() readonly parent: BreadcrumbComponent) { }
ngOnInit(): void {}
}
app.component
str-tpl-outlet.directive.ts
加入该指令的原因是:
传递的参数只能是字符串,加入指令判断后,则可传递字符串或者template模板
- 通过自定义指令判断传入的是template或者字符串,template直接使用,string则转化,然后创建视图容器并插入template
import {Directive, Input, OnChanges, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[xmStrTplOutlet]'
})
export class StrTplOutletDirective implements OnChanges {
@Input() xmStrTplOutlet: TemplateRef | string;
@Input() xmStrTplOutletContext: any;
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef) { }
ngOnChanges(changes: SimpleChanges): void {
const { xmStrTplOutlet } = changes;
if (xmStrTplOutlet) {
this.viewContainer.clear();
const template = (this.xmStrTplOutlet instanceof TemplateRef) ? this.xmStrTplOutlet : this.templateRef;
this.viewContainer.createEmbeddedView(template, this.xmStrTplOutletContext);
}
}
}
2. 分页组件
-
{{ item.num }}
//其余的
//使用+将string隐式转为number
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges} from '@angular/core';
import { clamp } from 'lodash';
type PageItemType = 'page' | 'prev' | 'next' | 'prev5' | 'next5';
interface PageItem {
type: PageItemType;
num?: number;
disabled?: boolean;
}
@Component({
selector: 'xm-pagination',
templateUrl: './pagination.component.html',
styleUrls: ['./pagination.component.scss']
})
export class PaginationComponent implements OnInit, OnChanges {
@Input() total = 0; //总数据条数
@Input() pageNum = 1; //当前显示页数
@Input() pageSize = 10; //每个网页显示数据条数
@Output() changed = new EventEmitter();
lastNum = 0; //总页数
//保存分页数据
//type: PageItemType;
//num?: number;
//disabled?: boolean;
listOfPageItems: PageItem[] = [];
constructor() { }
ngOnInit(): void {}
ngOnChanges(changes: SimpleChanges): void {
//向上取整,总页数,默认显示一页
this.lastNum = Math.ceil(this.total / this.pageSize) || 1;
this.listOfPageItems = this.getListOfPageItems(this.pageNum, this.lastNum);
// console.log('listOfPageItems', this.listOfPageItems);
}
private getListOfPageItems(pageNum: number, lastNum: number): PageItem[] {
if (lastNum <= 9) {
return concatWithPrevNext(generatePage(1, this.lastNum), pageNum, lastNum);
} else {
let listOfRange = [];
const prevFiveItem = {
type: 'prev5'
};
const nextFiveItem = {
type: 'next5'
};
const firstPageItem = generatePage(1, 1); //第一页
const lastPageItem = generatePage(lastNum, lastNum); //最后一页
//当前页小于4,显示(第1页 + 2-5页 + 三个点 + 最后一页)/1,2,3,4,5...10
if (pageNum < 4) {
listOfRange = [...generatePage(2, 5), nextFiveItem];
//当前页大于总页数减4,显示第一页+ 三个点 + (lastNum-4, lastNum-1)+最后一页/1...6,7,8,9,10
} else if (pageNum > lastNum - 4) {
listOfRange = [prevFiveItem, ...generatePage(lastNum - 4, lastNum - 1)];
} else {
//其它页显示,第一页+...+ (pageNum-2,pageNum+2)+...+最后一页 /1...5,6,7,8,9...12
listOfRange = [prevFiveItem, ...generatePage(pageNum - 2, pageNum + 2), nextFiveItem];
}
//拼接第一页,中间,和最后一页
return concatWithPrevNext([...firstPageItem, ...listOfRange, ...lastPageItem], pageNum, lastNum);
}
}
}
//生产type=page类型的数据
function generatePage(start: number, end: number): PageItem[] {
const list = [];
for (let i = start; i <= end; i++) {
list.push({
num: i,
type: 'page'
});
}
return list;
}
// 连接type不同的数据
function concatWithPrevNext(listOfPage:PageItem[],pageNum:number,lastNum:number): PageItem[] {
return [
{
type: 'prev',
disabled: pageNum === 1
},
...listOfPage,
{
type: 'next',
disabled: pageNum === lastNum
}
];
}
inputVal(num: number): void {
if (num > 0) {
this.pageClick({
type: 'page',
num
});
}
}
//点击按钮跳转到相应页
pageClick({ type, num, disabled }: PageItem): void {
if (!disabled) {
let newPageNum = this.pageNum;
if (type === 'page') {
newPageNum = num;
} else {
const diff: any = {
next: 1,
prev: -1,
prev5: -5,
next5: 5
};
newPageNum += diff[type];
}
// console.log('newPageNum', newPageNum);
this.changed.emit(clamp(newPageNum, 1, this.lastNum));
// clamp为lodash库的方法,这里用来限制页数变化范围
}
}
使用
//==============================================
changePage(newPageNum: number): void {
if (this.searchParams.page !== newPageNum) {
this.searchParams.page = newPageNum;
this.updateAlbums();
}
}
3. tag标签组件
import {
AfterViewInit,
Component,
ElementRef,
HostBinding,
Input,
OnChanges,
OnInit, Output,
Renderer2, SimpleChange,
SimpleChanges,
ViewEncapsulation,
EventEmitter
} from '@angular/core';
const ColorPresets = ['magenta', 'orange', 'green'];
type TagMode = 'default' | 'circle';
@Component({
selector: 'xm-tag',
templateUrl: './tag.component.html',
styleUrls: ['./tag.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TagComponent implements OnChanges {
@Input() xmColor = '';
@Input() xmShape: TagMode = 'default';
@Input() xmClosable = false;
@Output() closed = new EventEmitter();
@HostBinding('class.xm-tag-circle') get circleCls(): boolean { return this.xmShape === 'circle'; }
@HostBinding('class.xm-tag-close') get closeCls(): boolean { return this.xmClosable; }
@HostBinding('class.xm-tag') readonly hostCls = true;
private currentPresetCls = '';
constructor(private el: ElementRef, private rd2: Renderer2) { }
ngOnChanges(changes: SimpleChanges): void {
this.setStyle(changes.xmColor);
}
private setStyle(color: SimpleChange): void {
const hostEl = this.el.nativeElement;
if (!hostEl || !this.xmColor) { return; }
if (this.currentPresetCls) {
this.rd2.removeClass(hostEl, this.currentPresetCls);
this.currentPresetCls = '';
}
if (ColorPresets.includes(this.xmColor)) {
this.currentPresetCls = 'xm-tag-' + this.xmColor;
this.rd2.addClass(hostEl, this.currentPresetCls);
this.rd2.removeStyle(hostEl, 'color');
this.rd2.removeStyle(hostEl, 'border-color');
this.rd2.removeStyle(hostEl, 'background-color');
} else {
this.rd2.setStyle(hostEl, 'color', '#fff');
this.rd2.setStyle(hostEl, 'border-color', 'transparent');
this.rd2.setStyle(hostEl, 'background-color', color.currentValue);
}
}
}
rate评分组件
c
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
forwardRef,
Input,
OnInit,
Output, TemplateRef,
ViewEncapsulation
} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@Component({
selector: 'xm-rate',
templateUrl: './rate.component.html',
styleUrls: ['./rate.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RateComponent),
multi: true
}
]
})
export class RateComponent implements OnInit, ControlValueAccessor {
@Input() count = 5;
@Input() tpl: TemplateRef;
private readonly = false;
starArray: number[] = [];
private hoverValue = 0;
private actualValue = 0;
private hasHalf = false;
rateItemStyles: string[] = [];
@Output() changed = new EventEmitter();
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(): void {
this.updateStarArray();
}
rateHover(isHalf: boolean, index: number): void {
if (this.readonly || (this.hoverValue === index + 1 && isHalf === this.hasHalf)) {
return;
}
this.hoverValue = index + 1;
this.hasHalf = isHalf;
// console.log('hoverValue', this.hoverValue);
this.updateStarStyle();
}
rateClick(isHalf: boolean, index: number): void {
if (this.readonly) {
return;
}
this.hoverValue = index + 1;
this.hasHalf = isHalf;
this.setActualValue(isHalf ? index + 0.5 : this.hoverValue);
this.updateStarStyle();
}
private setActualValue(value: number): void {
if (this.actualValue !== value) {
this.actualValue = value;
this.onChange(value);
this.changed.emit(value);
}
}
rateLeave(): void {
this.hasHalf = !Number.isInteger(this.actualValue);
this.hoverValue = Math.ceil(this.actualValue);
this.updateStarStyle();
}
private updateStarArray(): void {
this.starArray = Array(this.count).fill(0).map((item, index) => index);
// console.log('starArray', this.starArray);
}
private updateStarStyle(): void {
this.rateItemStyles = this.starArray.map(index => {
const base = 'xm-rate-item';
const value = index + 1;
let cls = '';
if (value < this.hoverValue || (!this.hasHalf && value === this.hoverValue)) {
cls += base + '-full';
} else if (this.hasHalf && value === this.hoverValue) {
cls += base + '-half';
}
const midCls = this.readonly ? ' xm-rate-item-readonly ' : ' ';
return base + midCls + cls;
});
}
onChange: (value: number) => void = () => {};
onTouched: () => void = () => {};
writeValue(value: number): void {
// console.log('writeValue', value);
if (value) {
this.actualValue = value;
this.rateLeave();
this.cdr.markForCheck();
}
}
registerOnChange(fn: (value: number) => void): void {
this.onChange = fn;
}
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
this.readonly = isDisabled;
}
}
c
c
import {Component, OnInit, ChangeDetectionStrategy, Output, EventEmitter, Input, TemplateRef} from '@angular/core';
@Component({
selector: 'xm-rate-item',
templateUrl: './rate-item.component.html',
styleUrls: ['./rate-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RateItemComponent implements OnInit {
@Input() tpl: TemplateRef;
@Input() rateItemCls = 'xm-rate-item';
@Output() private itemHover = new EventEmitter();
@Output() private itemClick = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
hoverRate(isHalf: boolean): void {
this.itemHover.emit(isHalf);
}
clickRate(isHalf: boolean): void {
this.itemClick.emit(isHalf);
}
}