Full Code of Hzy0913/mpvue-calendar for AI

master a4024573605b cached
35 files
119.4 KB
38.1k tokens
50 symbols
1 requests
Download .txt
Repository: Hzy0913/mpvue-calendar
Branch: master
Commit: a4024573605b
Files: 35
Total size: 119.4 KB

Directory structure:
gitextract_lou6h3iy/

├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── README.zh.md
├── babel.config.js
├── example/
│   ├── App.vue
│   └── main.js
├── package.json
├── public/
│   └── index.html
├── src/
│   ├── components/
│   │   ├── icon/
│   │   │   └── icon.css
│   │   ├── swipe/
│   │   │   ├── declare.ts
│   │   │   ├── index.vue
│   │   │   ├── slide.vue
│   │   │   ├── style.less
│   │   │   └── utils.ts
│   │   ├── timetable/
│   │   │   ├── computed.ts
│   │   │   ├── controller.ts
│   │   │   ├── declare.ts
│   │   │   ├── index.vue
│   │   │   └── style.less
│   │   ├── tools/
│   │   │   ├── index.vue
│   │   │   └── style.less
│   │   └── utils/
│   │       └── index.ts
│   ├── declare.ts
│   ├── lunar.ts
│   ├── mpvue-calendar.vue
│   ├── shims-vue.d.ts
│   └── style.less
├── tsconfig.json
└── vue.config.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .browserslistrc
================================================
> 1%
last 2 versions
not dead


================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: .eslintignore
================================================
dist/*
node_modules/*
**/node_modules/*
example/*
src/lunar.ts


================================================
FILE: .eslintrc.js
================================================
module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    'plugin:vue/vue3-essential',
    '@vue/airbnb',
    '@vue/typescript/recommended',
  ],
  parserOptions: {
    ecmaVersion: 2020,
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'import/no-named-as-default': ['off'],
    'comma-dangle': ['off', 'never'],
    'spaced-comment': ['off', 'always'],
    'func-names': ['off'],
//    'indent': ['error', 2, {'SwitchCase': 1}], //暂时关闭
    'indent': 0, //暂时关闭
    'import/prefer-default-export': 0, //暂时关闭
    'no-unused-expressions': ['off'],
//    'max-len': ['warn', 100, 2, {'ignoreComments': true}], //暂时关闭
    'max-len': 0,
    'object-curly-spacing': 0,
    /* 暂时关闭 */
    'arrow-parens': 0,
    '@typescript-eslint/no-explicit-any': 0,
    '@typescript-eslint/no-use-before-define': 0,
    'array-callback-return': 0,
    'class-methods-use-this': 0,
    'default-case': 0,
    'no-continue': 0,
    'prefer-template': 0,
    'consistent-return': 0,
    'global-require': 0,
    'import/extensions': 0,
    'import/no-extraneous-dependencies': 0,
    'import/no-unresolved': 0,
    'no-bitwise': 0,
    'no-case-declarations': 0,
    'no-confusing-arrow': 0,
    'no-mixed-operators': 0,
    'no-param-reassign': 0,
    'no-plusplus': 0,
    'no-return-assign': 0,
    'no-underscore-dangle': 0,
    'prefer-object-spread': 0,
    'no-unused-vars': 0,
    'no-useless-concat': 0,
    'object-curly-newline': 0,
    'no-restricted-globals': 0,
    'no-else-return': 0,
    'space-infix-ops': 0,
    'prefer-destructuring': ['error', {
      'VariableDeclarator': {
        'array': false,
        'object': false
      },
      'AssignmentExpression': {
        'array': false,
        'object': false
      }
    }, {
      'enforceForRenamedProperties': true
    }]
  },
};


================================================
FILE: .gitignore
================================================
.DS_Store
node_modules/
dist/
lib/
demo/
npm-debug.log
yarn-error.log

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln


================================================
FILE: .npmignore
================================================
node_modules
build
config
example
demo
dist/demo.html
dist/mpvue-calendar.umd.js
static
public
src
lib
.babelrc
.browserslistrc
.editorconfig
.eslintignore
.eslintrc.js
babel.config.js
.postcssrc.js
tsconfig.json
vue.config.js
yarn.lock
.gitignore
index.html
package-lock.json
mpvue-calendar.common.js


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2018-present, ZhaoYun (Ricky) Han

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
<p align="center">
<a href="http://preview.binlive.cn/mpvue-calendar#/">
<img width="100" src="https://raw.githubusercontent.com/Hzy0913/hanlibrary/master/mpvue-calendar.png" alt="mpvue-calendar logo">
</a>
</p>
<p align="center">
  <a href="https://npmcharts.com/compare/mpvue-calendar?minimal=true">
  <img src="https://img.shields.io/npm/dm/mpvue-calendar.svg" alt="Downloads">
  </a>
  <a href="https://www.npmjs.com/package/mpvue-calendar">
  <img src="https://img.shields.io/npm/v/mpvue-calendar.svg" alt="Version">
  </a>
  <a href="https://www.npmjs.com/package/mpvue-calendar">
  <img src="https://img.shields.io/npm/l/mpvue-calendar.svg" alt="License">
  </a>
</p>

<h1 align="center">mpvue-calendar</h1>

> A feature-rich calendar component, support multiple modes and gesture sliding. For vue 3.0+

<p align="center">
<img width="940" src="http://img.binlive.cn/687474703a2f2f696d672e62696e6c6976652e636e2f75706c6f61642f3136313339373433383732383363616c656e6461722d707265766965772e706e67.png" alt="mpvue-calendar preview">
</p>

- #### [Preview](http://preview.binlive.cn/mpvue-calendar#/ "Preview")
- #### [Demo](https://github.com/Hzy0913/mpvue-calendar/blob/master/example/App.vue "Demo")
- #### [中文文档](https://github.com/Hzy0913/mpvue-calendar/blob/master/README.zh.md "Docs")

## 💻 Install
mpvue-calendar only support **vue@3.0+**

```
npm i mpvue-calendar -S
```
## 🔨 Usage

```javascript
<Calendar
  backgroundText
  class-name="select-mode"
  :remarks="remarks"
/>

import { ref } from 'vue'
import Calendar from 'mpvue-calendar'

export default {
  components: {
    Calendar,
  },
  setup() {
    const remarks = ref({'2021-1-13': 'some tings'})

    return {
      remarks,
    }
  }
}
```
## ⚙️ API
|  name  |  type  |  default  | description   |
| ------------ | ------------ | ------------ | ------------ |
| selectMode  | String  | 'select'  |  For the selection mode of calendar component, can be used by **'select'**, **'multi'**,**'range'**, **'multiRange'** mode  |
|  mode | String  |  'month'|  Configure calendar display mode, the modes has **'month'**, **'week'**,**'monthRange'** |
| selectDate  | String / String[] / {start: String; end: String} / {start: String; end: String} [] |   |   In different selection modes, there are use different types.  `String` type for **select** mode, `String[]` type for **multi** mode, `{start: String; end: String}` type for **range** mode, and `{start: String; end: String} []` type for **multiRange** mode.  |
|  monthRange  |  String[]  |    | If you use **monthRange mode**,  you need to set the content of the month to be displayed. for example `[2021-1, 2021-2, 2021-6, 2021-9]`   |
| remarks  | Object  |   |  Create remark for a day, key is date string, and value is remark content. for example `{ '2021-1-13': 'some things' }`  |
| tileContent  |  Object |   | Create tile content  for a day, key is date string, and value is `object`, object have **className** and **content**. for example `{ '2021-1-5': { className: 'tip-class', content: 'some tip' } } `   |
|  holidays  |  Object  |    |   Custom holiday information,  for example `{'2021-1-1': 'New Year'}`  |
|  completion | Boolean  | false  |  Complete the calendar table with 6 lines   |
| useSwipe  | Boolean  |  true |  The mobile terminal supports gesture sliding to switch calendar   |
| arrowLeft  | String  |   |  Left arrow image url of toolbar    |
| arrowRight  | String  |   |  Right arrow image url of toolbar   |
| monFirst  | Boolean  |  false |   The first day of the week begins on Monday  |
| backgroundText  | Boolean  |  false |  Displays the background text of the current month calendar  |
|  language  |  String  |    | use **'en'** or **'cn'** language   |
|  format  |  (year, month) => [String, String]  |    | Format the date display at the header. you need return a array,  the contents of the array are year and month  |
|  weeks  |  String[]  |    |  Weekly display content of custom header, for example ['S', 'M', 'T', 'W', 'T', 'F', 'S']   |
|  begin  |  String  |    |   Set the available date of the start, and the date before it will be disabled, for example ` '2021-1-5' `  |
|  end  |  String  |    |  Set the available date of the end, and the date after it will be disabled, for example `'2021-2-5'`  |
|  disabled  |  String[]  |    |  Disable certain dates , for example `['2021-1-9', '2021-2-5']`  |

#### Chinese lunar
If you need show chinese lunar, you need import lunar module.
```javascript
<Calendar
  :lunar="lunar"
/>

import lunar from 'mpvue-calendar/dist/lunar'
export default {
  ...,
  setup() {
    return {
      lunar,
    }
  }
}
```

## ⚙️ methods
|  name | type  |  description |
| ------------ | ------------ | ------------ |
| onSelect  |  (selectDate) => void |   This function is triggered when the date is selected  |
| onMonthChange | (year, month, day) => void  |   The callback is triggered when the month is change  |
| next | (year, month) => void  |    Callback this method when triggered next month   |
| prev | (year, month) => void  |    Callback this method when triggered prev month   |
| setToday | ref method |   Back today, you need to pass the ref parameter to call the internal method |




================================================
FILE: README.zh.md
================================================
<p align="center">
<a href="http://preview.binlive.cn/mpvue-calendar#/">
<img width="100" src="https://raw.githubusercontent.com/Hzy0913/hanlibrary/master/mpvue-calendar.png" alt="mpvue-calendar logo">
</a>
</p>
<p align="center">
  <a href="https://npmcharts.com/compare/mpvue-calendar?minimal=true">
  <img src="https://img.shields.io/npm/dm/mpvue-calendar.svg" alt="Downloads">
  </a>
  <a href="https://www.npmjs.com/package/mpvue-calendar">
  <img src="https://img.shields.io/npm/v/mpvue-calendar.svg" alt="Version">
  </a>
  <a href="https://www.npmjs.com/package/mpvue-calendar">
  <img src="https://img.shields.io/npm/l/mpvue-calendar.svg" alt="License">
  </a>
</p>

<h1 align="center">mpvue-calendar</h1>

> 一款功能丰富的日历组件,支持多种模式和手势滑动。 基于vue 3.0+

<p align="center">
<img width="940" src="http://img.binlive.cn/upload/1613974387283calendar-preview.png" alt="mpvue-calendar preview">
</p>

- #### [预览](http://preview.binlive.cn/mpvue-calendar#/ "Preview")
- #### [例子](https://github.com/Hzy0913/mpvue-calendar/blob/master/example/App.vue "Demo")

## 💻 安装
mpvue-calendar 只支持 **vue@3.0+**

```
npm i mpvue-calendar -S
```
## 🔨 使用

```javascript
<Calendar
  backgroundText
  class-name="select-mode"
  :remarks="remarks"
/>

import { ref } from 'vue'
import Calendar from 'mpvue-calendar'

export default {
  components: {
    Calendar,
  },
  setup() {
    const remarks = ref({'2021-1-13': 'some tings'})

    return {
      remarks,
    }
  }
}
```
## ⚙️ API
|  name  |  type  |  default  | description   |
| ------------ | ------------ | ------------ | ------------ |
| selectMode  | String  | 'select'  | 对于日历组件的选择模式,可以通过传入 **'select'**, **'multi'**, **'range'**, **'multiRange'** 参数使用  |
|  mode | String  |  'month'|  配置日历显示模式,可以通过传入 **'month'**, **'week'**,**'monthRange'**  使用该功能 |
| selectDate  | String / String[] / {start: String; end: String} / {start: String; end: String} [] |   |在不同的选择模式下,需要对应不同的数据类型。  `String` 类型对应在 'select' 模式下,`String[]` 数组类型对应在 'multi' 模式下,`{start: String; end: String}` 类型对应在 'range' 模式下,`{start: String; end: String}[]` 数组类型对应在 'multiRange' 模式下 |
|  monthRange  |  String[]  |    |如果使用monthRange模式,则需要设置要显示的月份的内容。 例如[2021-1、2021-2、2021-3]   |
| remarks  | Object  |   |  创建某一天的备注,key是日期字符串,value是备注内容。 例如{'2021-1-13':'一些备注'} |
| tileContent  |  Object |   | 创建某一天的贴片内容,key是日期字符串,value是object类型,`object`有**className**和**content**字段。例如{2021-1-5':{className:'tip class',content:'some tip'}  |
|  holidays  |  Object  |    |  自定义节假日信息,例如{'2021-1 ':'New Year'} |
|  completion | Boolean  | false  | 用6行补全日历表格 |
| useSwipe  | Boolean  |  true |  启用移动端支持手势滑动切换日历   |
| arrowLeft  | String  |   |  工具栏左侧箭头图片   |
| arrowRight  | String  |   |  工具栏右侧箭头图片   |
| monFirst  | Boolean  |  false |  一周的第一天从星期一开始 |
| backgroundText  | Boolean  |  false | 显示当前月份的背景文本 |
|  language  |  String  |    | 使用 **'en'** 或 **'cn'** 语言   |
|  format  |  (year, month) => [String, String]  |    | 格式化头部的日期显示。 您需要返回一个数组,该数组的内容是对应的年和月  |
|  weeks  |  String[]  |    | 自定义标题的每周显示内容,例如['星期一', '星期二', '星期三', '星期四', ...] |
|  begin  |  String  |    |   设置开始的可用日期,在此之前的日期将被禁用,例如 '2021-1-5'  |
|  end  |  String  |    |  设置结束的可用日期,在此之后的日期将被禁用,例如 '2021-2-5'   |
|  disabled  |  String[]  |    | 禁用某些日期,例如 `['2021-1-9', '2021-2-5']`|

#### 农历
如果你需要展示中国农历,你需要导入农历模块。
```javascript
<Calendar
  :lunar="lunar"
/>

import lunar from 'mpvue-calendar/dist/lunar'
export default {
  ...,
  setup() {
    return {
      lunar,
    }
  }
}
```

## ⚙️ methods
|  name | type  |  description |
| ------------ | ------------ | ------------ |
| onSelect  |  (selectDate) => void |   选择日期时触发此函数 |
| onMonthChange | (year, month, day) => void  |   当月份发生变化时会触发该回调  |
| next | (year, month) => void  |  进入下月时触发该回调方法|
| prev | (year, month) => void  |    进入上月时触发该回调方法 |
| setToday | ref method | 回到今天,您需要传递ref参数来调用组件内部方法 |


================================================
FILE: babel.config.js
================================================
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
  ],
};


================================================
FILE: example/App.vue
================================================
<template>
  <div class="container">
    <div class="container-select-modes">
      <!--select mode-->
      <Calendar
        backgroundText
        completion
        class-name="select-mode"
        :format="formatOfSelecteMode"
        :holidays="holidays"
        :select-date="selectModeDate"
        language="en"
        @selectYear="selectYear"
        @selectMonth="selectMonth"
        @next="next"
        @prev="prev"
        @onMonthChange="onMonthChange"
        @onSelect="onSelect"
      />
      <!--multi mode-->
      <Calendar
        selectMode="multi"
        class-name="multi-mode"
        language="en"
        :tile-content="multiTileContent"
        :select-date="multiModeDate"
        :begin="begin"
        :end="end"
        @onSelect="onSelect"
      />
      <!--range mode-->
      <Calendar
        monFirst
        backgroundText
        selectMode="range"
        :lunar="lunar"
        class-name="range-mode"
        :format="formatOfRangeMode"
        :select-date="rangeModeDate"
        language="cn"
        @onSelect="onSelect"
      />
      <!--multiRange mode-->
      <Calendar
        monFirst
        completion
        backgroundText
        selectMode="multiRange"
        class-name="multiRange-mode"
        :select-date="multiRangeModeDate"
        :format="formatOfmultiMode"
        :weeks="weeks"
        @onSelect="onSelect"
      />
    </div>
    <div class="container-view-modes">
      <!--week mode-->
      <Calendar
        backgroundText
        :lunar="lunar"
        selectMode="range"
        class-name="week-mode"
        mode="week"
        ref="weekModeRef"
      />
      <button @click="backToToday" class="back-to-today">Back to Today</button>
      <!--monthRange mode-->
      <Calendar
        backgroundText
        selectMode="range"
        class-name="monthRange-mode"
        mode="monthRange"
        :monthRange="['2021-2', '2021-3', '2021-4']"
      />
    </div>
  </div>
</template>

<script>
  import Calendar from '../src/mpvue-calendar';
  import lunar from '../src/lunar';
  import { defineComponent, ref, reactive, onMounted, watchEffect, watch } from 'vue';

  export default {
    name: 'app',
    components: {
      Calendar
    },
    setup() {
      const currentDate = new Date();
      const currentYear = currentDate.getFullYear();
      const currentMonth = currentDate.getMonth() + 1;
      const currentDay = currentDate.getDate();

      const holidays = ref({
        '1-1': 'New Year',
        '2-2': 'Wetlands',
        '2-14': 'Valentine',
        '3-8': 'Women',
        '4-1': 'April Fools',
        '4-22': 'World Earth',
        '5-1': 'Labour',
        '6-1': 'Children',
        '8-1': 'Youth',
        '10-5': 'World Teachers',
        '10-31': 'Halloween',
        '12-25': 'Christmas',
      })
      const completion = ref(false)

      const getRandom = () => Math.floor(Math.random() * (28 - 1 + 1)) + 1
      const selectModeDate = ref(`${currentYear}-${currentMonth}-${getRandom()}`)
      const multiModeDate = ref([`${currentYear}-${currentMonth}-${getRandom()}`, `${currentYear}-${currentMonth}-${getRandom()}`, `${currentYear}-${currentMonth}-${getRandom()}`])
      const rangeModeDate = ref({start: `${currentYear}-${currentMonth}-10`, end: `${currentYear}-${currentMonth}-14`})
      const multiRangeModeDate = ref([{start: `${currentYear}-${currentMonth}-8`, end: `${currentYear}-${currentMonth}-12`}, {start: `${currentYear}-${currentMonth}-20`, end: `${currentYear}-${currentMonth}-23`}])

      const weekModeRef = ref()
      const begin = ref('2021-1-13')
      const end = ref('2025-2-13')
      const monthRange = ref(['2021-1', '2021-6', '2021-12'])
      const disabled = ref(['2021-1-2', '2021-1-4', '2021-1-23'])
      const multiTileContent = ref({
        [`${currentYear}-${currentMonth}-${currentDay}`]: {
          className: 'content-item-classname',
          content: 'some things'
        }
      })

      function onSelect(selectDate) {
        console.log(selectDate, 'selectDate')
      }

      function backToToday() {
        weekModeRef.value.setToday();
      }

      function rangeMonthFormat(year, month) {
        return [year, month + '月']
      }

      function formatOfSelecteMode(year, month) {
        const transform = {
          1: 'Jan',
          2: 'Feb',
          3: 'Mar',
          4: 'Apr',
          5: 'May',
          6: 'Jun',
          7: 'Jul',
          8: 'Aug',
          9: 'Sept',
          10: 'Oct',
          11: 'Nov',
          12: 'Dec',
        }
        return [`${year}`, `${transform[month]}`];
      }

      function formatOfRangeMode(year, month) {
        const transform = {
          1: '一',
          2: '二',
          3: '三',
          4: '四',
          5: '五',
          6: '六',
          7: '七',
          8: '八',
          9: '九',
          10: '十',
          11: '十一',
          12: '十二',
        }
        return [`${year}年`, `${transform[month]}月`];
      }

      function formatOfmultiMode(year, month) {
        return [`${year}年`, `${month}月`];
      }

      function selectYear(y, m) {
        console.log(y, m, 'selectYear')
      }

      function onMonthChange(y, m) {
        console.log(y, m, 'onMonthChange')
      }

      function selectMonth(y, m) {
        console.log(y, m, 'selectMonth')
      }

      function next(y, m, d) {
        console.log(y, m, d, 'nextnext')
      }

      function prev(y, m, d) {
        console.log(y, m, d, 'prevprev')
      }

      return {
        lunar,
        holidays,
        onSelect,
        monthRange,
        disabled,
        completion,
        begin,
        weeks: ['一', '二', '三', '四', '五', '六', '日'],
        end,
        selectModeDate,
        onMonthChange,
        next,
        prev,
        selectMonth,
        selectYear,
        weekModeRef,
        multiModeDate,
        multiRangeModeDate,
        rangeModeDate,
        backToToday,
        multiTileContent,
        formatOfmultiMode,
        formatOfRangeMode,
        formatOfSelecteMode,
      }
    }
  };
</script>

<style lang="less">
  body, html {
    background-color: #fbf9fe;
    margin: 0;
    padding: 0;
  }
  .container{
    width: 1000px;
    margin: 0 auto;
    .select-mode{
      .vc-calendar-year{
        margin-right: 10px;
      }
    }
    .container-select-modes{
      display: flex;
      flex-wrap: wrap;
      .select-mode, .multi-mode, .range-mode, .multiRange-mode{
        &.mpvue-calendar{
          width: 400px;
          margin: 0 auto;
          flex: none;
        }
      }
    }
    .container-view-modes{
      display: flex;
      flex-wrap: wrap;
      position: relative;
      .week-mode, .multi-mode, .range-mode, .multiRange-mode, .monthRange-mode{
        &.mpvue-calendar{
          width: 400px;
          margin: 0 auto;
          flex: none;
        }
      }
    }
  }

  .select-mode{
    &:before{
      content: 'select mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
    .vc-calendar-holiday{
      white-space: nowrap;
    }
  }

  .multi-mode{
    &:before{
      content: 'multi select mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
    .content-item-classname{
      color: #fff;
      background: #0b6cbc;
      display: inline-block;
      white-space: nowrap;
      padding: 0 3px;
      border-radius: 3px;
      transform: scale(.8);
    }
  }

  .range-mode{
    &:before{
      content: 'range select mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
  }

  .multiRange-mode{
    &:before{
      content: 'multi range select mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
  }

  .week-mode{
    &:before{
      content: 'week mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
  }

  .monthRange-mode{
    &:before{
      content: 'month range mode';
      text-align: center;
      display: block;
      color: #38778a;
      font-weight: bold;
      margin-bottom: 5px;
    }
  }

  .back-to-today{
    position: absolute;
    left: 50px;
    top: 220px;
    box-shadow: 2px 0px 2px rgb(68, 146, 123, .2);
    position: absolute;
    left: 50px;
    top: 220px;
    height: 22px;
    border: none;
    cursor: pointer;
  }

  @media screen and (max-width: 600px) {
    .container{
      width: 100%;
    }
  }
</style>


================================================
FILE: example/main.js
================================================
import { createApp } from 'vue';
import App from './App.vue'

createApp(App).mount('#app')


================================================
FILE: package.json
================================================
{
  "name": "mpvue-calendar",
  "version": "3.0.1",
  "description": "vue calendar mpvue-calendar vue-calendar",
  "main": "dist/mpvue-calendar.umd.min.js",
  "scripts": {
    "start": "vue-cli-service serve",
    "build": "npm run build:calendar && npm run build:lunar && npm run remove",
    "build:calendar": "vue-cli-service build --target lib --name mpvue-calendar './src/mpvue-calendar.vue'",
    "build:lunar": "vue-cli-service build --target lib --name lunar --dest lib './src/lunar.ts'",
    "remove": "cp ./lib/lunar.umd.min.js ./dist/lunar.js",
    "demo": "vue-cli-service build --dest demo './example/main.js'",
    "lint": "vue-cli-service lint",
    "publish": "npm publish --registry https://registry.npmjs.org"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Hzy0913/mpvue-calendar.git"
  },
  "keywords": [
    "vue",
    "calendar",
    "mpvue-calendar",
    "calendar component",
    "日历组件"
  ],
  "author": "hzy",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/Hzy0913/mpvue-calendar/issues"
  },
  "homepage": "https://github.com/Hzy0913/mpvue-calendar#readme",
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^3.0.0"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/eslint-config-airbnb": "^5.0.2",
    "@vue/eslint-config-typescript": "^5.0.2",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-vue": "^7.0.0-0",
    "less": "^3.12.2",
    "less-loader": "^7.1.0",
    "typescript": "~3.9.3"
  }
}


================================================
FILE: public/index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title>mpvue-calendar</title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


================================================
FILE: src/components/icon/icon.css
================================================
@font-face {
    font-family: "calendar-iconfont";
    src: url('data:font/truetype;charset=utf-8;base64,d09GRgABAAAAAASEAAsAAAAABuwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY7d0f0Y21hcAAAAYAAAABTAAABhmJUzs9nbHlmAAAB1AAAALcAAADIzC0F5mhlYWQAAAKMAAAALwAAADYS7IZUaGhlYQAAArwAAAAcAAAAJAfeA4RobXR4AAAC2AAAAAwAAAAMDAAAAGxvY2EAAALkAAAACAAAAAgANgBkbWF4cAAAAuwAAAAfAAAAIAEOACluYW1lAAADDAAAAUUAAAJtPlT+fXBvc3QAAARUAAAALQAAAEOUPjuMeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMj5jYm7438AQw9zA0AAUZgTJAQDdSgvleJztkMERgDAIBPdIzMOxEB8W5MvuTRsRMHbhzSwHR/IBWIDiHE4FXYjQ6akyL6yZ13zT3IXd6jYGfO6S75q7xT81fm1Z9zlZXOsl+j5BD35IDU4AeJwVzUEOwUAYBeD/mfxTEso/mkEJoYluqgtajUjYsHEOSytncROJK/QErjNMd+8lL+8jEP3eqq8uNCPiokK1L4t1ihzVCSPMEekAep3mQCh4tpVm95JWz2QGj3iyiN3LZMJBODU49IMuDxk32YifuI89X4+xqwcsHUtEjfVVWt1p5MvWNs9RY4RowNLrJ7Tq8ZKNN7wmvMp8jBc7PDpW3RPfPrISV5ss4QEO9pxcdrix/gMMIyHOAHicY2BkYGAAYmNBZtV4fpuvDNwsDCBw/dlCBQT9fzMLA3MKkMvBwAQSBQDyNAlAAHicY2BkYGBu+N/AEMPCAAJAkpEBFTADAEcJAmwEAAAABAAAAAQAAAAAAAAAADYAZHicY2BkYGBgZpBlANEMDExAzAWEDAz/wXwGAAuHATgAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicY2BigAAuBuyAmZGJkZmRhYEnKzMxryS/tDgjMS+dC8qpzC9lYAAAiPIJlAAAAA==');
}

.iconfont {
    font-family:"calendar-iconfont" !important;
    font-size:16px;
    font-style:normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.icon-arrow-right:before { content: "\e602"; }

.icon-arrow-left:before { content: "\e501"; }


================================================
FILE: src/components/swipe/declare.ts
================================================
interface SwipeInterface {
  initialSlide?: number;
  auto?: number;
  speed: number;
  timetableHeight?: number;
  loop?: boolean;
  useSwipe?: boolean;
}

interface SlideInterface {
  className?: string;
  useSwipe?: boolean;
}

type startType = {
  x: number;
  y: number;
  time: number;
}

type deltaType = {
  x: number;
  y: number;
}

export {
  SwipeInterface,
  SlideInterface,
  startType,
  deltaType,
}


================================================
FILE: src/components/swipe/index.vue
================================================
<template>
  <div
    style='margin:0 auto'
    :class="[useSwipe ? 'vc-calendar-swipe': 'vc-calendar-timetable-container']"
    ref="swipeRef"
  >
    <div
      :class="[useSwipe ? 'swipe-wrap': 'vc-calendar-timetable-wrapper']"
    >
      <slot></slot>
    </div>
  </div>
</template>

<script lang="ts">
  import { ref, onMounted } from 'vue';
  import { offloadFn } from '../utils';
  import { hasTransitions } from './utils';
  import { SwipeInterface, startType, deltaType } from './declare';
  import './style.less';

  export default {
    props: {
      initialSlide: {
        type: Number,
        default: 0
      },
      auto: {
        type: Number,
        default: 3000
      },
      speed: {
        type: Number,
        default: 300
      },
      loop: {
        type: Boolean,
        default: false
      },
      useSwipe: {
        type: Boolean,
      },
    },
    emits: ['swiperChange', 'swiperChangeEnd', 'start', 'containerChange'],
    setup(props: SwipeInterface, { emit }: any) {
      let isTransitionEnd = true;
      const { initialSlide, auto, speed, loop, useSwipe } = props;  // eslint-disable-line

      if (!useSwipe) return;

      const options = {
        initialSlide,
        auto,
        speed,
        loop,
        disableScroll: false,
        stopPropagation: false,
        callback(index: number, element: HTMLElement) {
          emit('swiperChange', index, element);
        },
        transitionEnd(index: number, element: HTMLElement) {
          emit('swiperChangeEnd', index, element);

          setTimeout(() => {
            isTransitionEnd = true;
          });
        },
      };
      const swipeRef = ref(null);
      const browser = {
        addEventListener: !!window.addEventListener,
        touch: ('ontouchstart' in window) || (window as any).DocumentTouch && document instanceof (window as any).DocumentTouch,
        transitions: hasTransitions(),
      };

      let index: number = options.initialSlide || 0;
      let container: HTMLElement;
      let element: any;
      let slides: any;
      let slidePos: any;
      let width: number;
      options.loop = options.loop ?? true;

      function setup() {
        if (!container) return;
        setTimeout(() => emit('containerChange', container));

        element = container.children[0];
        // cache slides
        slides = element.children;

        // set loop to false if only one slide
        if (slides.length < 2) options.loop = false;

        //special case if two slides
        if (browser.transitions && options.loop && slides.length < 3) {
          element.appendChild(slides[0].cloneNode(true));
          element.appendChild(element.children[1].cloneNode(true));
          slides = element.children;
        }

        // create an array to store current positions of each slide
        slidePos = new Array(slides.length);

        // determine width of each slide
        width = container.getBoundingClientRect().width || container.offsetWidth;

        element.style.width = `${slides.length * width}px`;

        // stack elements

        let pos = slides.length;
        while (pos--) {
          const slideDom = slides[pos];

          slideDom.style.width = `${width}px`;
          slideDom.setAttribute('data-index', pos);

          if (browser.transitions) {
            slideDom.style.left = `${pos * -width}px`;
            move(pos, index > pos ? -width : (index < pos ? width : 0), 0); // eslint-disable-line
          }
        }

        // reposition elements before and after index
        if (options.loop && browser.transitions) {
          move(circle(index - 1), -width, 0);
          move(circle(index + 1), width, 0);
        }

        if (!browser.transitions) {
          element.style.left = `${index * -width}px`;
        }

        container.style.visibility = 'visible';
      }

      function prev() {
        if (options.loop) {
          slide(index - 1);
        } else if (index) {
          slide(index - 1);
        }
      }

      function next() {
        if (options.loop) {
          slide(index + 1);
        } else if (index < slides.length - 1) {
          slide(index + 1);
        }
      }

      function slide(to: number, slideSpeed?: number) {
        // do nothing if already on requested slide
        if (index === to) return;

        if (browser.transitions) {
          let direction = Math.abs(index - to) / (index - to); // 1: backward, -1: forward

          // get the actual position of the slide
          if (options.loop) {
            const naturalDirection = direction;
            direction = -slidePos[circle(to)] / width;

            // if going forward but to < index, use to = slides.length + to
            // if going backward but to > index, use to = -slides.length + to
            if (direction !== naturalDirection) {
              to = -direction * slides.length + to;
            }
          }

          let diff = Math.abs(index - to) - 1;

          // move all the slides between index and to in the right direction
          while (diff--) {
            move(circle((to > index ? to : index) - diff - 1), width * direction, 0);
          }

          to = circle(to);

          move(index, width * direction, slideSpeed ?? speed);
          move(to, 0, slideSpeed ?? speed);

          if (options.loop) { // we need to get the next in place
            move(circle(to - direction), -(width * direction), 0);
          }
        } else {
          to = circle(to);
          animate(index * -width, to * -width, slideSpeed || speed as number);
          //no fallback for a circular loop if the browser does not accept transitions
        }

        index = to;
        offloadFn(options.callback && options.callback(index, slides[index]));
      }

      function circle(circleIndex: number) {
        // a simple positive modulo using slides.length
        return (slides.length + (circleIndex % slides.length)) % slides.length;
      }

      function move(moveIndex: number, dist: any, moveSpeed: number) {
        translate(moveIndex, dist, moveSpeed);
        slidePos[moveIndex] = dist;
      }

      function translate(translateIndex: number, dist: any, translateSpeed: number) {
        const slideDom = slides[translateIndex];
        const style = slideDom && slideDom.style;

        if (!style) return;

        const duration = `${translateSpeed}ms`;
        style.webkitTransitionDuration = duration;
        style.MozTransitionDuration = duration;
        style.msTransitionDuration = duration;
        style.OTransitionDuration = duration;
        style.transitionDuration = duration;

        style.webkitTransform = `translate(${dist}px, 0)translateZ(0)`;
        style.msTransform = style.MozTransform = style.OTransform = `translateX(${dist}px)`; // eslint-disable-line
      }

      function animate(from: any, to: any, animateSpeed: number) {
        // if not an animation, just reposition
        if (!animateSpeed) {
          element.style.left = `${to}px`;
          return;
        }

        const start = +new Date();
        const timer = setInterval(() => {
          const timeElap = +new Date() - start;

          if (timeElap > animateSpeed) {
            element.style.left = `${to}px`;
            options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
            clearInterval(timer);
            return;
          }

          element.style.left = (( (to - from) * (Math.floor((timeElap / animateSpeed) * 100) / 100) ) + from) + 'px'; // eslint-disable-line
        }, 4);
      }

      // setup initial vars
      let start = {} as startType;
      let delta = {} as deltaType;
      let isScrolling: boolean | undefined;

      // setup event capturing
      const events = {
        handleEvent(event: any) {
          switch (event.type) {
            case 'touchstart':
              this.start(event);
              break;
            case 'touchmove':
              this.move(event);
              break;
            case 'touchend':
              offloadFn(this.end(event));
              break;
            case 'webkitTransitionEnd':
            case 'msTransitionEnd':
            case 'oTransitionEnd':
            case 'otransitionend':
            case 'transitionend':
              offloadFn(this.transitionEnd(event));
              break;
            case 'resize':
              offloadFn(setup);
              break;
          }

          if (options.stopPropagation) {
            event.stopPropagation();
            event.preventDefault();
          }
        },
        start(event: any) {
          emit('start');

          if (!isTransitionEnd) {
            return setTimeout(() => {
              isTransitionEnd = true;
            });
          }

          const touches = event.touches[0];
          // measure start values
          start = {
            // get initial touch coords
            x: touches.pageX,
            y: touches.pageY,
            time: +new Date() // store time to determine touch duration
          };

          // used for testing first move event
          isScrolling = undefined;

          // reset delta and end measurements
          delta = {
            x: 0,
            y: 0,
          };

          // attach touchmove and touchend listeners
          element.addEventListener('touchmove', this, false);
          element.addEventListener('touchend', this, false);
        },
        move(event: any) {
          // ensure swiping with one touch and not pinching
          if (event.touches.length > 1 || event.scale && event.scale !== 1) return;

          if (options.disableScroll) event.preventDefault();

          const touches = event.touches[0];

          // measure change in x and y
          delta = {
            x: touches.pageX - start.x,
            y: touches.pageY - start.y
          };

          // determine if scrolling test has run - one time test
          if (typeof isScrolling === 'undefined') {
            isScrolling = !!(isScrolling || Math.abs(delta.x) < Math.abs(delta.y));
          }

          // if user is not trying to scroll vertically
          if (!isScrolling) {
            // prevent native scrolling
            event.preventDefault();

            // stop slideshow
            stop();

            // increase resistance if first or last slide
            if (options.loop) { // we don't add resistance at the end
              translate(circle(index - 1), delta.x + slidePos[circle(index - 1)], 0);
              translate(index, delta.x + slidePos[index], 0);
              translate(circle(index + 1), delta.x + slidePos[circle(index + 1)], 0);
            } else {
              /* eslint-disable */
              delta.x =
                delta.x /
                ( (!index && delta.x > 0               // if first slide and sliding left
                  || index == slides.length - 1        // or if last slide and sliding right
                  && delta.x < 0                       // and if sliding at all
                ) ?
                  ( Math.abs(delta.x) / width + 1 )      // determine resistance level
                  : 1 );                                 // no resistance if false
              /* eslint-disable */

              // translate 1:1
              translate(index - 1, delta.x + slidePos[index - 1], 0);
              translate(index, delta.x + slidePos[index], 0);
              translate(index + 1, delta.x + slidePos[index + 1], 0);
            }
          }
        },
        end(event: any) {
          isTransitionEnd = false;
          // measure duration
          const duration = +new Date - start.time;

          // determine if slide attempt triggers next/prev slide
          const isValidSlide =
            Number(duration) < 250               // if slide duration is less than 250ms
            && Math.abs(delta.x) > 20            // and if slide amt is greater than 20px
            || Math.abs(delta.x) > width / 2;      // or if slide amt is greater than half the width

          // determine if slide attempt is past start and end
          let isPastBounds =
            !index && delta.x > 0                            // if first slide and slide amt is greater than 0
            || index == slides.length - 1 && delta.x < 0;    // or if last slide and slide amt is less than 0

          if (options.loop) {
            isPastBounds = false;
          }

          // determine direction of swipe (true:right, false:left)
          const direction = delta.x < 0;

          // if not scrolling vertically
          if (!isScrolling) {
            if (isValidSlide && !isPastBounds) {
              if (direction) {
                if (options.loop) { // we need to get the next in this direction in place
                  move(circle(index - 1), -width, 0);
                  move(circle(index + 2), width, 0);
                } else {
                  move(index - 1, -width, 0);
                }

                move(index, slidePos[index] - width, speed);
                move(circle(index + 1), slidePos[circle(index + 1)]-width, speed);
                index = circle(index + 1);
              } else {
                if (options.loop) { // we need to get the next in this direction in place
                  move(circle(index + 1), width, 0);
                  move(circle(index - 2), -width, 0);
                } else {
                  move(index + 1, width, 0);
                }

                move(index, slidePos[index]+width, speed);
                move(circle(index - 1), slidePos[circle(index - 1)]+width, speed);
                index = circle(index - 1);
              }

              options.callback && options.callback(index, slides[index]);
            } else {
              if (options.loop) {
                move(circle(index - 1), -width, speed);
                move(index, 0, speed);
                move(circle(index + 1), width, speed);
              } else {
                move(index - 1, -width, speed);
                move(index, 0, speed);
                move(index + 1, width, speed);
              }
            }
          }

          // kill touchmove and touchend event listeners until touchstart called again
          element.removeEventListener('touchmove', events, false)
          element.removeEventListener('touchend', events, false)
        },
        transitionEnd(event: any) {
          if (parseInt(event.target.getAttribute('data-index'), 10) === index) {
            options.transitionEnd && options.transitionEnd.call(event, index, slides[index]);
          }
        }
      }

      onMounted(() => {
        container = swipeRef.value as any;
        setup();

        if (browser.addEventListener) {
          // set touchstart event on element
          if (browser.touch) {
            element.addEventListener('touchstart', events, false);
          }

          if (browser.transitions) {
            element.addEventListener('webkitTransitionEnd', events, false);
            element.addEventListener('msTransitionEnd', events, false);
            element.addEventListener('oTransitionEnd', events, false);
            element.addEventListener('otransitionend', events, false);
            element.addEventListener('transitionend', events, false);
          }

          // set resize event on window
          window.addEventListener('resize', events, false);
        }
      });

      return {
        swipeRef,
        slide,
        useSwipe,
        prev,
        next,
      }
    }
  }
</script>


================================================
FILE: src/components/swipe/slide.vue
================================================
<template>
  <div
    :class="[{[className]: !!className}, useSwipe ? 'swipe-slide': 'vc-calendar-timetable-item']"
  >
    <slot></slot>
  </div>
</template>

<script lang="ts">
  import './style.less';

  export default {
    props: {
      className: {
        type: String,
        default: ''
      },
      useSwipe: {
        type: Boolean,
      },
    },
  };
</script>


================================================
FILE: src/components/swipe/style.less
================================================
.vc-calendar-swipe {
  overflow: hidden;
  width: 100%;
  .swipe-wrap{
    width: 100%;
    overflow: hidden;
    position: relative;
    .swipe-slide{
      width: 100%;
      height: 100%;
      float: left;
      position: relative;
    }
  }
}


================================================
FILE: src/components/swipe/utils.ts
================================================
export function hasTransitions() {
  const element = document.createElement('div');
  const property = ['transitionProperty', 'WebkitTransition', 'MozTransition', 'OTransition', 'msTransition'];

  for (let value of property) {// eslint-disable-line
    if ((element.style as any)[value] !== undefined) return true;
  }

  return false;
}


================================================
FILE: src/components/timetable/computed.ts
================================================
import { computedNextMonth, computedPrevMonth, getToday, computedNextYear } from '../utils';

function date2ymd(date: string): number[] {
  const [y, m, d] = date.split('-');
  return [Number(y), Number(m), Number(d)];
}

function date2timeStamp(date: string): number {
  const [y, m, d] = date2ymd(date);
  return +new Date(y, m - 1, d);
}

function getLunarInfo(y: string, m: string, d: string, lunar: any) {
  const date = `${y}-${m}-${d}`;
  if (!lunar) {
    return { date };
  }

  const lunarInfo = lunar.solar2lunar(y, m, d) as any;
  const { Term, lMonth, lDay, lYear } = lunarInfo || {};
  const { lunarHoliday, gregorianHoliday } = lunar || {};
  const lunarValue = lunarInfo.IDayCn;
  const yearEve = lMonth === 12 && lDay === lunar.monthDays(lYear, 12) ? '除夕' : undefined;

  const lunarInfoObj = {
    date,
    lunar: Term || lunarValue,
    gregorianHoliday: gregorianHoliday?.[`${m}-${d}`],
    lunarHoliday: lunarHoliday?.[`${lMonth}-${lDay}`] || yearEve,
    isTerm: !!yearEve || lunarInfo.isTerm
  };
  return lunarInfoObj;
}

const setRemark = (function () {
  let remarksInfo: any = {};

  return function () {
    return {
      update(remarks: any = {}) {
        remarksInfo = remarks;
      },
      getRemark(date: string) {
        if (remarksInfo) {
          return {
            remark: remarksInfo[date]
          };
        }
      }
    };
  };
}());

function computedPrevDay(year: string, month: string, day: string | number): string {
  if ((Number(day) - 1) === 0) {
    const prevMonth = computedPrevMonth(month);
    if (prevMonth === 12) { //prev year
      const prevYear = Number(year) - 1;
      const prevDay = new Date(prevYear, prevMonth - 2, 0).getDate();
      return `${prevYear}-${prevMonth}-${prevDay}`;
    } else { //current year and prev month
      const prevDay = new Date(Number(year), prevMonth, 0).getDate();
      return `${year}-${prevMonth}-${prevDay}`;
    }
  } else {
    return `${year}-${month}-${Number(day) - 1}`;
  }
}

function computedNextDay(year: string, month: string, day: string): string {
  const lastDateOfCurrentMonth = new Date(Number(year), Number(month), 0).getDate(); //last date of current month

  if ((Number(day) + 1) > lastDateOfCurrentMonth) {
    const nextMonth = computedNextMonth(month);
    if (nextMonth === 1) { //next year
      const nextYear = computedNextYear(year, month);
      const nextDay = new Date(nextYear, 0, 1).getDate();
      return `${nextYear}-1-${nextDay}`;
    } else { //current year and next month
      const nextDay = new Date(Number(year), nextMonth - 1, 1).getDate();
      return `${year}-${nextMonth}-${nextDay}`;
    }
  } else {
    return `${year}-${month}-${Number(day) + 1}`;
  }
}

type rangeOptionType = {
  date: string;
  isWeekMode: boolean;
  rangeDate: [string, string];
  getLunarInfo: (year: number, month: number, day: number) => any;
  getEvents: (year: number, month: number, day: number) => any;
}

function isCurrentMonthToday(date: string) {
  const todayString = getToday();
  return todayString === date;
}

function rangeOption({selectDate, date}: any) {
  const { start, end } = selectDate;
  if (start === date) {
    const notCompleteClassName = end ? '' : ' selected-range-not-complete';
    return 'vc-day-selected selected-range-start' + notCompleteClassName;
  }
  if (end === date) {
    return 'vc-day-selected selected-range-end';
  }

  if (start && end && date) {
    const startTimeStamp: number = date2timeStamp(start);
    const endTimeStamp: number = date2timeStamp(end);
    const currentTimeStamp: number = date2timeStamp(date);

    if (startTimeStamp < currentTimeStamp && currentTimeStamp < endTimeStamp) {
      return 'vc-day-selected selected-range-includes';
    }
  }
}

function multiRangeOption({selectDate = [], date}: any) {
  let className;
  selectDate.some((selectItem: any) => {
    const { start, end } = selectItem;
    if (start === date) {
      const notCompleteClassName = end ? '' : ' selected-range-not-complete';
      className = 'vc-day-selected selected-range-start' + notCompleteClassName;
      return true;
    }
    if (end === date) {
      className = 'vc-day-selected selected-range-end';
      return true;
    }

    if (start && end && date) {
      const startTimeStamp: number = date2timeStamp(start);
      const endTimeStamp: number = date2timeStamp(end);
      const currentTimeStamp: number = date2timeStamp(date);

      if (startTimeStamp < currentTimeStamp && currentTimeStamp < endTimeStamp) {
        className = 'vc-day-selected selected-range-includes';
        return true;
      }
    }
  });
  return className;
}

function multiOption({selectDate, date}: any) {
  return selectDate.includes(date) ? 'vc-day-selected' : undefined;
}

function selectOption({date, selectDate}: any) {
  return selectDate === date ? 'vc-day-selected' : undefined;
}

type selectOptionType = {
  date: string;
  playload: any;
  isWeekMode: boolean;
  weekSwitch: boolean;
  selectDate: string;
  getLunarInfo: (year: number, month: number, day: number) => any;
  getEvents: (year: number, month: number, day: number) => any;
}

const disabledDate = (function () {
  let disabledDates: any = {};
  return function () {
    return {
      update(disabled: any[] = []) {
        disabledDates = disabled.reduce((previousValue, currentValue) => {
          previousValue[currentValue] = true;
          return previousValue;
        }, {});

        return disabledDate;
      },
      isDisabled(date: string) {
        return !!disabledDates[date];
      }
    };
  };
}());

const setTileContent = (function () {
  let tileContentInfo: any = {};

  return function () {
    return {
      update(tileContent: any) {
        tileContentInfo = tileContent || [];
      },
      getTileContent(date: string) {
        return {
          tileContent: (tileContentInfo || {})[date]
        };
      }
    };
  };
}());

export {
  getToday,
  rangeOption,
  multiOption,
  multiRangeOption,
  selectOption,
  disabledDate,
  setRemark,
  setTileContent,
  getLunarInfo,
  computedPrevMonth,
  computedNextYear,
  isCurrentMonthToday,
  date2timeStamp,
  computedNextDay,
  computedPrevDay,
  date2ymd,
};


================================================
FILE: src/components/timetable/controller.ts
================================================
import { date2timeStamp } from './computed';

function singleSelect(selectDate: string, date: string) {
  return date;
}

function multiSelect(selectDate: string[] = [], date: string) {
  const index = selectDate.indexOf(date);
  if (~index) {
    selectDate.splice(index, 1);
    return selectDate;
  }

  selectDate.push(date);
  return selectDate;
}

function rangeSelect(selectDate: { start?: string; end?: string }, date: string) {
  const { start, end } = selectDate;

  if (start && end) {
    return {
      start: date,
      end: ''
    };
  }

  if (start) {
    if (start === date) {
      return {};
    }

    if (date2timeStamp(start) > date2timeStamp(date)) {
      return {
        start: date,
        end: start
      };
    }

    return {
      start,
      end: date,
    };
  }

  return {
    start: date,
    end,
  };
}

function multiRange(selectDates: { start?: string; end?: string }[], date: string) {
  const selects = [...selectDates];
  let deleteIndex;

  if (!selects.length) {
    selects.push({start: date});
    return selects;
  }

  const searchResult = selects.some((selectItem: any, index: number) => {
    const { start, end } = selectItem;
    if (start && end) {
      if (date2timeStamp(start) < date2timeStamp(date) && date2timeStamp(date) < date2timeStamp(end)) {
        return true;
      }

      if (start === date || end === date) {
        deleteIndex = index;
        return true;
      }
    } else if (start) {
      const selectItemDate: { start: string; end: string } = { start: '', end: '' };
      if (start === date) {
        deleteIndex = index;
        return true;
      }
      if (date2timeStamp(start) > date2timeStamp(date)) {
        selectItemDate.start = date;
        selectItemDate.end = start;
      } else {
        selectItemDate.start = start;
        selectItemDate.end = date;
      }

      let isIncludeOtherRange;
      if (selects.length > 1) {
        isIncludeOtherRange = selects.some((item: any, i: number) => {
          if (i === selects.length - 1) return;
          const { start: prevItemDate } = item;

          if (date2timeStamp(selectItemDate.start) < date2timeStamp(prevItemDate) && date2timeStamp(prevItemDate) < date2timeStamp(selectItemDate.end)) {
            return true;
          }
        });
      }

      if (!isIncludeOtherRange) {
        selectItem.start = selectItemDate.start;
        selectItem.end = selectItemDate.end;
      }

      return true;
    }
  });

  if (typeof deleteIndex === 'number') {
    selects.splice(deleteIndex, 1);
  } else if (!searchResult) {
    selects.push({start: date});
  }
  return selects;
}

export {
  singleSelect,
  multiSelect,
  rangeSelect,
  multiRange,
};


================================================
FILE: src/components/timetable/declare.ts
================================================
interface TimeTableInterface {
  monFirst?: boolean;
  format?: (year: string | number, month: string | number) => any[];
  weeks: string[];
  tableMode: 'month' | 'week' | 'monthRange';
  lunar?: any;
  useSwipe: boolean;
  tableIndex?: number;
  timestamp?: number;
  year?: string | number
  month?: string | number
  day?: string | number
  begin?: string;
  end?: string;
  completion: boolean;
  holidays?: {[key: string]: string};
  tileContent: {className?: string, tileContent?: string}[];
  remarks: {[key: string]: string};
  selectMode: 'select' | 'multi' | 'range' | 'multiRange';
  selectDate?: string | string[] | {start?: string, end?: string} | {start?: string, end?: string}[]
  disabled: string[];
};

interface SwipeInterface {
  initialSlide?: number;
  auto?: number;
  speed: number;
  loop?: boolean;
}

interface SlideInterface {
  className?: string;
}

type startType = {
  x: number;
  y: number;
  time: number;
}

type deltaType = {
  x: number;
  y: number;
}

export {
  SwipeInterface,
  SlideInterface,
  startType,
  deltaType,
  TimeTableInterface,
}


================================================
FILE: src/components/timetable/index.vue
================================================
<template>
  <div
    style='margin:0 auto'
    class="vc-calendar-timetable"
    :class="{
      'vc-calendar-timetable-prev': tableIndex === 0,
      'vc-calendar-timetable-current': tableIndex === 1,
      'vc-calendar-timetable-next': tableIndex === 2,
    }"
  >
    <div class="vc-calendar-timetable-wrap">
        <div
          :class="['vc-calendar-body', `mc-range-mode-${tableMode}`]"
        >
          <div class="vc-calendar-rang-head" v-if="tableMode === 'monthRange'">
            <div class="vc-calendar-rang-year-month-box">
              <span className="vc-calendar-rang-head-year">{{formatRangeMonth[0]}}</span>
              <span className="vc-calendar-rang-head-month">{{formatRangeMonth[1]}}</span>
            </div>
            <div class="vc-calendar-rang-week-box">
              <span v-for="(week, index) in weeks" :key="index">{{week}}</span>
            </div>
          </div>
          <div className="vc-calendar-content">
            <div v-for="(days,k1) in monthRender.value" :key="k1" class="vc-calendar-row" >
              <div
                v-for="(child,k2) in days"
                :key="k2"
                class="vc-calendar-day"
                :class="[
                  {
                    'vc-calendar-today': child.isToday,
                    'vc-calendar-dayoff': k2 === (monFirst ? 5 : 0) || k2 === 6,
                    'vc-calendar-disabled': child.disabled,
                    'vc-calendar-prev-month-day': child.prevMonthDay,
                    'vc-calendar-next-month-day': child.nextMonthDay,
                    'vc-calendar-row-first': k2 === 0,
                    'vc-calendar-row-last': k2 === 6,
                    'month-last-date': child.lastDay, 'month-first-date': 1 === child.day,
                    'mc-last-month': child.lastMonth,
                    'mc-next-month': child.nextMonth
                  },
                  child.className,
                  child.selectedClassName,
                  child.rangeClassName
                ]"
                @click="select(k1, k2, child, $event, index)"
              >
                <div class="vc-calendar-day-container">
                  <span :class="['vc-calendar-date']" >{{child.day}}</span>
                  <div :class="['vc-calendar-slot-element', child.tileContent.className]" v-if="child.tileContent">{{child.tileContent.content}}</div>
                  <div class="vc-calendar-remark-text" v-if="child.remark">{{child.remark}}</div>
                  <div
                    class="vc-calendar-almanac"
                    :class="{'vc-calendar-holiday': child.holiday, 'vc-calendar-isTerm': child.isTerm, 'isLunarFestival': child.isAlmanac || child.isLunarFestival, 'isGregorianFestival': child.isGregorianFestival}"
                  >
                    {{child.holiday || child.lunarHoliday || child.gregorianHoliday || child.lunar}}
                  </div>
                </div>
              </div>
            </div>
            <div v-if="backgroundText" className="vc-calendar-month-background-text">{{month}}</div>
          </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import { ref, reactive, watch, toRefs } from 'vue';
  import { disabledDate, computedPrevDay, date2ymd, selectOption, multiOption, rangeOption,
    multiRangeOption, getLunarInfo, isCurrentMonthToday, setTileContent, setRemark,
    computedPrevMonth, computedNextYear, computedNextDay
  } from './computed';
  import { rangeSelect, singleSelect, multiSelect, multiRange } from './controller';
  import { computedNextMonth, computedPrevYear } from '../utils';
  import { TimeTableInterface } from './declare';
  import './style.less';

  const disabledDateHandle = disabledDate();
  const setRemarkHandle = setRemark();
  const setTileContentHandle = setTileContent();

  export default {
    props: {
      format: {
        type: Function,
      },
      weeks: {
        type: Array,
      },
      tableMode: {
        type: String,
        default: 'month'
      },
      language: {
        type: String,
      },
      year: {
        type: [String, Number],
      },
      month: {
        type: [String, Number],
      },
      day: {
        type: [String, Number],
      },
      lunar: {
        type: Object,
      },
      monFirst: {
        type: Boolean,
        default: true
      },
      useSwipe: {
        type: Boolean,
        default: true
      },
      backgroundText: {
        type: Boolean,
        default: true
      },
      tableIndex: {
        type: Number,
      },
      begin: {
        type: String,
      },
      end: {
        type: String,
      },
      completion: {
        type: Boolean,
        default: false
      },
      holidays: {
        type: Object,
        default() {
          return {};
        }
      },
      tileContent: {
        type: Object,
        default() {
          return {};
        }
      },
      remarks: {
        type: Object,
        default() {
          return {};
        }
      },
      timestamp: {
        type: Number,
      },
      selectMode: {
        type: String,
      },
      disabled: {
        type: Array,
      },
      selectDate: {
        type: [String, Array, Object],
      },
    },
    emits: ['onSelect', 'onMonthChange'],
    setup(props: TimeTableInterface, { emit }: any) {
      const { year, month, selectMode, tableMode: propsTableMode, monFirst, begin: propsBegin,
        end: propsEnd, completion: propsCompletion, day, tileContent, disabled, remarks, holidays,
        selectDate,
      } = toRefs(props);

      const tableMode = ref(propsTableMode);
      const begin = ref(propsBegin);
      const end = ref(propsEnd);
      const completion = ref(propsTableMode.value === 'week' || propsCompletion.value);
      const formatRangeMonth = ref([year?.value, month?.value]);
      disabledDateHandle.update(disabled.value);
      setRemarkHandle.update(remarks.value);
      setTileContentHandle.update(tileContent.value);

      function selectComputed(date: string) {
        switch (selectMode.value) {
          case 'range':
            return rangeOption({selectDate: selectDate?.value, date} as any);
          case 'multiRange':
            return multiRangeOption({selectDate: selectDate?.value, date} as any);
          case 'multi':
            return multiOption({selectDate: selectDate?.value, date} as any);
          case 'select':
            return selectOption({selectDate: selectDate?.value, date});
        }
      }

      function select(k1: any, k2: any, child: any) {
        const { date, prevMonthDay, nextMonthDay } = child;

        if (prevMonthDay || nextMonthDay) {
          if (propsTableMode.value === 'monthRange') return;

          return emit('onMonthChange', {prevMonthDay, nextMonthDay});
        }

        let selectValue;
        switch (selectMode.value) {
          case 'select':
            selectValue = singleSelect(selectDate?.value as string, date);
            break;
          case 'multi':
            selectValue = multiSelect(selectDate?.value as string[], date);
            break;
          case 'range':
            selectValue = rangeSelect(selectDate?.value as {start?: string; end?: string}, date);
            break;
          case 'multiRange':
            selectValue = multiRange(selectDate?.value as {start?: string; end?: string}[], date);
            break;
        }

        emit('onSelect', selectValue);
      }

      function setDisabledDate(options: { year: string; month: string; i: string; date: string }) {
        const { year: disYear, month: disMonth, i, date } = options;
        const dateTimestamp = +new Date(Number(disYear), Number(disMonth) - 1, Number(i));
        const disabledOptions = {} as { disabled: boolean };
        if (begin.value) {
          const [beginY, beginM, beginD] = begin.value.split('-');
          const beginTimestamp = +new Date(Number(beginY), Number(beginM) - 1, Number(beginD));
          if (beginTimestamp > dateTimestamp) {
            disabledOptions.disabled = true;
          }
        }

        if (end.value) {
          const [endY, endM, endD] = end.value.split('-');
          const endTimestamp = +new Date(Number(endY), Number(endM) - 1, Number(endD));
          if (endTimestamp < dateTimestamp) {
            disabledOptions.disabled = true;
          }
        }

        if (disabledDateHandle.isDisabled(date)) {
          disabledOptions.disabled = true;
        }

        return disabledOptions;
      }

      function getToday(date: string) {
        return {
          isToday: isCurrentMonthToday(date),
        };
      }

      function renderOption({year, month, i, playload}: any) { // eslint-disable-line
        const date = `${year}-${month}-${i}`;
        const modeOptions = {
          selectedClassName: selectComputed(date)
        };
        const options = {
          day: i,
          holiday: holidays?.value?.[`${month}-${i}`],
          ...getLunarInfo(year, month, i, props.lunar),
          ...setRemarkHandle.getRemark(date),
          ...setTileContentHandle.getTileContent(date),
          ...setDisabledDate({year, month, i, date}),
          ...getToday(date),
        };

        return Object.assign(options, modeOptions);
      }

      const monthRender = reactive({value: render({year: year?.value, month: month?.value, day: day?.value})});

      function render({year, month, day, renderer, payload}: any) { // eslint-disable-line
        completion.value = propsTableMode.value === 'week' || propsCompletion.value;
        const firstDayOfMonth = new Date(year, month - 1, 1).getDay(); //what day is the first day of the month
        const lastDateOfCurrentMonth = new Date(year, month, 0).getDate(); //last date of current month
        const lastDateOfLastMonth = new Date(year, month - 1, 0).getDate(); // last day Of last month

        let firstWeekDayCompletionCount;
        if (monFirst?.value) {
          firstWeekDayCompletionCount = (firstDayOfMonth === 0 ? 7 : firstDayOfMonth) - 1;
        } else {
          firstWeekDayCompletionCount = firstDayOfMonth === 0 ? 0 : firstDayOfMonth;
        }

        const firstWeekDayCount = 7 - firstWeekDayCompletionCount;
        const temp: any[] = [];

        if (tableMode.value === 'week') {
          let dayOfCurrentWeek = new Date(year, month - 1, day).getDay() - (monFirst?.value ? 1: 0); // what day is the current week
          if (dayOfCurrentWeek === -1) { // when current day is sunday and use monFirst mode
            dayOfCurrentWeek = 6;
          }

          temp.push(renderOption({year, month, i: day}));

          let countDate = [year, month, day];
          for (let i = 0; i < dayOfCurrentWeek; i++) {
            const [y, m, d] = countDate;
            const prevDate = computedPrevDay(y, m, d);
            countDate = date2ymd(prevDate);
            temp.unshift(renderOption({year: countDate[0], month: countDate[1], i: countDate[2]}));
          }

          countDate = [year, month, day];
          for (let i = dayOfCurrentWeek; i < 6; i++) {
            const [y, m, d] = countDate;
            const nextDate = computedNextDay(y, m, d);
            countDate = date2ymd(nextDate);
            temp.push(renderOption({year: countDate[0], month: countDate[1], i: countDate[2]}));
          }

          return [temp];
        }

        let line = 0;
        for (let i = 1; i <= lastDateOfCurrentMonth; i++) {
          if (!Array.isArray(temp[line])) temp[line] = [];

          if (line === 0) {
            temp[line].push(renderOption({year, month, i}));
            if (temp[line].length === firstWeekDayCount) {
              line += 1;
            }
          } else {
            temp[line].push(renderOption({year, month, i}));
            if (temp[line].length === 7) line += 1;
          }
        }

        if (completion.value) {
          //completion prev month
          let completionCounting = 0;
          const [prevYear, prevMonth] = [computedPrevYear(year, month), computedPrevMonth(month)];
          while (firstWeekDayCompletionCount !== completionCounting) {
            temp[0].unshift(Object.assign(
              renderOption({year: prevYear, month: prevMonth, i: lastDateOfLastMonth - completionCounting}),
              {prevMonthDay: true, disabled: true},
            ));
            completionCounting += 1;
          }

          //completion next month
          const completionWeeksCount = (5 - line) > 0 ? 5 - line : 0;
          const [nextYear, nextMonth] = [computedNextYear(year, month), computedNextMonth(month)];

          if (!Array.isArray(temp[line])) temp[line] = [];

          const lastWeekDayCompletionCount = 7 - temp[line].length + (7 * completionWeeksCount);
          let completionCountingNext = 0;

          while (lastWeekDayCompletionCount !== completionCountingNext) {
            if (temp[line].length === 7) {
              line += 1;
              temp[line] = [];
              continue;
            }

            completionCountingNext += 1;
            temp[line].push(Object.assign(
              renderOption({year: nextYear, month: nextMonth, i: completionCountingNext}),
              {nextMonthDay: true, disabled: true},
            ));
          }
        } else if (firstWeekDayCompletionCount) {
          temp[0].unshift(...Array.from({length: firstWeekDayCompletionCount}).fill('PLACEHOLDER'));
        }

        return temp;
      }

      function refreshRender() {
        monthRender.value = render({year: year?.value, month: month?.value, day: day?.value});
      }

      function formatYearAndMonth() {
        const { format } = props;
        if (format) {
          formatRangeMonth.value = format(year?.value as any, month?.value as any);
        } else {
          formatRangeMonth.value = [`${year?.value}-`, month?.value];
        }
      }

      formatYearAndMonth();

      watch(() => props.timestamp, () => {
        disabledDateHandle.update(disabled.value);
        setRemarkHandle.update(remarks.value);
        setTileContentHandle.update(tileContent.value);
        refreshRender();
      });

      watch(() => [props.monFirst, props.completion, props.year, props.month, props.day], () => {
        refreshRender();
      });

      watch(() => props.month, () => {
        formatYearAndMonth();
      });

      return {
        select,
        monthRender,
        selectComputed,
        refreshRender,
        formatRangeMonth,
      };
    }
  };
</script>


================================================
FILE: src/components/timetable/style.less
================================================
.vc-calendar-timetable{
  background: #fff;
  padding-bottom: 10px;
  .vc-calendar-timetable-wrap{
    .vc-calendar-body{
      .vc-calendar-row{
        overflow: hidden;
        .vc-calendar-day{
          -webkit-tap-highlight-color:transparent;
          width: 14.285714285714285%;
          position: relative;
          float: left;
          color: #7C86A2;
          cursor: pointer;
          padding: 1%;
          box-sizing: border-box;
          &:before{
            content: '';
            padding-top: 100%;
            display: block;
            position: relative;
            z-index: 1;
          }
          &.selected-range-includes{
            &:after{
              content: '';
              display: block;
              position: absolute;
              width: 100%;
              height: 86%;
              background: #E8F4FF ;
              top: 7%;
              z-index: 1;
            }
          }
          &.selected-range-start{
            &:after{
              content: '';
              display: block;
              position: absolute;
              width: 100%;
              height: 86%;
              background: #E8F4FF ;
              top: 7%;
              z-index: 0;
              left: 50%;
            }
            &.selected-range-not-complete, &.vc-calendar-row-last{
              &:after{
                content: none;
              }
            }
          }
          .vc-calendar-day-container{
            position: absolute;
            left: 0;
            right: 0;
            top: 0;
            bottom: 0;
            padding: 1%;
            z-index: 3;
          }
          &.vc-day-selected{
            &:before{
              background: #60CDFF;
              overflow: hidden;
              border-radius: 4%;
            }
            .vc-calendar-date{
              color: #fff;
            }
            &.selected-range-includes{
              .vc-calendar-date{
                color: #7C86A2;
              }
            }
          }
          &.vc-calendar-dayoff{
            .vc-calendar-date {
              color: #FF6260;
            }
          }
          .vc-calendar-date{
            width: 100%;
            display: block;
            position: absolute;
            text-align: center;
            top: 50%;
            transform: translateY(-50%);
          }
          .vc-calendar-almanac{
            display: block;
            position: absolute;
            bottom: 8%;
            font-size: 12px;
            width: 100%;
            text-align: center;
            &.vc-calendar-holiday, &.vc-calendar-isTerm{
              color: #FF6260;
            }
          }
          .vc-calendar-slot-element{
            font-size: 12px;
            position: absolute;
            top: 0;
          }
          .vc-calendar-text{
            font-size: 12px;
            position: absolute;
            bottom: 0;
            width: 100%;
            display: block;
            text-align: center;
          }
          &.vc-calendar-disabled{
            pointer-events: none;
            .vc-calendar-day-container{
              .vc-calendar-date, .vc-calendar-almanac{
                color: #ccc;
              }
            }
            &.vc-calendar-prev-month-day, &.vc-calendar-next-month-day{
              pointer-events: inherit;
            }
          }
        }
      }
      .vc-calendar-rang-head{
        .vc-calendar-rang-week-box{
          width: 100%;
          display: flex;
          align-items: center;
          height: 30px;
          margin-top: 10px;
          span{
            flex: 1;
            text-align: center;
            color: #6B7897;
          }
        }
        .vc-calendar-rang-year-month-box{
          background: #fafafa;
          padding: 10px 0;
          color: #646464;
          text-align: center;
          font-size: 16px;
        }
      }
      .vc-calendar-content{
        position: relative;
        .vc-calendar-month-background-text{
          position: absolute;
          top: 50%;
          text-align: center;
          width: 100%;
          font-size: 200px;
          transform: translateY(-50%);
          font-weight: 900;
          color: #f4f5f9;
        }
      }
    }
  }
}


================================================
FILE: src/components/tools/index.vue
================================================
<template>
  <div
    class="vc-calendar-tools"
    ref="toolsRef"
    v-if="tableMode !== 'monthRange'"
  >
    <div
      class="vc-calendar-tools-container"
    >
      <div class="vc-calendar-prev" @click="prev">
        <img :src="arrowLeft" v-if="arrowLeft" />
        <i class="iconfont icon-arrow-left" v-else />
      </div>
      <div class="vc-calendar-next" @click="next">
        <img :src="arrowRight" v-if="arrowRight" />
        <i class="iconfont icon-arrow-right" v-else />
      </div>
      <div class="vc-calendar-info" @click.stop="switchDate">
        <div class="vc-calendar-year">{{formatText[0]}}</div>
        <div class="vc-calendar-month">{{formatText[1]}}</div>
      </div>
    </div>
    <div
      class="vc-calendar-picker"
      :class="{'vc-picker-show': pickerVisible}"
      :style="{height: toolsStyle.timetableHeight + toolsStyle.weekHeadHeight + toolsStyle.toolsHeight + 'px', paddingTop: toolsStyle.toolsHeight + 'px'}"
    >
      <div :class="['vc-calendar-months', {'vc-calendar-week-switch-months': weekSwitch}]">
        <span
          v-for="(m, i) in months"
          :key="m"
          @click.stop="selectMonth(i + 1)"
          :class="{'piecker-month-active': (i + 1) === month}"
        >
          <i>{{m}}</i>
        </span>
      </div>
      <div class="vc-calendar-years">
        <span
          v-for="y in years"
          :key="y"
          @click.stop="selectYearHandle(y)"
          :class="{'piecker-year-active': y === year}"
        >
          <i>{{y}}</i>
        </span>
      </div>
    </div>
    <div class="vc-calendar-week-head">
      <div class="vc-calendar-week-head-container">
        <div v-for="(weekItem, index) in weeks" :key="index" class="vc-calendar-week-item">{{weekItem}}</div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import { ref, reactive, watch, toRefs } from 'vue';
  import './style.less';
  import '../icon/icon.css';

  export default {
    props: {
      months: {
        type: Array,
      },
      weeks: {
        type: Array,
      },
      weekSwitch: {
        type: Boolean,
        default: false
      },
      monFirst: {
        type: Boolean,
        default: false
      },
      format: {
        type: Function,
      },
      tableMode: {
        type: String,
        default: 'month'
      },
      arrowLeft: {
        type: String,
        default: ''
      },
      arrowRight: {
        type: String,
        default: ''
      },
      year: {
        type: [String, Number],
      },
      month: {
        type: [String, Number],
      },
      timetableHeight: {
        type: Number,
      }
    },
    emits: ['next', 'prev', 'selectMonth', 'selectYear', 'onMonthChange'],
    setup(props: any, { emit }: any) {
      const { year, month, tableMode } = toRefs(props);
      const pickerVisible = ref(false);
      const toolsStyle = reactive({
        toolsContainerHeight: 0,
        toolsHeight: 0,
        timetableHeight: 0,
        weekHeadHeight: 0,
      });
      const toolsRef = ref();
      const formatText = ref([] as string[]);
      const years = ref(createYears(year.value));

      function selectMonth(selectedMonth: number) {
        pickerVisible.value = false;
        emit('selectMonth', year.value, selectedMonth);
        emit('onMonthChange', year.value, selectedMonth);
      }

      function selectYearHandle(selectedYear: number) {
        pickerVisible.value = false;
        years.value = createYears(selectedYear);
        emit('selectYear', selectedYear, month.value);
        emit('onMonthChange', selectedYear, month.value);
      }

      function next() {
        emit('next', year.value, month.value);
      }

      function prev() {
        emit('prev', year.value, month.value);
      }

      function switchDate() {
        if (tableMode.value === 'week') return;

        toolsStyle.toolsContainerHeight = toolsRef.value.clientHeight;
        toolsStyle.toolsHeight = toolsRef.value.querySelector('.vc-calendar-tools-container').clientHeight;
        toolsStyle.weekHeadHeight = toolsRef.value.querySelector('.vc-calendar-week-head').clientHeight;
        toolsStyle.timetableHeight = toolsRef.value.parentNode.querySelector('.vc-calendar-timetable-current').clientHeight;
        pickerVisible.value = !pickerVisible.value;
      }

      function formatYearAndMonth() {
        const { format } = props;
        if (format) {
          return formatText.value = format(year.value, month.value);
        }
        formatText.value = [`${year.value}-`, `${month.value}`];
      }

      formatYearAndMonth();

      watch(year, () => {
        formatYearAndMonth();
      });

      watch(month, () => {
        formatYearAndMonth();
        if (pickerVisible.value) {
          setTimeout(() => {
            toolsStyle.timetableHeight = toolsRef.value.parentNode.querySelector('.vc-calendar-timetable-current').clientHeight;
          });
        }

        if (!years.value.includes(year.value)) {
          years.value = createYears(year.value);
        }
      });

      function createYears(creatorYear: number) {
        const yearRange = [];
        for (let i = creatorYear - 7; i < creatorYear + 8; i++) {
          yearRange.push(i);
        }
        return yearRange;
      }

      return {
        next,
        prev,
        years,
        selectMonth,
        selectYearHandle,
        pickerVisible,
        switchDate,
        toolsStyle,
        toolsRef,
        formatText,
      };
    }
  };
</script>


================================================
FILE: src/components/tools/style.less
================================================
.vc-calendar-tools {
  margin: auto;
  width: 100%;
  background: #fff;
  user-select: none;
  position: relative;
  .vc-calendar-tools-container {
    height: 40px;
    font-size: 20px;
    line-height: 40px;
    color: #5e7a88;
    -webkit-box-shadow: 0 4px 8px rgba(25, 47, 89, .1);
    box-shadow: 0 4px 8px rgba(25, 47, 89, .1);
    border-top: 1px solid hsla(0, 0%, 78.4%, .1);
    z-index: 5;
    position: relative;
  }
  .vc-calendar-week-head{
    .vc-calendar-week-head-container{
      width: 100%;
      display: flex;
      align-items: center;
      height: 30px;
      padding-top: 10px;
      .vc-calendar-week-item{
        flex: 1;
        text-align: center;
        color: #6B7897;
      }
    }
  }
  .vc-calendar-prev, .vc-calendar-next {
    cursor: pointer;
    img {
      width: 34px;
      height: 34px;
    }
  }
  .vc-calendar-prev {
    width: 14.28571429%;
    float: left;
    text-align: center;
  }
  .vc-calendar-next {
    width: 14.28571429%;
    float: right;
    text-align: center;
  }
  .vc-calendar-info {
    font-size: 16px;
    width: 50%;
    margin: 0 auto;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    .vc-calendar-month {
      color: #5e7a88;
    }
    .vc-calendar-year {
      color: #5e7a88;
    }
  }
  .vc-calendar-picker{
    color: #5e7a88;
    opacity: 0;
    position: absolute;
    top: 0px;
    background: #fff;
    width: 100%;
    height: 100%;
    z-index: 4;
    display: none;
    box-sizing: border-box;
    >div{
      height: 50%;
    }
    &.vc-picker-show{
      animation-name: pickerShow;
      animation-duration: .3s;
      animation-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
      animation-fill-mode: both;
      display: block;
      @keyframes pickerShow {
        from {
          top: -30%;
          opacity: 0;
        }
        to {
          top: 0;
          opacity: 1;
        }
      }
    }
    .vc-calendar-months{
      display: flex;
      flex-wrap: wrap;
      padding: 10px 5px;
      position: relative;
      box-sizing: border-box;
      .piecker-month-active{
        i{
          background: #60CDFF;
          color: #fff;
          border-radius: 5px;
          box-shadow: 2px 2px 2px rgba(0, 81, 118, .1);
        }
      }
      &:after {
        content: '';
        display: block;
        width: 94%;
        height: 1px;
        background-color: #eee;
        position: absolute;
        bottom: 0;
        left: 3%;
      }
      span{
        flex-basis: 25%;
        text-align: center;
        font-size: 16px;
        position: relative;
        i{
          position: absolute;
          font-style: normal;
          text-align: center;
          word-break: keep-all;
          left: 50%;
          top: 50%;
          transform: translate3d(-50%, -50%, 0);
          padding: 5% 22%;
          cursor: pointer;
        }
      }
    }
    .vc-calendar-years{
      display: flex;
      flex-wrap: wrap;
      padding: 10px 5px;
      box-sizing: border-box;
      .piecker-year-active{
        i{
          background: #60CDFF;
          color: #fff;
          border-radius: 5px;
          box-shadow: 2px 2px 2px rgba(0, 81, 118, .1);
        }
      }
      span{
        flex-basis: 20%;
        text-align: center;
        margin-bottom: 10px;
        font-size: 16px;
        position: relative;
        i{
          position: absolute;
          font-style: normal;
          text-align: center;
          word-break: keep-all;
          left: 50%;
          top: 50%;
          transform: translate3d(-50%, -50%, 0);
          padding: 5% 22%;
          cursor: pointer;
        }
      }
    }
  }
}


================================================
FILE: src/components/utils/index.ts
================================================
function noop() {} // eslint-disable-line

function computedNextYear(year: string | number, month: string | number): number {
  if ((Number(month) + 1) > 12) {
    return Number(year) + 1;
  }
  return Number(year);
}

function computedPrevYear(year: string | number, month: string | number): number {
  if ((Number(month) - 1 - 1) < 0) {
    return Number(year) - 1;
  }

  return +year;
}

function date2ymd(date: string): number[] {
  const [y, m, d] = date.split('-');
  return [Number(y), Number(m), Number(d)];
}

function offloadFn(fn: any) {
  setTimeout(fn || noop, 0);
}

function language(): string {
  return (navigator.language || (navigator as any).browserLanguage).toLowerCase();
}

function isZh(languageValue?: string) {
  if (languageValue) {
    return languageValue === 'cn';
  }
  return language() === 'zh-cn';
}

const enWeeks = ['Sun', 'Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat'];
const zhWeeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
const enMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
const zhMonths = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];

function getWeeks(languageParam: string) {
  return isZh(languageParam) ? zhWeeks : enWeeks;
}

function getMonths(languageParam: string | undefined) {
  return isZh(languageParam) ? zhMonths : enMonths;
}

function computedNextMonth(month: string | number) {
  if ((Number(month) + 1) > 12) {
    return 1;
  } else {
    return Number(month) + 1;
  }
}

function computedPrevMonth(month: string | number): number {
  if ((Number(month) - 1) === 0) {
    return 12;
  } else {
    return Number(month) - 1;
  }
}

function getDateByCount(date: string, count: number): string {
  const [y, m, d] = date2ymd(date);
  const timestamp = +new Date(y, m - 1, d);
  const dateObj = new Date(timestamp + count * 86400000);
  const year = dateObj.getFullYear();
  const month = dateObj.getMonth();
  const day = dateObj.getDate();
  return `${year}-${month + 1}-${day}`;
}

function getPrevDate(year: string | number, month: string | number, day?: string | number): any[] {
  if (day) {
    return date2ymd(getDateByCount(`${year}-${month}-${day}`, -7));
  }
  return [computedPrevYear(year, month), computedPrevMonth(month)];
}

function getNextDate(year: string | number, month: string | number, day?: string | number): any[] {
  if (day) {
    return date2ymd(getDateByCount(`${year}-${month}-${day}`, 7));
  }
  return [computedNextYear(year, month), computedNextMonth(month)];
}

function delay(time?: number) {
  return new Promise(resolve => setTimeout(() => resolve(), time || 0));
}

function getToday(needArray?: boolean) {
  const now = new Date();
  const year = now.getFullYear();
  const month = now.getMonth() + 1;
  const day = now.getDate();

  if (needArray) {
    return [year, month, day];
  }
  return [year, month, day].join('-');
}

function getSomeNextMonths(year: string | number, month: string | number, count: number) {
  let currentYear = year;
  let currentMonth = month;
  return Array.from({length: count}).map((v, index) => {
    if (!index) {
      return `${currentYear}-${currentMonth}`;
    }
    const [y, m] = getNextDate(currentYear, currentMonth);
    currentYear = y;
    currentMonth = m;
    return `${currentYear}-${currentMonth}`;
  });
}

export {
  noop,
  isZh,
  delay,
  language,
  offloadFn,
  zhWeeks,
  enWeeks,
  date2ymd,
  getDateByCount,
  computedNextMonth,
  computedPrevMonth,
  getPrevDate,
  getNextDate,
  getToday,
  getWeeks,
  getMonths,
  computedPrevYear,
  computedNextYear,
  getSomeNextMonths,
};


================================================
FILE: src/declare.ts
================================================
interface CalendarInterface {
  monFirst?: boolean;
  completion?: boolean;
  useSwipe?: boolean;
  weeks?: string[];
  className?: string;
  language?: string;
  holidays: {[key: string]: string};
  tileContent: {[key: string]: any };
  remarks: {[key: string]: any };
  disabled: string[];
  begin: string[];
  end: string[];
  monthRange: string[];
  mode: 'monthRange' | 'week' | 'month';
  selectMode: 'select' | 'multi' | 'range' | 'multiRange';
  lunar: any;
  selectDate: any;
  format: (year: string, month: string) => string[];
}

export {
  CalendarInterface,
}


================================================
FILE: src/lunar.ts
================================================
// @ts-nocheck
/**
 * @1900-2100区间内的公历、农历互转
 * @charset UTF-8
 * @Author  Jea杨(JJonline@JJonline.Cn)
 * @Time    2014-7-21
 * @Time    2016-8-13 Fixed 2033hex、Attribution Annals
 * @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
 * @Version 1.0.2
 * @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
 * @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
 */
var calendar = {

  /**
   * 农历1900-2100的润大小信息表
   * @Array Of Property
   * @return Hex
   */
  lunarInfo:[0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d2,//1900-1909
    0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977,//1910-1919
    0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970,//1920-1929
    0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950,//1930-1939
    0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557,//1940-1949
    0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0,//1950-1959
    0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0,//1960-1969
    0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6,//1970-1979
    0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570,//1980-1989
    0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x055c0,0x0ab60,0x096d5,0x092e0,//1990-1999
    0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5,//2000-2009
    0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930,//2010-2019
    0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a6e6,0x0a4e0,0x0d260,0x0ea65,0x0d530,//2020-2029
    0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45,//2030-2039
    0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0,//2040-2049
    /**Add By JJonline@JJonline.Cn**/
    0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0,//2050-2059
    0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4,//2060-2069
    0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0,//2070-2079
    0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160,//2080-2089
    0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252,//2090-2099
    0x0d520],//2100

  /**
   * 公历每个月份的天数普通表
   * @Array Of Property
   * @return Number
   */
  solarMonth:[31,28,31,30,31,30,31,31,30,31,30,31],

  /**
   * 农历节日
   * @return String
   */
  lunarHoliday: {
    '1-1': '春节',
    '1-15': '元宵节',
    '2-2': '龙头节',
    '5-5': '端午节',
    '7-7': '七夕节',
    '7-15': '中元节',
    '8-15': '中秋节',
    '9-9': '重阳节',
    '10-1': '寒衣节',
    '10-15': '下元节',
    '12-8': '腊八节',
    '12-23': '小年',
  },

  /**
   * 阳历节日
   * @return String
   */
  gregorianHoliday: {
    '1-1': '元旦',
    '2-14': '情人节',
    '3-8': '妇女节',
    '3-12': '植树节',
    '5-1': '劳动节',
    '5-4': '青年节',
    '6-1': '儿童节',
    '7-1': '建党节',
    '8-1': '建军节',
    '9-10': '教师节',
    '10-1': '国庆节',
    '12-24': '平安夜',
    '12-25': '圣诞节',
  },

  /**
   * 天干地支之天干速查表
   * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
   * @return Cn string
   */
  Gan:["\u7532","\u4e59","\u4e19","\u4e01","\u620a","\u5df1","\u5e9a","\u8f9b","\u58ec","\u7678"],

  /**
   * 天干地支之地支速查表
   * @Array Of Property
   * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
   * @return Cn string
   */
  Zhi:["\u5b50","\u4e11","\u5bc5","\u536f","\u8fb0","\u5df3","\u5348","\u672a","\u7533","\u9149","\u620c","\u4ea5"],

  /**
   * 天干地支之地支速查表<=>生肖
   * @Array Of Property
   * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
   * @return Cn string
   */
  Animals:["\u9f20","\u725b","\u864e","\u5154","\u9f99","\u86c7","\u9a6c","\u7f8a","\u7334","\u9e21","\u72d7","\u732a"],

  /**
   * 24节气速查表
   * @Array Of Property
   * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
   * @return Cn string
   */
  solarTerm:["\u5c0f\u5bd2","\u5927\u5bd2","\u7acb\u6625","\u96e8\u6c34","\u60ca\u86f0","\u6625\u5206","\u6e05\u660e","\u8c37\u96e8","\u7acb\u590f","\u5c0f\u6ee1","\u8292\u79cd","\u590f\u81f3","\u5c0f\u6691","\u5927\u6691","\u7acb\u79cb","\u5904\u6691","\u767d\u9732","\u79cb\u5206","\u5bd2\u9732","\u971c\u964d","\u7acb\u51ac","\u5c0f\u96ea","\u5927\u96ea","\u51ac\u81f3"],

  /**
   * 1900-2100各年的24节气日期速查表
   * @Array Of Property
   * @return 0x string For splice
   */
  sTermInfo:['9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf97c3598082c95f8c965cc920f',
    '97bd0b06bdb0722c965ce1cfcc920f','b027097bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e',
    '97bcf97c359801ec95f8c965cc920f','97bd0b06bdb0722c965ce1cfcc920f','b027097bd097c36b0b6fc9274c91aa',
    '97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f','97bd0b06bdb0722c965ce1cfcc920f',
    'b027097bd097c36b0b6fc9274c91aa','9778397bd19801ec9210c965cc920e','97b6b97bd19801ec95f8c965cc920f',
    '97bd09801d98082c95f8e1cfcc920f','97bd097bd097c36b0b6fc9210c8dc2','9778397bd197c36c9210c9274c91aa',
    '97b6b97bd19801ec95f8c965cc920e','97bd09801d98082c95f8e1cfcc920f','97bd097bd097c36b0b6fc9210c8dc2',
    '9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec95f8c965cc920e','97bcf97c3598082c95f8e1cfcc920f',
    '97bd097bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec9210c965cc920e',
    '97bcf97c3598082c95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b97bd19801ec9210c965cc920e','97bcf97c3598082c95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722',
    '9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f',
    '97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e',
    '97bcf97c359801ec95f8c965cc920f','97bd097bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b97bd19801ec9210c965cc920e','97bcf97c359801ec95f8c965cc920f','97bd097bd07f595b0b6fc920fb0722',
    '9778397bd097c36b0b6fc9210c8dc2','9778397bd19801ec9210c9274c920e','97b6b97bd19801ec95f8c965cc920f',
    '97bd07f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c920e',
    '97b6b97bd19801ec95f8c965cc920f','97bd07f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2',
    '9778397bd097c36c9210c9274c91aa','97b6b97bd19801ec9210c965cc920e','97bd07f1487f595b0b0bc920fb0722',
    '7f0e397bd097c36b0b6fc9210c8dc2','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e',
    '97bcf7f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b97bd19801ec9210c965cc920e','97bcf7f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722',
    '9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e','97bcf7f1487f531b0b0bb0b6fb0722',
    '7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b97bd19801ec9210c965cc920e',
    '97bcf7f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b97bd19801ec9210c9274c920e','97bcf7f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722',
    '9778397bd097c36b0b6fc9210c91aa','97b6b97bd197c36c9210c9274c920e','97bcf7f0e47f531b0b0bb0b6fb0722',
    '7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9210c8dc2','9778397bd097c36c9210c9274c920e',
    '97b6b7f0e47f531b0723b0b6fb0722','7f0e37f5307f595b0b0bc920fb0722','7f0e397bd097c36b0b6fc9210c8dc2',
    '9778397bd097c36b0b70c9274c91aa','97b6b7f0e47f531b0723b0b6fb0721','7f0e37f1487f595b0b0bb0b6fb0722',
    '7f0e397bd097c35b0b6fc9210c8dc2','9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721',
    '7f0e27f1487f595b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722',
    '9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722',
    '7f0e397bd097c35b0b6fc920fb0722','9778397bd097c36b0b6fc9274c91aa','97b6b7f0e47f531b0723b0b6fb0721',
    '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9274c91aa',
    '97b6b7f0e47f531b0723b0787b0721','7f0e27f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722',
    '9778397bd097c36b0b6fc9210c91aa','97b6b7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722',
    '7f0e397bd07f595b0b0bc920fb0722','9778397bd097c36b0b6fc9210c8dc2','977837f0e37f149b0723b0787b0721',
    '7f07e7f0e47f531b0723b0b6fb0722','7f0e37f5307f595b0b0bc920fb0722','7f0e397bd097c35b0b6fc9210c8dc2',
    '977837f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0721','7f0e37f1487f595b0b0bb0b6fb0722',
    '7f0e397bd097c35b0b6fc9210c8dc2','977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721',
    '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722','977837f0e37f14998082b0787b06bd',
    '7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd097c35b0b6fc920fb0722',
    '977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722',
    '7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721',
    '7f0e27f1487f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14998082b0787b06bd',
    '7f07e7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0b0bb0b6fb0722','7f0e397bd07f595b0b0bc920fb0722',
    '977837f0e37f14998082b0723b06bd','7f07e7f0e37f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722',
    '7f0e397bd07f595b0b0bc920fb0722','977837f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b0721',
    '7f07e7f0e47f531b0723b0b6fb0722','7f0e37f1487f595b0b0bb0b6fb0722','7f0e37f0e37f14898082b0723b02d5',
    '7ec967f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0722','7f0e37f1487f531b0b0bb0b6fb0722',
    '7f0e37f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721',
    '7f0e37f1487f531b0b0bb0b6fb0722','7f0e37f0e37f14898082b072297c35','7ec967f0e37f14998082b0787b06bd',
    '7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722','7f0e37f0e37f14898082b072297c35',
    '7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722',
    '7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f149b0723b0787b0721',
    '7f0e27f1487f531b0b0bb0b6fb0722','7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14998082b0723b06bd',
    '7f07e7f0e47f149b0723b0787b0721','7f0e27f0e47f531b0723b0b6fb0722','7f0e37f0e366aa89801eb072297c35',
    '7ec967f0e37f14998082b0723b06bd','7f07e7f0e37f14998083b0787b0721','7f0e27f0e47f531b0723b0b6fb0722',
    '7f0e37f0e366aa89801eb072297c35','7ec967f0e37f14898082b0723b02d5','7f07e7f0e37f14998082b0787b0721',
    '7f07e7f0e47f531b0723b0b6fb0722','7f0e36665b66aa89801e9808297c35','665f67f0e37f14898082b0723b02d5',
    '7ec967f0e37f14998082b0787b0721','7f07e7f0e47f531b0723b0b6fb0722','7f0e36665b66a449801e9808297c35',
    '665f67f0e37f14898082b0723b02d5','7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721',
    '7f0e36665b66a449801e9808297c35','665f67f0e37f14898082b072297c35','7ec967f0e37f14998082b0787b06bd',
    '7f07e7f0e47f531b0723b0b6fb0721','7f0e26665b66a449801e9808297c35','665f67f0e37f1489801eb072297c35',
    '7ec967f0e37f14998082b0787b06bd','7f07e7f0e47f531b0723b0b6fb0721','7f0e27f1487f531b0b0bb0b6fb0722'],

  /**
   * 数字转中文速查表
   * @Array Of Property
   * @trans ['日','一','二','三','四','五','六','七','八','九','十']
   * @return Cn string
   */
  nStr1:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341"],

  /**
   * 日期转农历称呼速查表
   * @Array Of Property
   * @trans ['初','十','廿','卅']
   * @return Cn string
   */
  nStr2:["\u521d","\u5341","\u5eff","\u5345"],

  /**
   * 月份转农历称呼速查表
   * @Array Of Property
   * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
   * @return Cn string
   */
  nStr3:["\u6b63","\u4e8c","\u4e09","\u56db","\u4e94","\u516d","\u4e03","\u516b","\u4e5d","\u5341","\u51ac","\u814a"],

  /**
   * 返回农历y年一整年的总天数
   * @param lunar Year
   * @return Number
   * @eg:var count = calendar.lYearDays(1987) ;//count=387
   */
  lYearDays:function(y) {
    var i, sum = 348;
    for(i=0x8000; i>0x8; i>>=1) { sum += (calendar.lunarInfo[y-1900] & i)? 1: 0; }
    return(sum+calendar.leapDays(y));
  },

  /**
   * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
   * @param lunar Year
   * @return Number (0-12)
   * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
   */
  leapMonth:function(y) { //闰字编码 \u95f0
    return(calendar.lunarInfo[y-1900] & 0xf);
  },

  /**
   * 返回农历y年闰月的天数 若该年没有闰月则返回0
   * @param lunar Year
   * @return Number (0、29、30)
   * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
   */
  leapDays:function(y) {
    if(calendar.leapMonth(y))  {
      return((calendar.lunarInfo[y-1900] & 0x10000)? 30: 29);
    }
    return(0);
  },

  /**
   * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
   * @param lunar Year
   * @return Number (-1、29、30)
   * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
   */
  monthDays:function(y,m) {
    if(m>12 || m<1) {return -1}//月份参数从1至12,参数错误返回-1
    return( (calendar.lunarInfo[y-1900] & (0x10000>>m))? 30: 29 );
  },

  /**
   * 返回公历(!)y年m月的天数
   * @param solar Year
   * @return Number (-1、28、29、30、31)
   * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
   */
  solarDays:function(y,m) {
    if(m>12 || m<1) {return -1} //若参数错误 返回-1
    var ms = m-1;
    if(ms==1) { //2月份的闰平规律测算后确认返回28或29
      return(((y%4 == 0) && (y%100 != 0) || (y%400 == 0))? 29: 28);
    }else {
      return(calendar.solarMonth[ms]);
    }
  },

  /**
   * 农历年份转换为干支纪年
   * @param  lYear 农历年的年份数
   * @return Cn string
   */
  toGanZhiYear:function(lYear) {
    var ganKey = (lYear - 3) % 10;
    var zhiKey = (lYear - 3) % 12;
    if(ganKey == 0) ganKey = 10;//如果余数为0则为最后一个天干
    if(zhiKey == 0) zhiKey = 12;//如果余数为0则为最后一个地支
    return calendar.Gan[ganKey-1] + calendar.Zhi[zhiKey-1];

  },

  /**
   * 公历月、日判断所属星座
   * @param  cMonth [description]
   * @param  cDay [description]
   * @return Cn string
   */
  toAstro:function(cMonth,cDay) {
    var s   = "\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf";
    var arr = [20,19,21,21,21,22,23,23,23,23,22,22];
    return s.substr(cMonth*2 - (cDay < arr[cMonth-1] ? 2 : 0),2) + "\u5ea7";//座
  },

  /**
   * 传入offset偏移量返回干支
   * @param offset 相对甲子的偏移量
   * @return Cn string
   */
  toGanZhi:function(offset) {
    return calendar.Gan[offset%10] + calendar.Zhi[offset%12];
  },

  /**
   * 传入公历(!)y年获得该年第n个节气的公历日期
   * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
   * @return day Number
   * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
   */
  getTerm:function(y,n) {
    if(y<1900 || y>2100) {return -1;}
    if(n<1 || n>24) {return -1;}
    var _table = calendar.sTermInfo[y-1900];
    var _info = [
      parseInt('0x'+_table.substr(0,5)).toString() ,
      parseInt('0x'+_table.substr(5,5)).toString(),
      parseInt('0x'+_table.substr(10,5)).toString(),
      parseInt('0x'+_table.substr(15,5)).toString(),
      parseInt('0x'+_table.substr(20,5)).toString(),
      parseInt('0x'+_table.substr(25,5)).toString()
    ];
    var _calday = [
      _info[0].substr(0,1),
      _info[0].substr(1,2),
      _info[0].substr(3,1),
      _info[0].substr(4,2),

      _info[1].substr(0,1),
      _info[1].substr(1,2),
      _info[1].substr(3,1),
      _info[1].substr(4,2),

      _info[2].substr(0,1),
      _info[2].substr(1,2),
      _info[2].substr(3,1),
      _info[2].substr(4,2),

      _info[3].substr(0,1),
      _info[3].substr(1,2),
      _info[3].substr(3,1),
      _info[3].substr(4,2),

      _info[4].substr(0,1),
      _info[4].substr(1,2),
      _info[4].substr(3,1),
      _info[4].substr(4,2),

      _info[5].substr(0,1),
      _info[5].substr(1,2),
      _info[5].substr(3,1),
      _info[5].substr(4,2),
    ];
    return parseInt(_calday[n-1]);
  },

  /**
   * 传入农历数字月份返回汉语通俗表示法
   * @param lunar month
   * @return Cn string
   * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
   */
  toChinaMonth:function(m) { // 月 => \u6708
    if(m>12 || m<1) {return -1} //若参数错误 返回-1
    var s = calendar.nStr3[m-1];
    s+= "\u6708";//加上月字
    return s;
  },

  /**
   * 传入农历日期数字返回汉字表示法
   * @param lunar day
   * @return Cn string
   * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
   */
  toChinaDay:function(d){ //日 => \u65e5
    var s;
    switch (d) {
      case 10:
        s = '\u521d\u5341'; break;
      case 20:
        s = '\u4e8c\u5341'; break;
        break;
      case 30:
        s = '\u4e09\u5341'; break;
        break;
      default :
        s = calendar.nStr2[Math.floor(d/10)];
        s += calendar.nStr1[d%10];
    }
    return(s);
  },

  /**
   * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
   * @param y year
   * @return Cn string
   * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
   */
  getAnimal: function(y) {
    return calendar.Animals[(y - 4) % 12]
  },

  /**
   * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
   * @param y  solar year
   * @param m  solar month
   * @param d  solar day
   * @return JSON object
   * @eg:console.log(calendar.solar2lunar(1987,11,01));
   */
  solar2lunar:function (y,m,d) { //参数区间1900.1.31~2100.12.31
    if(y<1900 || y>2100) {return -1;}//年份限定、上限
    if(y==1900&&m==1&&d<31) {return -1;}//下限
    if(!y) { //未传参  获得当天
      var objDate = new Date();
    }else {
      var objDate = new Date(y,parseInt(m)-1,d)
    }
    var i, leap=0, temp=0;
    //修正ymd参数
    var y = objDate.getFullYear(),m = objDate.getMonth()+1,d = objDate.getDate();
    var offset   = (Date.UTC(objDate.getFullYear(),objDate.getMonth(),objDate.getDate()) - Date.UTC(1900,0,31))/86400000;
    for(i=1900; i<2101 && offset>0; i++) { temp=calendar.lYearDays(i); offset-=temp; }
    if(offset<0) { offset+=temp; i--; }

    //是否今天
    var isTodayObj = new Date(),isToday=false;
    if(isTodayObj.getFullYear()==y && isTodayObj.getMonth()+1==m && isTodayObj.getDate()==d) {
      isToday = true;
    }
    //星期几
    var nWeek = objDate.getDay(),cWeek = calendar.nStr1[nWeek];
    if(nWeek==0) {nWeek =7;}//数字表示周几顺应天朝周一开始的惯例
    //农历年
    var year = i;

    var leap = calendar.leapMonth(i); //闰哪个月
    var isLeap = false;

    //效验闰月
    for(i=1; i<13 && offset>0; i++) {
      //闰月
      if(leap>0 && i==(leap+1) && isLeap==false){
        --i;
        isLeap = true; temp = calendar.leapDays(year); //计算农历闰月天数
      }
      else{
        temp = calendar.monthDays(year, i);//计算农历普通月天数
      }
      //解除闰月
      if(isLeap==true && i==(leap+1)) { isLeap = false; }
      offset -= temp;
    }

    if(offset==0 && leap>0 && i==leap+1)
      if(isLeap){
        isLeap = false;
      }else{
        isLeap = true; --i;
      }
    if(offset<0){ offset += temp; --i; }
    //农历月
    var month   = i;
    //农历日
    var day     = offset + 1;

    //天干地支处理
    var sm      =   m-1;
    var gzY     =   calendar.toGanZhiYear(year);

    //月柱 1900年1月小寒以前为 丙子月(60进制12)
    var firstNode   = calendar.getTerm(year,(m*2-1));//返回当月「节」为几日开始
    var secondNode  = calendar.getTerm(year,(m*2));//返回当月「节」为几日开始

    //依据12节气修正干支月
    var gzM     =   calendar.toGanZhi((y-1900)*12+m+11);
    if(d>=firstNode) {
      gzM     =   calendar.toGanZhi((y-1900)*12+m+12);
    }

    //传入的日期的节气与否
    var isTerm = false;
    var Term   = null;
    if(firstNode==d) {
      isTerm  = true;
      Term    = calendar.solarTerm[m*2-2];
    }
    if(secondNode==d) {
      isTerm  = true;
      Term    = calendar.solarTerm[m*2-1];
    }
    //日柱 当月一日与 1900/1/1 相差天数
    var dayCyclical = Date.UTC(y,sm,1,0,0,0,0)/86400000+25567+10;
    var gzD = calendar.toGanZhi(dayCyclical+d-1);
    //该日期所属的星座
    var astro = calendar.toAstro(m,d);

    return {'lYear':year,'lMonth':month,'lDay':day,'Animal':calendar.getAnimal(year),'IMonthCn':(isLeap?"\u95f0":'')+calendar.toChinaMonth(month),'IDayCn':calendar.toChinaDay(day),'cYear':y,'cMonth':m,'cDay':d,'gzYear':gzY,'gzMonth':gzM,'gzDay':gzD,'isToday':isToday,'isLeap':isLeap,'nWeek':nWeek,'ncWeek':"\u661f\u671f"+cWeek,'isTerm':isTerm,'Term':Term,'astro':astro};
  },

  /**
   * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
   * @param y  lunar year
   * @param m  lunar month
   * @param d  lunar day
   * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
   * @return JSON object
   * @eg:console.log(calendar.lunar2solar(1987,9,10));
   */
  lunar2solar:function(y,m,d,isLeapMonth) {   //参数区间1900.1.31~2100.12.1
    var isLeapMonth = !!isLeapMonth;
    var leapOffset  = 0;
    var leapMonth   = calendar.leapMonth(y);
    var leapDay     = calendar.leapDays(y);
    if(isLeapMonth&&(leapMonth!=m)) {return -1;}//传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
    if(y==2100&&m==12&&d>1 || y==1900&&m==1&&d<31) {return -1;}//超出了最大极限值
    var day  = calendar.monthDays(y,m);
    var _day = day;
    //bugFix 2016-9-25
    //if month is leap, _day use leapDays method
    if(isLeapMonth) {
      _day = calendar.leapDays(y,m);
    }
    if(y < 1900 || y > 2100 || d > _day) {return -1;}//参数合法性效验

    //计算农历的时间差
    var offset = 0;
    for(var i=1900;i<y;i++) {
      offset+=calendar.lYearDays(i);
    }
    var leap = 0,isAdd= false;
    for(var i=1;i<m;i++) {
      leap = calendar.leapMonth(y);
      if(!isAdd) {//处理闰月
        if(leap<=i && leap>0) {
          offset+=calendar.leapDays(y);isAdd = true;
        }
      }
      offset+=calendar.monthDays(y,i);
    }
    //转换闰月农历 需补充该年闰月的前一个月的时差
    if(isLeapMonth) {offset+=day;}
    //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
    var stmap   =   Date.UTC(1900,1,30,0,0,0);
    var calObj  =   new Date((offset+d-31)*86400000+stmap);
    var cY      =   calObj.getUTCFullYear();
    var cM      =   calObj.getUTCMonth()+1;
    var cD      =   calObj.getUTCDate();

    return calendar.solar2lunar(cY,cM,cD);
  }
};

export default calendar;


================================================
FILE: src/mpvue-calendar.vue
================================================
<template>
  <div
    class="mpvue-calendar"
    :class="[`mpvue-calendar-mode-${tableMode}`, className]"
  >
    <Tools
      @onMonthChange="onToolsMonthChange"
      @next="nextChange"
      @prev="prevChange"
      @selectMonth="selectMonth"
      @selectYear="selectYear"
      :month="month"
      :year="year"
      :monFirst="monFirst"
      :timetableHeight="timetableHeight"
      :format="format"
      :tableMode="tableMode"
      :weeks="weeksInner"
      :months="months"
      :language="language"
      :arrowLeft="arrowLeft"
      :arrowRight="arrowRight"
    />
    <div
      :style="{height: timetableHeight + 'px'}"
    >
      <Swipe
        :initialSlide="1"
        :useSwipe="useSwipeInner"
        @swiperChangeEnd="swiperChangeEnd"
        @containerChange="containerChange"
        ref="swipeRef"
      >
        <Slide
          :useSwipe="useSwipeInner"
          class-name="asdasdsd"
          v-for="(item, index) in timetableList.list"
          :key="index"
        >
          <Timetable
            :tableIndex="index"
            :language="language"
            :timestamp="timestamp"
            :weeks="weeksInner"
            ref="timetableRef"
            :useSwipe="useSwipeInner"
            :month="item.month"
            :year="item.year"
            :day="item.day"
            :remarks="remarks"
            :holidays="holidays"
            :tileContent="tileContent"
            :disabled="disabled"
            :completion="completion"
            :monFirst="monFirst"
            :tableMode="tableMode"
            :selectMode="selectMode"
            :selectDate="selectDateInner"
            :begin="begin"
            :end="end"
            :format="format"
            :lunar="lunar"
            :backgroundText="backgroundText"
            @onSelect="onSelect"
            @onMonthChange="monthChange"
          />
        </Slide>
      </Swipe>
    </div>
  </div>
</template>

<script lang="ts">
  import { ref, reactive, watch, toRefs } from 'vue';
  import { CalendarInterface } from './declare';
  import Tools from './components/tools/index.vue';
  import Swipe from './components/swipe/index.vue';
  import Slide from './components/swipe/slide.vue';
  import Timetable from './components/timetable/index.vue';
  import { delay, enWeeks, getToday, isZh, zhWeeks, computedNextMonth, computedPrevMonth,
    getDateByCount, date2ymd, getPrevDate, getNextDate, getSomeNextMonths, getMonths,
  } from './components/utils';
  import './style.less';

  export default {
    props: {
      format: {
        type: Function,
      },
      holidays: {
        type: Object,
        default() {
          return {};
        }
      },
      lunar: {
        type: Object,
      },
      remarks: {
        type: Object,
        default() {
          return {};
        }
      },
      monthRange: {
        type: Array,
      },
      tileContent: {
        type: Object,
        default() {
          return {};
        }
      },
      completion: {
        type: Boolean,
        default: false
      },
      useSwipe: {
        type: Boolean,
        default: true
      },
      arrowLeft: {
        type: String,
        default: ''
      },
      arrowRight: {
        type: String,
        default: ''
      },
      backgroundText: {
        type: Boolean,
      },
      monFirst: {
        type: Boolean,
        default: false
      },
      className: {
        type: String,
      },
      mode: {
        type: String,
        default: 'month'
      },
      weeks: {
        type: Array,
        default: undefined
      },
      begin: {
        type: String,
      },
      end: {
        type: String,
      },
      selectMode: {
        type: String,
        default: 'select'
      },
      language: {
        type: String,
      },
      selectDate: {
        type: [String, Array, Object],
      },
      disabled: {
        type: Object,
      },
    },
    components: {
      Tools,
      Swipe,
      Slide,
      Timetable,
    },
    emits: ['onSelect', 'next', 'prev', 'selectMonth', 'selectYear', 'onMonthChange'],
    setup(props: CalendarInterface, { emit }: any) {
      const { tileContent, monFirst, monthRange, mode: tableMode, selectMode, selectDate, remarks,
        weeks, language: propLanguage,
      } = toRefs(props);
      const timestamp = ref(+new Date()); // listener timestamp change to refresh timetable
      const [todayYear, todayMonth, todaytDay] = getToday(true);
      const year = ref(todayYear);
      const month = ref(todayMonth);
      const day = ref(todaytDay);
      const timetableHeight = ref(undefined);
      const months = ref(getMonths(propLanguage?.value));
      const swipeRef = ref();
      const timetableRef = ref();
      const weeksInner = ref(computedWeek());
      const useSwipeInner = ref(tableMode.value === 'monthRange' ? false : props.useSwipe);
      const initSelectValue = selectDate?.value || {
        select: '',
        multi: [],
        multiRange: [],
        range: { start: '', end: '' },
      }[selectMode.value];
      const timetableList = reactive({list: getTimetableList()});
      const selectDateInner = ref(initSelectValue);

      function swiperChangeEnd(index: number) {
        const isWeekMode = tableMode.value === 'week';
        if (index === 2) {
          const [nextYear, nextMonth, nextDay] = getNextDate(year.value, month.value, isWeekMode ? day.value : undefined);
          year.value = nextYear;
          month.value = nextMonth;

          if (isWeekMode) {
            day.value = nextDay;
          }

          emit('onMonthChange', year.value, month.value);
          return swipeRef.value.slide(1, 0);
        }

        if (index === 0) {
          const [prevYear, prevMonth, prevDay] = getPrevDate(year.value, month.value, isWeekMode ? day.value : undefined);
          year.value = prevYear;
          month.value = prevMonth;

          if (isWeekMode) {
            day.value = prevDay;
          }

          emit('onMonthChange', year.value, month.value);
          return swipeRef.value.slide(1, 0);
        }
      }

      function computedWeek() {
        if (Array.isArray(props.weeks)) return props.weeks;

        const language = isZh(propLanguage?.value) ? 'zh': 'en';
        const weeksArray = {
          en: enWeeks,
          zh: zhWeeks,
        }[language];

        if (monFirst?.value) {
          return weeksArray.reduce((previousValue: any, currentValue: any, index: number) => {
            if (!index) {
              previousValue.first = currentValue;
              return previousValue;
            }

            previousValue.week.push(currentValue);

            if (index === weeksArray.length - 1) {
              return [...previousValue.week, previousValue.first];
            }

            return previousValue;
          }, {first: undefined, week: []});
        }

        return weeksArray;
      }

      function getTimetableList() {
        const isWeekMode = tableMode.value === 'week';

        if (tableMode.value === 'monthRange') {
          const monthRangeData = monthRange?.value || getSomeNextMonths(year?.value, month?.value, 3);

          return monthRangeData.map((item: string) => {
            const [rangeYear, rangeMonth] = item.split('-');
            return { year: rangeYear, month: rangeMonth };
          });
        }

        if (isWeekMode) {
          const [prevYear, prevMonth, prevDay] = getPrevDate(year.value, month.value, day.value);
          const [nextYear, nextMonth, nextDay] = getNextDate(year.value, month.value, day.value);
          return [
            {year: prevYear, month: prevMonth, day: prevDay, id: `${prevYear}-${prevMonth}-${prevDay}`},
            {year: year.value, month: month.value, day: day.value, id: `${year.value}-${month.value}-${day.value}`},
            {year: nextYear, month: nextMonth, day: nextDay, id: `${nextYear}-${nextMonth}-${nextDay}`},
          ];
        }

        if (!useSwipeInner.value) {
          return [{year: year.value, month: month.value, day: day.value, id: `${year.value}-${month.value}-${day.value}`}];
        }

        const [prevYear, prevMonth] = getPrevDate(year.value, month.value);
        const [nextYear, nextMonth] = getNextDate(year.value, month.value);
        return [
          {year: prevYear, month: prevMonth, id: `${prevYear}-${prevMonth}`},
          {year: year.value, month: month.value, id: `${year.value}-${month.value}`},
          {year: nextYear, month: nextMonth, id: `${nextYear}-${nextMonth}`},
        ];
      }

      function nextChange(currentYear: number | string, currentMonth: number | string) {
        if (tableMode.value === 'week') {
          const nextDate = getDateByCount(`${year.value}-${month.value}-${day.value}`, 7);
          const [nextYear, nextMonth, nextDay] = date2ymd(nextDate);
          year.value = nextYear;
          month.value = nextMonth;
          day.value = nextDay;

          emit('next', nextYear, nextMonth, nextDay);
          return emit('onMonthChange', nextYear, nextMonth, nextDay);
        }
        const nextMonth = computedNextMonth(currentMonth);
        month.value = nextMonth;
        year.value = nextMonth === 1 ? Number(year.value) + 1 : year.value;

        emit('next', year.value, month.value);
        emit('onMonthChange', year.value, month.value);
      }

      function prevChange(currentYear: number | string, currentMonth: number | string) {
        if (tableMode.value === 'week') {
          const nextDate = getDateByCount(`${year.value}-${month.value}-${day.value}`, -7);
          const [prevYear, prevMonth, prevDay] = date2ymd(nextDate);
          year.value = prevYear;
          month.value = prevMonth;
          day.value = prevDay;

          emit('prev', prevYear, prevMonth, prevDay);
          return emit('onMonthChange', prevYear, prevMonth, prevDay);
        }
        const prevMonth = computedPrevMonth(currentMonth);
        month.value = prevMonth;
        year.value = prevMonth === 12 ? Number(year.value) - 1 : year.value;

        emit('prev', year.value, month.value);
        emit('onMonthChange', year.value, month.value);
      }

      function selectMonth(selectedYear: number, selectedMonth: number) {
        year.value = selectedYear;
        month.value = selectedMonth;
        emit('selectMonth', selectedYear, selectedMonth);
      }

      function selectYear(selectedYear: number, selectedMonth: number) {
        year.value = selectedYear;
        month.value = selectedMonth;
        emit('selectYear', selectedYear, selectedMonth);
      }

      function containerChange(element: any) {
        timetableHeight.value = element?.clientHeight;
      }

      function onSelect(param: any) {
        selectDateInner.value = param;

        emit('onSelect', param);
        delay().then(refreshRender);
      }

      function refreshRender() {
        timestamp.value = +new Date();
      }

      function onToolsMonthChange(...param: any) {
        emit('onMonthChange', ...param);
      }

      function monthChange(param: any) {
        const { prevMonthDay, nextMonthDay } = param || {};
        if (nextMonthDay) {
          nextChange(year.value, month.value);
          return emit('next', year.value, month.value);
        }
        if (prevMonthDay) {
          prevChange(year.value, month.value);
          emit('prev', year.value, month.value);
        }
      }

      function render(renderYear: string | number, renderMonth: string | number, renderDay?: string | number) {
        if (renderYear && renderMonth) {
          year.value = renderYear;
          month.value = renderMonth;
        }

        if (renderDay) {
          day.value = renderDay;
        }
      }

      function setToday() {
        if (tableMode.value === 'monthRange') return;

        const [todayY, todayM, todaytD] = getToday(true);
        render(todayY, todayM, todaytD);
      }

      watch([year, month, day], () => {
        timetableList.list = getTimetableList();
      });

      watch(() => props.selectDate, () => {
        selectDateInner.value = selectDate.value;
      });

      watch(tableMode, () => {
        if (tableMode.value === 'monthRange') {
          useSwipeInner.value = false;
        }
        timetableList.list = getTimetableList();
        refreshRender();
      });

      watch(() => [props.begin, props.end, props.disabled, props.holidays, remarks, props.remarks], () => {
        refreshRender();
      });

      watch(tileContent, () => {
        refreshRender();
      });

      if (Object.keys(props.tileContent).length) {
        watch(props.tileContent, () => {
          refreshRender();
        });
      }

      watch([weeks, monFirst], () => {
        weeksInner.value = computedWeek();
      });

      return {
        month,
        year,
        day,
        nextChange,
        prevChange,
        selectMonth,
        selectYear,
        onSelect,
        monthChange,
        tableMode,
        timetableList,
        swiperChangeEnd,
        containerChange,
        timetableHeight,
        onToolsMonthChange,
        swipeRef,
        timetableRef,
        timestamp,
        render,
        selectDateInner,
        weeksInner,
        setToday,
        useSwipeInner,
        months,
      };
    }
  };
</script>


================================================
FILE: src/shims-vue.d.ts
================================================
declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}


================================================
FILE: src/style.less
================================================
.mpvue-calendar{
  position: relative;
  width: 300px;
  &.mpvue-calendar-mode-week{
    .vc-calendar-info{
      cursor: inherit;
    }
    .vc-calendar-timetable{
      .vc-calendar-timetable-wrap{
        .vc-calendar-body{
          .vc-calendar-content{
            .vc-calendar-month-background-text{
              font-size: 80px;
            }
          }
        }
      }
    }
  }
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": [
        "src/*",
        "example/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue"
  ],
  "exclude": [
    "node_modules"
  ]
}


================================================
FILE: vue.config.js
================================================
const path = require("path");

module.exports = {
  publicPath: process.env.NODE_ENV === 'production' ? '/mpvue-calendar/' : '/',
  configureWebpack: {
    context: path.resolve(__dirname, './'),
    entry: {
      app: process.env.NODE_ENV === 'production' ? './src/mpvue-calendar.vue' : './example/main.js'
    },
  },
  css: {
    extract: false
  },
  productionSourceMap: false,
  lintOnSave: process.env.NODE_ENV !== 'production'
}
Download .txt
gitextract_lou6h3iy/

├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── README.zh.md
├── babel.config.js
├── example/
│   ├── App.vue
│   └── main.js
├── package.json
├── public/
│   └── index.html
├── src/
│   ├── components/
│   │   ├── icon/
│   │   │   └── icon.css
│   │   ├── swipe/
│   │   │   ├── declare.ts
│   │   │   ├── index.vue
│   │   │   ├── slide.vue
│   │   │   ├── style.less
│   │   │   └── utils.ts
│   │   ├── timetable/
│   │   │   ├── computed.ts
│   │   │   ├── controller.ts
│   │   │   ├── declare.ts
│   │   │   ├── index.vue
│   │   │   └── style.less
│   │   ├── tools/
│   │   │   ├── index.vue
│   │   │   └── style.less
│   │   └── utils/
│   │       └── index.ts
│   ├── declare.ts
│   ├── lunar.ts
│   ├── mpvue-calendar.vue
│   ├── shims-vue.d.ts
│   └── style.less
├── tsconfig.json
└── vue.config.js
Download .txt
SYMBOL INDEX (50 symbols across 7 files)

FILE: src/components/swipe/declare.ts
  type SwipeInterface (line 1) | interface SwipeInterface {
  type SlideInterface (line 10) | interface SlideInterface {
  type startType (line 15) | type startType = {
  type deltaType (line 21) | type deltaType = {

FILE: src/components/swipe/utils.ts
  function hasTransitions (line 1) | function hasTransitions() {

FILE: src/components/timetable/computed.ts
  function date2ymd (line 3) | function date2ymd(date: string): number[] {
  function date2timeStamp (line 8) | function date2timeStamp(date: string): number {
  function getLunarInfo (line 13) | function getLunarInfo(y: string, m: string, d: string, lunar: any) {
  method update (line 40) | update(remarks: any = {}) {
  method getRemark (line 43) | getRemark(date: string) {
  function computedPrevDay (line 54) | function computedPrevDay(year: string, month: string, day: string | numb...
  function computedNextDay (line 70) | function computedNextDay(year: string, month: string, day: string): stri...
  type rangeOptionType (line 88) | type rangeOptionType = {
  function isCurrentMonthToday (line 96) | function isCurrentMonthToday(date: string) {
  function rangeOption (line 101) | function rangeOption({selectDate, date}: any) {
  function multiRangeOption (line 122) | function multiRangeOption({selectDate = [], date}: any) {
  function multiOption (line 150) | function multiOption({selectDate, date}: any) {
  function selectOption (line 154) | function selectOption({date, selectDate}: any) {
  type selectOptionType (line 158) | type selectOptionType = {
  method update (line 172) | update(disabled: any[] = []) {
  method isDisabled (line 180) | isDisabled(date: string) {
  method update (line 192) | update(tileContent: any) {
  method getTileContent (line 195) | getTileContent(date: string) {

FILE: src/components/timetable/controller.ts
  function singleSelect (line 3) | function singleSelect(selectDate: string, date: string) {
  function multiSelect (line 7) | function multiSelect(selectDate: string[] = [], date: string) {
  function rangeSelect (line 18) | function rangeSelect(selectDate: { start?: string; end?: string }, date:...
  function multiRange (line 52) | function multiRange(selectDates: { start?: string; end?: string }[], dat...

FILE: src/components/timetable/declare.ts
  type TimeTableInterface (line 1) | interface TimeTableInterface {
  type SwipeInterface (line 24) | interface SwipeInterface {
  type SlideInterface (line 31) | interface SlideInterface {
  type startType (line 35) | type startType = {
  type deltaType (line 41) | type deltaType = {

FILE: src/components/utils/index.ts
  function noop (line 1) | function noop() {} // eslint-disable-line
  function computedNextYear (line 3) | function computedNextYear(year: string | number, month: string | number)...
  function computedPrevYear (line 10) | function computedPrevYear(year: string | number, month: string | number)...
  function date2ymd (line 18) | function date2ymd(date: string): number[] {
  function offloadFn (line 23) | function offloadFn(fn: any) {
  function language (line 27) | function language(): string {
  function isZh (line 31) | function isZh(languageValue?: string) {
  function getWeeks (line 43) | function getWeeks(languageParam: string) {
  function getMonths (line 47) | function getMonths(languageParam: string | undefined) {
  function computedNextMonth (line 51) | function computedNextMonth(month: string | number) {
  function computedPrevMonth (line 59) | function computedPrevMonth(month: string | number): number {
  function getDateByCount (line 67) | function getDateByCount(date: string, count: number): string {
  function getPrevDate (line 77) | function getPrevDate(year: string | number, month: string | number, day?...
  function getNextDate (line 84) | function getNextDate(year: string | number, month: string | number, day?...
  function delay (line 91) | function delay(time?: number) {
  function getToday (line 95) | function getToday(needArray?: boolean) {
  function getSomeNextMonths (line 107) | function getSomeNextMonths(year: string | number, month: string | number...

FILE: src/declare.ts
  type CalendarInterface (line 1) | interface CalendarInterface {
Condensed preview — 35 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (132K chars).
[
  {
    "path": ".browserslistrc",
    "chars": 30,
    "preview": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": ".editorconfig",
    "chars": 188,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".eslintignore",
    "chars": 63,
    "preview": "dist/*\nnode_modules/*\n**/node_modules/*\nexample/*\nsrc/lunar.ts\n"
  },
  {
    "path": ".eslintrc.js",
    "chars": 1927,
    "preview": "module.exports = {\n  root: true,\n  env: {\n    node: true,\n  },\n  extends: [\n    'plugin:vue/vue3-essential',\n    '@vue/a"
  },
  {
    "path": ".gitignore",
    "chars": 146,
    "preview": ".DS_Store\nnode_modules/\ndist/\nlib/\ndemo/\nnpm-debug.log\nyarn-error.log\n\n# Editor directories and files\n.idea\n.vscode\n*.su"
  },
  {
    "path": ".npmignore",
    "chars": 302,
    "preview": "node_modules\nbuild\nconfig\nexample\ndemo\ndist/demo.html\ndist/mpvue-calendar.umd.js\nstatic\npublic\nsrc\nlib\n.babelrc\n.browser"
  },
  {
    "path": "LICENSE",
    "chars": 1095,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2018-present, ZhaoYun (Ricky) Han\n\nPermission is hereby granted, free of charge, to"
  },
  {
    "path": "README.md",
    "chars": 5210,
    "preview": "<p align=\"center\">\n<a href=\"http://preview.binlive.cn/mpvue-calendar#/\">\n<img width=\"100\" src=\"https://raw.githubusercon"
  },
  {
    "path": "README.zh.md",
    "chars": 3811,
    "preview": "<p align=\"center\">\n<a href=\"http://preview.binlive.cn/mpvue-calendar#/\">\n<img width=\"100\" src=\"https://raw.githubusercon"
  },
  {
    "path": "babel.config.js",
    "chars": 76,
    "preview": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset',\n  ],\n};\n"
  },
  {
    "path": "example/App.vue",
    "chars": 8667,
    "preview": "<template>\n  <div class=\"container\">\n    <div class=\"container-select-modes\">\n      <!--select mode-->\n      <Calendar\n "
  },
  {
    "path": "example/main.js",
    "chars": 91,
    "preview": "import { createApp } from 'vue';\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "package.json",
    "chars": 1784,
    "preview": "{\n  \"name\": \"mpvue-calendar\",\n  \"version\": \"3.0.1\",\n  \"description\": \"vue calendar mpvue-calendar vue-calendar\",\n  \"main"
  },
  {
    "path": "public/index.html",
    "chars": 589,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE="
  },
  {
    "path": "src/components/icon/icon.css",
    "chars": 1937,
    "preview": "@font-face {\n    font-family: \"calendar-iconfont\";\n    src: url('data:font/truetype;charset=utf-8;base64,d09GRgABAAAAAAS"
  },
  {
    "path": "src/components/swipe/declare.ts",
    "chars": 416,
    "preview": "interface SwipeInterface {\n  initialSlide?: number;\n  auto?: number;\n  speed: number;\n  timetableHeight?: number;\n  loop"
  },
  {
    "path": "src/components/swipe/index.vue",
    "chars": 15601,
    "preview": "<template>\n  <div\n    style='margin:0 auto'\n    :class=\"[useSwipe ? 'vc-calendar-swipe': 'vc-calendar-timetable-containe"
  },
  {
    "path": "src/components/swipe/slide.vue",
    "chars": 379,
    "preview": "<template>\n  <div\n    :class=\"[{[className]: !!className}, useSwipe ? 'swipe-slide': 'vc-calendar-timetable-item']\"\n  >\n"
  },
  {
    "path": "src/components/swipe/style.less",
    "chars": 248,
    "preview": ".vc-calendar-swipe {\n  overflow: hidden;\n  width: 100%;\n  .swipe-wrap{\n    width: 100%;\n    overflow: hidden;\n    positi"
  },
  {
    "path": "src/components/swipe/utils.ts",
    "chars": 339,
    "preview": "export function hasTransitions() {\n  const element = document.createElement('div');\n  const property = ['transitionPrope"
  },
  {
    "path": "src/components/timetable/computed.ts",
    "chars": 6201,
    "preview": "import { computedNextMonth, computedPrevMonth, getToday, computedNextYear } from '../utils';\n\nfunction date2ymd(date: st"
  },
  {
    "path": "src/components/timetable/controller.ts",
    "chars": 2716,
    "preview": "import { date2timeStamp } from './computed';\n\nfunction singleSelect(selectDate: string, date: string) {\n  return date;\n}"
  },
  {
    "path": "src/components/timetable/declare.ts",
    "chars": 1087,
    "preview": "interface TimeTableInterface {\n  monFirst?: boolean;\n  format?: (year: string | number, month: string | number) => any[]"
  },
  {
    "path": "src/components/timetable/index.vue",
    "chars": 14531,
    "preview": "<template>\n  <div\n    style='margin:0 auto'\n    class=\"vc-calendar-timetable\"\n    :class=\"{\n      'vc-calendar-timetable"
  },
  {
    "path": "src/components/timetable/style.less",
    "chars": 4267,
    "preview": ".vc-calendar-timetable{\n  background: #fff;\n  padding-bottom: 10px;\n  .vc-calendar-timetable-wrap{\n    .vc-calendar-body"
  },
  {
    "path": "src/components/tools/index.vue",
    "chars": 5526,
    "preview": "<template>\n  <div\n    class=\"vc-calendar-tools\"\n    ref=\"toolsRef\"\n    v-if=\"tableMode !== 'monthRange'\"\n  >\n    <div\n  "
  },
  {
    "path": "src/components/tools/style.less",
    "chars": 3714,
    "preview": ".vc-calendar-tools {\n  margin: auto;\n  width: 100%;\n  background: #fff;\n  user-select: none;\n  position: relative;\n  .vc"
  },
  {
    "path": "src/components/utils/index.ts",
    "chars": 3623,
    "preview": "function noop() {} // eslint-disable-line\n\nfunction computedNextYear(year: string | number, month: string | number): num"
  },
  {
    "path": "src/declare.ts",
    "chars": 573,
    "preview": "interface CalendarInterface {\n  monFirst?: boolean;\n  completion?: boolean;\n  useSwipe?: boolean;\n  weeks?: string[];\n  "
  },
  {
    "path": "src/lunar.ts",
    "chars": 22241,
    "preview": "// @ts-nocheck\n/**\n * @1900-2100区间内的公历、农历互转\n * @charset UTF-8\n * @Author  Jea杨(JJonline@JJonline.Cn)\n * @Time    2014-7-"
  },
  {
    "path": "src/mpvue-calendar.vue",
    "chars": 13343,
    "preview": "<template>\n  <div\n    class=\"mpvue-calendar\"\n    :class=\"[`mpvue-calendar-mode-${tableMode}`, className]\"\n  >\n    <Tools"
  },
  {
    "path": "src/shims-vue.d.ts",
    "chars": 72,
    "preview": "declare module \"*.vue\" {\n  import Vue from 'vue'\n  export default Vue\n}\n"
  },
  {
    "path": "src/style.less",
    "chars": 394,
    "preview": ".mpvue-calendar{\n  position: relative;\n  width: 300px;\n  &.mpvue-calendar-mode-week{\n    .vc-calendar-info{\n      cursor"
  },
  {
    "path": "tsconfig.json",
    "chars": 651,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"module\": \"esnext\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    "
  },
  {
    "path": "vue.config.js",
    "chars": 438,
    "preview": "const path = require(\"path\");\n\nmodule.exports = {\n  publicPath: process.env.NODE_ENV === 'production' ? '/mpvue-calendar"
  }
]

About this extraction

This page contains the full source code of the Hzy0913/mpvue-calendar GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 35 files (119.4 KB), approximately 38.1k tokens, and a symbol index with 50 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!