完成的效果:
时间组件的目录结构:
这个组件可以拆分成两部分,第一个部分是时间框和时间选择窗口,包括确定,取消按钮,即目录中的dateTime组件。第二个部分是通过滚轮选择日期,即目录中的ScrollDateTime组件。目录中还包含一个自定义管道,考虑到滚轮选择日期时,只有中间的数字是有用的,因此上下两个数字可用管道生成,不需要用额外的变量去控制。
ScrollDateTime组件:
先放下代码:
<div class="DateArea">
<div class="YearArea" (mousewheel)="YearWheel($event)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Year - 1}}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Year}}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Year + 1}}</span>
</div>
</div>
<div class="MonthArea" (mousewheel)="OtherWheel($event,'Month',1,12)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Month | dateFill : {'Dir':false,'min':1,'max':12} }}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Month | dateFill }}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Month | dateFill : {'Dir':true,'min':1,'max':12} }}</span>
</div>
</div>
<div class="DayArea" (mousewheel)="OtherWheel($event,'Day',1,timeInfo.DayMax)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Day | dateFill : {'Dir':false,'min':1,'max':timeInfo.DayMax} }}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Day | dateFill }}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Day | dateFill : {'Dir':true,'min':1,'max':timeInfo.DayMax} }}</span>
</div>
</div>
<div class="HourArea" (mousewheel)="OtherWheel($event,'Hour',0,23)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Hour | dateFill : {'Dir':false,'min':0,'max':23} }}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Hour | dateFill }}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Hour | dateFill : {'Dir':true,'min':0,'max':23} }}</span>
</div>
</div>
<div class="MinArea" (mousewheel)="OtherWheel($event,'Min',0,59)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Min | dateFill : {'Dir':false,'min':0,'max':59} }}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Min | dateFill }}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Min | dateFill : {'Dir':true,'min':0,'max':59} }}</span>
</div>
</div>
<div class="SecArea" (mousewheel)="OtherWheel($event,'Sec',0,59)">
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Sec | dateFill : {'Dir':false,'min':0,'max':59} }}</span>
</div>
<div class="SpanBase">
<span class="SelectSpan SelectSpan">{{timeInfo.Sec | dateFill }}</span>
</div>
<div class="SpanBase">
<span class="UnSelectSpan UnSelectSpan">{{timeInfo.Sec | dateFill : {'Dir':true,'min':0,'max':59} }}</span>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
export class TimeInfoDTO {
Year: number;
Month: number;
Day: number;
Hour: number;
Min: number;
Sec: number;
DayMax: number;
}
@Component({
selector: 'app-scrollDateTime',
templateUrl: './scrollDateTime.component.html',
styleUrls: ['./scrollDateTime.component.css']
})
export class ScrollDateTimeComponent implements OnInit {
@Input() time: string;
timeInfo: TimeInfoDTO;
solarMonth: Number[] = [1, 3, 5, 7, 8, 10, 12];
lunarMonth: Number[] = [4, 6, 9, 11];
constructor(
) { }
ngOnInit() {
let datetimearray = this.time.split(' ');
let datearray = datetimearray[0].split('-');
let timearray = datetimearray[1].split(':');
this.timeInfo = {
Year: Number(datearray[0]),
Month: Number(datearray[1]),
Day: Number(datearray[2]),
Hour: Number(timearray[0]),
Min: Number(timearray[1]),
Sec: Number(timearray[2]),
DayMax: 30
}
this.RefreshDayMax();
}
RefreshDayMax() {
this.timeInfo.DayMax = this.CheckDayMax(this.timeInfo);
this.timeInfo.Day = this.timeInfo.Day < this.timeInfo.DayMax ? this.timeInfo.Day : this.timeInfo.DayMax;
}
YearWheel(event) {
this.timeInfo.Year -= event.wheelDelta / 120;
this.RefreshDayMax();
}
OtherWheel(event, type: string, Min: number, Max: number) {
let ChangeValue = this.timeInfo[type] - event.wheelDelta / 120;
if (ChangeValue > Max) {
ChangeValue = ChangeValue - Max + Min - 1;
}
else if (ChangeValue < Min) {
ChangeValue = ChangeValue + Max - Min + 1;
}
this.timeInfo[type] = ChangeValue;
this.RefreshDayMax();
}
CheckDayMax(timeInfo: TimeInfoDTO) {
if (this.solarMonth.includes(timeInfo.Month)) {
return 31;
}
else if (this.lunarMonth.includes(timeInfo.Month)) {
return 30;
}
else {
if (this.CheckYear(timeInfo.Year)) {
return 29;
} else {
return 28;
}
}
}
// 判断闰年平年
CheckYear(year: number) {
if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
return true;
} else {
return false;
}
}
GetDateTime() {
return new Date(this.timeInfo.Year.toString() + "-" +
(this.timeInfo.Month < 10 ? "0" + this.timeInfo.Month.toString() : this.timeInfo.Month.toString()) + "-" +
(this.timeInfo.Day < 10 ? "0" + this.timeInfo.Day.toString() : this.timeInfo.Day.toString()) + " " +
(this.timeInfo.Hour < 10 ? "0" + this.timeInfo.Hour.toString() : this.timeInfo.Hour.toString()) + ":" +
(this.timeInfo.Min < 10 ? "0" + this.timeInfo.Min.toString() : this.timeInfo.Min.toString()) + ":" +
(this.timeInfo.Sec < 10 ? "0" + this.timeInfo.Sec.toString() : this.timeInfo.Sec.toString()));
}
}
.DateArea {
width: 100%;
height: 100%;
}
.YearArea {
float: left;
height: 100%;
width: 58px;
}
.MonthArea {
float: left;
margin-left: 12px;
height: 100%;
width: 30px;
}
.DayArea {
float: left;
margin-left: 12px;
height: 100%;
width: 30px;
}
.HourArea {
float: left;
margin-left: 44px;
height: 100%;
width: 30px;
}
.MinArea {
float: left;
margin-left: 12px;
height: 100%;
width: 30px;
}
.SecArea {
float: left;
margin-left: 12px;
height: 100%;
width: 30px;
}
.SpanBase {
float: left;
height: 40px;
margin-top: 10px;
width: 100%;
text-align: center;
}
.UnSelectSpan {
font-size: 22px;
font-family: PingFang SC Regular;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: block;
user-select: none;
}
.UnSelectSpan_Light {
color: rgba(17, 63, 110, 0.95);
}
.SelectSpan {
font-size: 24px;
font-family: PingFang SC Regular;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: block;
user-select: none;
text-decoration: underline
}
.SelectSpan_Light {
color: rgba(30, 117, 182, 1);
}
先看ts中,将传入的时间切分为 年 月 日 时 分 秒,需要注意DayMax这个属性,代表的是选定 年 月 属性之后,该月份一共有几天。每当滚轮转动日期时,通过RefreshDayMax函数来更新DayMax属性,同时也要对已经选中的Day进行判断(比如先选中的时间是3月31号,滚动月份变成4月时,应该自动把日变为30号,而不是31号)。同时也要注意如果滚轮超过最大日期,或者小于1时,要能从最小值滚动到最大值。这一点在OtherWheel中处理。最后提供了一个获取选定时间的函数GetDateTime,当在dateTime组件中点击确定时,通过GetDateTime来获取滚轮选择的时间。
html中就非常简单了,OtherWheel参数中提供该滚轮的最大最小值和滚轮代表的含义(月 日 时 分 秒)和一个dateFill 管道。
datefill管道:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'dateFill' })
export class DateFill implements PipeTransform {
transform(value: number, param?: any): string {
let ChangeValue = value;
if (param) {
if (param.Dir) {
if (value + 1 <= param.max) {
ChangeValue = value + 1;
} else {
ChangeValue = param.min;
}
} else {
if (value - 1 >= param.min) {
ChangeValue = value - 1;
} else {
ChangeValue = param.max;
}
}
}
return this.Formatvalue(ChangeValue);
}
Formatvalue(value: number) {
if (value < 10) {
return "0" + value.toString();
}
else {
return value.toString();
}
}
}
管道主要提供两个功能:
一个是补齐日期位数,比如选择3月1号,则补齐为03 01。
一个是生成选择日期上方数字及下方数字(通过参数Dir控制),当选中的数字是最大值时,下方的数字应生成为最小值,同样当选中的数字为最小值时,上方的数字应生成为最大值。形成滚轮的视觉效果。
dateTime组件:
<div style="position:relative;float: left;">
<div class="dateTime dateTime_{{SearchSign}}" onClick="event.cancelBubble = true" (click)="ClickTime()">
<div class="dateTimeText">
<input [(ngModel)]="Time" [title]="Time" placeholder="{{Placeholder}}" class="dateTimeInput dateTimeInput_{{SearchSign}}" readonly="readonly">
</div>
<div class="dateTimeImg dateTimeImg_{{SearchSign}}"></div>
</div>
<div *ngIf="SearchSign" class="dateTimePop startLoc dateTimePop" onClick="event.cancelBubble = true">
<div class="SearchTriangle SearchTriangle">
<div class="Triangleout Triangleout"></div>
<div class="Trianglein Trianglein"></div>
<div class="ScrollDateArea">
<app-scrollDateTime #Scroll [time]="Time"></app-scrollDateTime>
</div>
<div class="BtnArea">
<div class="PopBtnBase">
<div class="CancelButton" (click)="Cancel()">
<span class="CancelSpan">取消</span>
</div>
<div class="OKButton" (click)="OK()">
<span class="OKSpan">确定</span>
</div>
</div>
</div>
</div>
</div>
</div>
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { ScrollDateTimeComponent } from './ScrollDateTime/ScrollDateTime.component';
@Component({
selector: 'app-dateTime',
templateUrl: './dateTime.component.html',
styleUrls: ['./dateTime.component.css']
})
export class DateTimeComponent implements OnInit {
@Input() DateTime: Date;
@Input() Placeholder: string;
@Output() TimeChange = new EventEmitter<Date>();
Time:string;
@ViewChild('Scroll', { static: false })
Scroll: ScrollDateTimeComponent;
SearchSign: boolean = false;
constructor(
) { }
ngOnInit() {
if(this.DateTime){
this.Time = this.DateFormat(this.DateTime,"yyyy-MM-dd hh:mm:ss");
}
}
ClickTime() {
if(!this.SearchSign){
if(!this.Time){
this.Time = this.DateFormat(new Date(), "yyyy-MM-dd hh:mm:ss");
}
this.SearchSign = true;
}else{
this.SearchSign = false;
}
}
OnBlur() {
this.SearchSign = false;
}
Cancel() {
this.SearchSign = false;
}
OK() {
this.TimeChange.emit(this.Scroll.GetDateTime());
this.SearchSign = false;
}
DateFormat(date: Date, fmt: string) {
const o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
'S': date.getMilliseconds() // 毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
}
}
return fmt;
}
}
.dateTime {
float: left;
margin-top: 5px;
width: 250px;
height: 36px;
overflow: hidden;
border-radius: 5px;
cursor: pointer;
}
.dateTime_false {
background: rgba(234, 241, 249, 1);
border: 1px solid rgba(155, 184, 210, 0.6);
}
.dateTime_false:hover {
background: rgba(234, 241, 249, 1);
border: 1px solid rgba(74, 144, 226, 1);
}
.dateTime_true {
background: rgba(234, 241, 249, 1);
border: 1px solid rgba(74, 144, 226, 1);
}
.dateTimeText {
float: left;
height: 100%;
width: 210px;
margin-left: 5px;
margin-top: 2px;
}
.dateTimeImg {
float: right;
height: 100%;
width: 18px;
margin-right: 8px;
}
.dateTimeImg_false {
background: url('./Resource/window-arrow-up.png') no-repeat center center;
}
.dateTime:hover .dateTimeImg_false {
background: url('./Resource/window-arrow-up-hover.png') no-repeat center center;
}
.dateTimeImg_true {
background: url('./Resource/window-arrow-up-hover.png') no-repeat center center;
}
.dateTimeInput {
width: 100%;
height: 30px;
outline-style: none;
background: transparent;
border: 0px;
font-size: 20px;
font-family: PingFang SC Regular;
cursor: pointer;
user-select: none;
}
.dateTimeInput_false {
color: rgba(17, 63, 110, 0.95);
}
.dateTimeInput_false::placeholder {
color: rgba(17, 63, 110, 0.95);
}
.dateTime:hover .dateTimeInput_false {
color: rgba(17, 63, 110, 0.95);
}
.dateTime:hover .dateTimeInput_false::placeholder {
color: rgba(17, 63, 110, 0.95);
}
.dateTimeInput_true {
color: rgba(17, 63, 110, 0.95);
}
.dateTimeInput_true::placeholder {
color: rgba(17, 63, 110, 0.95);
}
.dateTimePop {
position: absolute;
width: 365px;
height: 256px;
}
.dateTimePop {
background: rgba(255, 255, 255, 1);
}
.timeSpan {
float: left;
margin-left: 4px;
margin-right: 4px;
width: 14px;
height: 24px;
}
.timeSpan {
border-bottom: 1px solid rgba(17, 63, 110, 0.95);
}
.startLoc {
left: -114px;
top: 54px;
}
.SearchTriangle {
position: relative;
width: 100%;
height: 100%;
}
.SearchTriangle {
box-shadow: 0 0 10px 0;
}
.Triangleout,
.Trianglein {
position: absolute;
width: 0;
height: 0px;
}
.Trianglein {
border: 8px solid transparent;
top: -16px;
left: 66.6%;
}
.Trianglein {
border-bottom-color: rgba(255, 255, 255, 1);
}
.Triangleout {
border: 10px solid transparent;
top: -20px;
left: 66%;
}
.Triangleout {
border-bottom-color: rgba(255, 255, 255, 1);
}
.ScrollDateArea {
float: left;
width: 300px;
height: 150px;
margin-left: 28px;
margin-top: 20px;
}
.BtnArea {
float: left;
width: 100%;
height: 80px;
}
.PopBtnBase {
margin-left: auto;
margin-right: auto;
margin-top: 30px;
height: 34px;
width: 285px;
}
.CancelButton {
float: left;
width: 130px;
height: 100%;
margin-right: 21px;
border-radius: 4px;
cursor: pointer;
text-align: center;
}
.CancelButton {
border: 1px solid rgba(88, 148, 230, 1);
}
.CancelSpan {
margin-top: 5px;
font-size: 16px;
font-family: PingFang SC Regular;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: block;
user-select: none;
color: rgba(74, 144, 226, 1);
}
.OKButton {
float: left;
width: 130px;
height: 100%;
border-radius: 5px;
cursor: pointer;
text-align: center;
background: linear-gradient(to right, rgba(88, 148, 230, 1), rgba(114, 172, 248, 1) 100%);
}
.OKSpan {
margin-top: 6px;
font-size: 16px;
font-family: PingFang SC Regular;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
display: block;
user-select: none;
color: rgba(255, 255, 255, 1);
}
先看ts中,包含两个输入:
DateTime-日期控件的初始时间,Placeholder-当没有输入初始时间时,时间框显示的Placeholder,
包含一个输出:
TimeChange = new EventEmitter<Date>();
当点击确定时,会通过这个事件输出选择的时间。
注意
@ViewChild('Scroll', { static: false })
Scroll: ScrollDateTimeComponent;
OK() {
this.TimeChange.emit(this.Scroll.GetDateTime());
this.SearchSign = false;
}
通过这种方式在确定时获取ScrollDateTime组件选中的时间。
<app-scrollDateTime #Scroll [time]="Time"></app-scrollDateTime>
引用scrollDateTime组件。
在其他页面使用日期组件:
<div style="width: 100%;height: 100%;">
<div style="width: 100%;height: 50px;margin-top:50px;background:gray;">
<div style="float: left; margin-left: 100px;">
<span class="Span">开始时间:</span>
<app-dateTime [DateTime]="StartTime" (TimeChange)="StartTimeChange($event)"></app-dateTime>
</div>
<div style="float:left;margin-left: 100px;">
<span class="Span">结束时间:</span>
<app-dateTime [DateTime]="EndTime" (TimeChange)="EndTimeChange($event)"></app-dateTime>
</div>
<div style="float:left;margin-left: 100px;">
<span class="Span">请选择时间:</span>
<app-dateTime [Placeholder]="'请选择时间'" (TimeChange)="TimeChange($event)"></app-dateTime>
</div>
</div>
<div style="width: 100%;height: 50px;">
<span class="Span">{{'开始时间:' + StartTime}}</span>
</div>
<div style="width: 100%;height: 50px;">
<span class="Span">{{'结束时间:' + EndTime}}</span>
</div>
<div style="width: 100%;height: 50px;">
<span class="Span">{{'选择的时间:' + (SelectTime || '')}}</span>
</div>
</div>
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.css']
})
export class IndexComponent implements OnInit {
StartTime: Date;
EndTime: Date;
SelectTime:Date;
ngOnInit() {
let Datetime = new Date();
this.EndTime = Datetime;
this.StartTime = new Date(Datetime.setDate(Datetime.getDate() - 1));
}
StartTimeChange(time: Date) {
this.StartTime = time;
}
EndTimeChange(time: Date) {
this.EndTime = time;
}
TimeChange(time: Date) {
this.SelectTime = time;
}
}
在index中分别使用了设置初始时间与设置PlaceHolder两种输入。
这样一个简单的日期组件就封装完成了!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。