LoginSignup
32
20

More than 3 years have passed since last update.

Vuetifyのdatepickerを使って【和暦】+【年度/月】pickerを作ってみた

Last updated at Posted at 2019-12-08

vuetifyのv-date-pickerは、表面上のテキストをfuncitionで書き換えることができるので【和暦表示】と【年度/月】が選択できるピッカーを作ってみました。
0.5x倍にしてみると普通に見れると思います。

See the Pen WarekiDatePicker by BigFly3 (@bigfly3) on CodePen.

本来やらないような操作したときに稀にエラーになるかもしれませんが、公式のもおかしな感じになるのでそこは無視してください。

親からも和暦情報が取れます

ピッカーだけ和暦になっても、コンポーネントの連携での恩恵が薄いので、@changeの際にオブジェクトで和暦情報を送るようにしています。

データについて

v-modelでセットされるデータは普通のピッカーと同様(YYYY-MM-DD)です.
和暦のオブジェクトに関してはcodepenの日付を選択してもらうと、typeによって取れる情報が確認できます。年度に関しては、リアル年と年度年の両方取得できます。

@change → 和暦のデータを含めたオブジェクトの取得
@input → valueの取得

フォーマット変更したい場合

下にあるcomponentのjpv~Format()のメソッド部分をいじれば色々変えられるかと思います。

オブジェクトで返すデータを調整したい場合

下にあるcomponentのjpvDate()の中で調整すると、自分好みのデータを返すことが出来ると思います。

component化したもの

v-date-pickerをラッパー化して、フォーマット関連以外のプロパティは、ほぼそのまま使えるようにしてあります。v-date-pickerから置き換えてもそのまま動くと思います。

あんまり自分で使ったわけではないですが、vuetifyの公式サンプルが動くくらいにはなってると思います。よかったらお試しください。

jpv-date-picker
<template>
<v-date-picker
  :allowedDates='allowedDates'
  :color='color'
  :dark='dark'
  :dayFormat='jpvDayFormat'
  :disabled='disabled'
  :eventColor='eventColor'
  :events='events'
  :firstDayOfWeek='firstDayOfWeek'
  :fullWidth='fullWidth'
  :headerColor='headerColor'
  :headerDateFormat='str => jpvHeaderDateFormat(str)'
  :landscape='landscape'
  :light='light'
  locale='ja-jp'
  :max='max'
  :min='min'
  :monthFormat='str => jpvMonthFormat(str)'
  :multiple='multiple'
  :nextIcon='nextIcon'
  :noTitle='noTitle'
  :pickerDate='pickerDate'
  :prevIcon='prevIcon'
  :range='range'
  :reactive='reactive'
  :readonly='readonly'
  :scrollable='scrollable'
  :selectedItemsText='selectedItemsText'
  :showCurrent='pickerShowCurrent'
  :showWeek='showWeek'
  :titleDateFormat=jpvTitleDateFormat
  :type='jpvType'
  :value='jpvValue'
  :width='width'
  :yearFormat='str => jpvYearFormat(str)'
  :yearIcon='yearIcon'
  @change="changeValue"
  @click:month='clickMonth'
  @click:date='clickDate'
  @update:picker-date='updatePickerDate'

  ><slot></slot></v-date-picker>
</template>
<script>
export default {
  props:{
    allowedDates: Function,
    color: String,
    dark: Boolean,
    dayFormat: Function,
    disabled: Boolean,
    eventColor: [Array, Function, Object, String],
    events: [Array, Function, Object],
    firstDayOfWeek: [String, Number],
    fullWidth: Boolean,
    headerColor: String,
    landscape: Boolean,
    light: Boolean,
    max: String,
    min: String,
    multiple: Boolean,
    nextIcon: String,
    noTitle: Boolean,
    pickerDate: String,
    prevIcon: String,
    range: Boolean,
    reactive: Boolean,
    readonly: Boolean,
    scrollable: Boolean,
    showCurrent: [Boolean, String],
    showWeek: [Boolean],
    selectedItemsText: String,
    type: {
      type: String,
      default: 'date' // 'date' or 'month' or 'nendo'
    },
    value:[String, Array, Object],
    width:[Number, String],
    yearIcon: String
  },
  data: () => {
    const now = new Date();
    return {
      inputValue:'',
      now:now,
    }
  },
  created(){
    this.inputValue = this.value
  },
  methods:{
    real2nendoM(realM){ // 内部/月 → 年度/月
      return realM < 10 ? realM + 3 : realM - 9
    },
    real2nendoY(realY,realM){ // 内部/月 → 年度/年
      return realM < 10 ? realY : realY + 1
    },
    real2nendoYM(realYM){ // 内部/年月 → 年度/年月
      if(!realYM) return ''
      const y = parseInt(realYM.split('-')[0]);
      const m = parseInt(realYM.split('-')[1]);
      return this.real2nendoY(y,m) + '-' + this.strpad(this.real2nendoM(m))
    },
    nendo2realM(nendoM){ // 年度/月 → 内部/月
      return nendoM < 4 ? nendoM + 9 : nendoM - 3
    },
    nendo2realY(nendoY,nendoM){ // 年度/年月 → 内部/年
      return nendoM < 4 ? nendoY - 1 : nendoY
    },
    nendo2realYM(nendoYM){ // 年度/年月 → 内部/年月
      if(!nendoYM) return ''
      const y = parseInt(nendoYM.split('-')[0]);
      const m = parseInt(nendoYM.split('-')[1]);
      return this.nendo2realY(y,m) + '-' + this.strpad(this.nendo2realM(m))
    },
    year2Wareki(year){
      let wYear = ''
      let gen = ''
      if(year > 2018){
        wYear = year-2018
        gen = '令和'
      }else if(year > 1988){
        wYear= year-1988
        gen = '平成'
      }else if(year > 1925){
        wYear = year-1925
        gen = '昭和'
      }else if(year > 1911){
        wYear = year-1911
        gen = '大正'
      }else if(year > 1867){
        wYear = year-1867
        gen = '明治'
      }
      if(wYear === 1) wYear = ''
      return gen !== '' ? gen + wYear + '' : false
    },
    strpad(num){
      return ( '00' + num ).slice( -2 );
    },
    changeValue:function(str){
      if(!str) return false
      if(this.readonly) return false
      if(this.type === 'nendo'){
        str = this.real2nendoYM(str)
      }
      this.inputValue = str
      this.$emit('change',this.jpvDate)
    },
    clickMonth:function(str){
      if(this.readonly) return false
      if(this.type === 'nendo'){
        str = this.real2nendoYM(str)
      }
      if(this.type !== 'date'){
        this.updateValue(str)
      }
      this.$emit('input',this.inputValue)
    },
    clickDate:function(str){
      if(this.readonly) return false
      this.updateValue(str)
      this.$emit('input',this.inputValue)
    },
    updateValue(str){
      if(this.multiple){
        const index = this.inputValue.indexOf(str)
        if(index > -1){
          this.inputValue = this.inputValue.filter(n => n != str);
        }else{
          this.inputValue.push(str)
        }
      }else if(this.range){
        if(this.inputValue.length > 1){
          this.inputValue = []
        }
        this.inputValue.push(str)
      }
    },
    updatePickerDate(str){
      this.$emit('update:picker-date',str)
    },

    //ヘッダーの一番上にある年表示+年選択のフォーマット
    jpvYearFormat(str){
      if(!str) return ''
      const year = this.type==='date' ? str.split('-')[0]: str
      return this.year2Wareki(year) + '(' + year + ')'
    },
    //ヘッダーの選択データを表示する部分
    jpvTitleDateFormat(str){ // realYM → YYYY-MM
      if(!str) return ''
      return this.multiple || this.range ? String(this.inputValue.length) + ' selected' : this.inputValue
    },
    //月選択ボタン上部のYYYYを変換するfunction
    jpvHeaderDateFormat(str){
      if(!str) return ''
      const [year,month,day] = str.split('-')
      if(this.type === 'nendo'){
        return this.year2Wareki(year) + '度(' + year + ')'
      }else if(this.type === 'date' && month){ //dateの場合でも月選択するケースがある
        return this.year2Wareki(year)  + '(' + year + ')' + month + ''
      }else{
        return this.year2Wareki(year) + '(' + year + ')'
      }
      return str
    },
    //月選択ボタンを年度の見た目にするfunction
    jpvMonthFormat(str){ // realYM → YYYY-MM
      if(str === '') return str
      let m = parseInt(str.split('-')[1])
      if(this.type === 'nendo') m = this.real2nendoM(m)
      return m + ''
    },
    //月選択ボタンを年度の見た目にするfunction
    jpvDayFormat(str){ // realYMD → YYYY-MM-DD
      if(str === '') return str
      let d = parseInt(str.split('-')[2])
      return d
    },
  },
  computed:{
    pickerShowCurrent(){ //picker値は元のまま変わらないのでpickerの位置を3か月ずらす
      if(!this.showCurrent){ //カレント表示をなくす場合
        return false
      }
      let current = this.showCurrent
      if(this.type === 'nendo'){
        //初期状態でカレントがセットされていない場合、内部を現時刻から3か月前にセット
        if(current === true){
          current = this.now.getFullYear() + '-' +  parseInt(this.now.getMonth() + 1);
        }
        current = this.nendo2realYM(current)
      }
      return current
    },
    lastValue(){
      return this.multiple || this.range ? this.inputValue[this.inputValue.length - 1] : this.inputValue
    },
    jpvType(){
      return this.type==='nendo' ? 'month' : this.type
    },
    jpvDate(){
      if(typeof this.inputValue !== 'string' || !this.inputValue.match( /^(\d{4})/ )) return ""

      const d = {}
      d.value = this.inputValue
      let [year,month,day] = d.value.split('-')

      d.year = parseInt(year)
      d.month = month
      d.day = day ? day : ''
      d.wareki = this.year2Wareki(d.year)

      d.WM = d.wareki + d.month + ''
      d.WYM = d.wareki + '(' + d.year + '/' + d.month + ')'

      //表示用のデータ
      if(this.type==='date'){
        d.value = d.year + '-' + d.month + '-' + d.day
        d.WMD = d.wareki + d.month + '' + d.day + ''
        d.WYMD = d.wareki + '(' + d.year + '/' + d.month + '/' + d.day + ')'
      }else if(this.type==='nendo'){
        d.nendo = {}
        d.nendo.year = d.month < 4 ? d.year -1 : d.year
        d.nendo.month = d.month
        d.nendo.wareki = this.year2Wareki(d.nendo.year) + ''
        d.nendo.WM = d.nendo.wareki + d.nendo.month + ''
        d.nendo.WYM = d.nendo.wareki + '(' + d.nendo.year + '/' + d.nendo.month + ')'
      }
      return d
    },
    jpvValue(){
      if(!this.inputValue){
        return this.multiple || this.range ? []:"";
      }
      let returnValue = this.inputValue;
      if(this.type==='nendo'){
        if(this.multiple || this.range){
          returnValue = this.inputValue.map((str)=>{
            return str.match( /^(\d{4})-(\d{2})$/ ) ? this.nendo2realYM(str) : str
          })
        }else{
          returnValue = this.nendo2realYM(this.inputValue)
        }
      }
      return returnValue
    },
  },
  watch:{
    multiple:{
      handler(newVal){
        this.inputValue = newVal ? []: ''
        this.$emit('input',this.inputValue)
      }
    },
    range:{
      handler(newVal){
        this.inputValue = newVal ? []: ''
        this.$emit('input',this.inputValue)
      }
    },
    type:{
      handler(newVal){ //typeを切り替えるとv-date-pickerとの整合性が合わないので再設定する
        this.$children[0].tableDate = this.$children[0].inputDate
      }
    },
  }
}
</script>

:christmas_tree: FORK Advent Calendar 2019
:arrow_left: 08日目 Timelineをもう一度 @AsaToBan
:arrow_right: 10日目 Google Translation API v3 を Node で使ってみた @karaage7

32
20
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
20