前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Angular日期组件开发

Angular日期组件开发

原创
作者头像
4cos90
修改2021-03-08 10:01:15
1.5K0
修改2021-03-08 10:01:15
举报
文章被收录于专栏:随想

完成的效果:

点击时间框弹出时间选择窗口,通过滚轮选择时间,点击确定更新为选择的时间,点击取消恢复选择前的时间。
点击时间框弹出时间选择窗口,通过滚轮选择时间,点击确定更新为选择的时间,点击取消恢复选择前的时间。

时间组件的目录结构:

目录结构
目录结构

这个组件可以拆分成两部分,第一个部分是时间框和时间选择窗口,包括确定,取消按钮,即目录中的dateTime组件。第二个部分是通过滚轮选择日期,即目录中的ScrollDateTime组件。目录中还包含一个自定义管道,考虑到滚轮选择日期时,只有中间的数字是有用的,因此上下两个数字可用管道生成,不需要用额外的变量去控制。

ScrollDateTime组件:

先放下代码:

代码语言:javascript
复制
<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>
代码语言:javascript
复制
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())); 
    }
}
代码语言:javascript
复制
.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管道:

代码语言:javascript
复制
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组件:

代码语言:javascript
复制
<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>
代码语言:javascript
复制
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;
    }
}
代码语言:javascript
复制
.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>();

当点击确定时,会通过这个事件输出选择的时间。

注意

代码语言:javascript
复制
    @ViewChild('Scroll', { static: false })
    Scroll: ScrollDateTimeComponent;
    
    OK() {
        this.TimeChange.emit(this.Scroll.GetDateTime());
        this.SearchSign = false;
    }

通过这种方式在确定时获取ScrollDateTime组件选中的时间。

代码语言:javascript
复制
<app-scrollDateTime #Scroll [time]="Time"></app-scrollDateTime>

引用scrollDateTime组件。

在其他页面使用日期组件:

代码语言:javascript
复制
<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>
代码语言:javascript
复制
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两种输入。

在appmodule中引入刚才完成的两个组件与自定义管道
在appmodule中引入刚才完成的两个组件与自定义管道

这样一个简单的日期组件就封装完成了!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档