vue实现日历组件

1、当前组件说明

一个用vue实现的简单日历组件,可以手动设置日历的宽高,未设置时,有默认宽高,设置后,其中的每个日期项的宽高可以根据设定的宽高自适应、可以设置当前选中日期,可以设置时间可用范围,可以通过左右箭头进行日期切换。(第一次使用简书,代码格式比较丑,而且换行很高,也不知道怎么调整,大家见谅)

2、组件效果展示

3、组件实现代码

<template>

  <div class="picker-box" :style="'width: ' + width + 'px; height: ' + height + 'px;'">

    <div class="picker-tool" :style="'height: ' + toolHeight + 'px; line-height: ' + toolHeight + 'px;'">

      <Button type="text" icon="ios-arrow-back" @click="toVdpUpper()"></Button>

      {{vdpYear + '年' + vdpMonth + '月'}}

      <Button type="text" icon="ios-arrow-forward" @click="toVdpLower()"></Button>

    </div>

    <div class="picker-body" :style="'height: ' + (height - 50) + 'px;'">

      <div class="picker-title" v-for="item in xqList" :key="item" :style="itemStyles">{{item}}</div>

      <div class="picker-item"

        :style="itemStyles"

        v-for="(item, i) in dateList" :key="i" @click="toSelect(item)"

        :class="(item.disabled === false && item.actived ? 'actived' :'grey') + (item.checked ? ' checked' : '') + (item.today ? ' today' : '') + (item.disabled ? ' disabled' : '')">

          {{item.day}}

       </div>

      <div style="clear: both;"></div>

    </div>

  </div>

</template>

<script>

export default {

  name: 'cDatePicker',

  props: {

    // 要求传入的值 必须为 yyyy-MM-dd 格式数据

    value: {

      type: String

    },

    // 日历显示的宽度,默认300px

    width: {

      type: Number,

      default: 300

    },

     // 日历显示的高度,默认300px

    height: {

      type: Number,

      default: 300

    },

     // 日历工具栏的显示高度,默认32px

     toolHeight: {

      type: Number,

      default: 32

    },

    // 日历中每个时间项的行高

    lineHeight: {

      type: Number

    },

    margin: {

      type: Number,

      default: 2

    },

    // 日历可选范围-开始时间

    start: {

      type: String,

      default: ''

    },

    // 日历可选范围-结束时间

    end: {

      type: String,

      default: ''

    }

  },

  watch: {

    // 监听 外部插入的value值变化

    // value (n, l) {

    //   this.init()

    // }

  },

  data () {

    return {

      xqList: ['日', '一', '二', '三', '四', '五', '六'],

      vdpYear: 0, // 选择的年

      vdpMonth: 0, // 选择的月

      vdpDay: 0, // 选择的日

      monthStart: 0, // 月初

      monthEnd: 0, // 月末

      monthWeek: 0,

      dateList: [] // 当前时间集合

    }

  },

  methods: {

    // 初始化

    init () {

      const now = this.newDate(this.value || undefined)

      this.vdpYear = now.getFullYear()

      this.vdpMonth = now.getMonth() + 1

      this.vdpDay = this.value ? now.getDate() : 0

      this.computePicker()

    },

    // 选中某一天

    toSelect (item) {

      if (item && item.disabled === false) {

        // // 通过重新生成dateList数据 达到改变选中效果

        // this.vdpDay = item.day

        // this.computePicker()

        // 通过 直接循环修改dateList数据 达到改变选中效果

        this.dateList = this.dateList.map(v => {

          v.checked = false

          return v

        })

        this.vdpDay = item.day

        item.checked = true

        this.$emit('input', item.date)

        this.$emit('on-change', item.date, this.newDate(item.date))

      }

    },

    // 上一个月

    toVdpUpper () {

      if (this.vdpMonth > 1) {

        this.vdpMonth--

      } else {

        this.vdpYear--

        this.vdpMonth = 12

      }

      this.vdpDay = 0

      this.computePicker()

    },

    // 下一个月

    toVdpLower () {

      if (this.vdpMonth < 12) {

        this.vdpMonth++

      } else {

        this.vdpYear++

        this.vdpMonth = 1

      }

      this.vdpDay = 0

      this.computePicker()

    },

    // 计算月初、月末、月初是星期几及当前日期数据

    computePicker () {

      const yc = new Date(this.vdpYear, this.vdpMonth - 1, 1) // 月初

      const ym = new Date(this.vdpYear, this.vdpMonth, 0) // 月末

      this.monthStart = yc.getDate()

      this.monthEnd = ym.getDate()

      this.monthWeek = yc.getDay()

      const v = []

      for (let i = 0; i < 42; i++) {

        v.push(this.getVdpDay(i, '-'))

      }

      this.dateList = v

    },

    // 获取当前日期 核心方法

    getVdpDay (index, defV) {

      let _disabled = false

      let _actived = false

      let _year = this.vdpYear

      let _month = this.vdpMonth

      let _day = 0

      if (this.monthWeek === 0) {

        index = index - 7

      }

      if (index < this.monthWeek) {

        // 计算上月

        _month--

        if (_month === 0) {

          _year--

          _month = 12

        }

        _day = new Date(_year, _month, 0).getDate() - this.monthWeek + index + 1 // 算当前选中月的上一月 月末

      } else if (index > (this.monthWeek + this.monthEnd - 1)) {

        // 计算下月

        _month++

        if (_month === 13) {

          _year++

          _month = 1

        }

        _day = new Date(this.vdpYear, this.vdpMonth, 1).getDate() + index - (this.monthEnd + this.monthWeek) // 算当前选中月的下一月 月初

      } else {

        // 计算当月

        _day = this.monthStart + index - this.monthWeek

        _actived = true

      }

      // 计算开始日期和结束日期

      if (this.start || this.end) {

        const _curtm = new Date(_year + '/' + _month + '/' + _day)

        if (this.start) {

          const _start = this.newDate(this.start)

          if (_curtm < _start) {

            _disabled = true

          }

        }

        if (this.end) {

          const _end = this.newDate(this.end)

          if (_curtm > _end) {

            _disabled = true

          }

        }

      }

      return { year: _year, month: _month, day: _day, date: this.generateDateStr(_year, _month, _day), checked: this.verifyCheckDate(_year, _month, _day), actived: _actived, today: this.isToday(_year, _month, _day), disabled: _disabled } // _disabled ? (defV || '') : _day

    },

    // 生成时间字符串

    generateDateStr (year, month, day) {

      return year + '-' + (month > 9 ? month : '0' + month) + '-' + (day > 9 ? day : '0' + day)

    },

    // 验证是否是当前选中的日期

    verifyCheckDate (year, month, day) {

      if ((this.value && this.value === this.generateDateStr(year, month, day)) || (this.vdpYear === year && this.vdpMonth === month && this.vdpDay === day)) {

        return true

      } else {

        return false

      }

    },

    // 验证是否今天

    isToday (year, month, day) {

      if (this.generateDateStr(year, month, day) === this.formatDate(new Date(), 'yyyy-MM-dd')) {

        return true

      } else {

        return false

      }

    },

    // 字符串转换 Date 解决ie11 时间兼容性问题

    newDate (time, defV) {

      let v = defV === undefined ? new Date() : defV

      if (time) {

        v = new Date(time.replace(/-/g, '/'))

      }

      return v

    },

    // 时间格式化 兼容IE

    formatDate (date, format) {

      if (date) {

        var time

        if (typeof date === 'string') {

          time = new Date(date.replace(/-/g, '/'))

        } else if (typeof date === 'object') {

          time = new Date(date)

        }

        var o = {

          'M+': time.getMonth() + 1, // 月份

          'd+': time.getDate(), // 日

          'h+': time.getHours(), // 小时

          'm+': time.getMinutes(), // 分

          's+': time.getSeconds(), // 秒

          'q+': Math.floor((time.getMonth() + 3) / 3), // 季度

          S: time.getMilliseconds() // 毫秒

        }

        if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (time.getFullYear() + '').substr(4 - RegExp.$1.length))

        for (var k in o) { if (new RegExp('(' + k + ')').test(format)) format = format.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }

        return format

      } else { return '' }

    }

  },

  computed: {

    itemStyles () {

      const _w = (this.width / 7 - this.margin * 2)

      const _h = ((this.height - this.toolHeight) / 7 - this.margin * 2)

      const _lh = this.lineHeight ? this.lineHeight : (_h - 1) + 'px' // parseInt(_h)

      return { width: _w + 'px', height: _h + 'px', margin: this.margin + 'px', 'line-height': _lh }

    }

  },

  created () {

    this.init()

  }

}

</script>

<style scoped>

.picker-box {

  width: 100%;

  height: 100%;

  margin: 0 auto;

  font-size: 14px;

}

.picker-box .picker-tool {

  height: 50px;

  line-height: 50px;

  text-align: center;

}

.picker-box .picker-body {

  width: 100%;

}

.picker-box .picker-body .picker-item, .picker-box .picker-body .picker-title  {

  width: calc(14.285714285% - 10px);

  height: calc(16.666666666% - 10px);

  line-height: 2.6;

  text-align: center;

  border: 1px solid #ececec;

  border-radius: 5px;

  margin: 5px;

  float: left;

}

.picker-box .picker-body .picker-item {

  cursor: pointer;

}

.picker-box .picker-body .picker-title {

  background: #eaf0f5;

}

.picker-box .picker-body .grey {

  color: #b2b2b2;

  border: 1px solid #ececec;

  background: #fbfbfb;

}

.picker-box .picker-body .actived {

  color: #3c3c3c;

  border: 1px solid #ececec;

}

.picker-box .picker-body .today {

  background: #d8f9d8;

  border: 1px solid #ececec;

}

.picker-box .picker-body .checked {

  color: #fff;

  background: #19be6b;

  border: 1px solid #19be6b;

}

.picker-box .picker-body .disabled {

  color: #b2b2b2;

  background: #f2f2f2;

  border: 1px solid #ececec;

  cursor: not-allowed;

}

</style>

4、组件调用示例

<template>

  <div>

      <cDatePicker v-model="timeText"></cDatePicker>

      <cDatePicker v-model="timeText" :start="start" :end="end" :width="500" :height="500" :tool-height="50" :margin="5"></cDatePicker>

  </div>

</template>

<script>

import cDatePicker from './cDatePicker'

export default {

  components: {

    cDatePicker

  },

  data () {

    return {

      timeText: '2020-07-16',

      start: '2020-06-12',

      end: '2020-08-05'

    }

  }

}

</script>

5、尚需待完善

a、点击年,进行年份切换

b、点击月,进行月份切换。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349