实现公历和农历日期选择组件(用于选择出生日期)

使用的技术栈 html + vue3 + vant + lunar

需求:

  • 默认公历:2000年5月10日 12点30分钟
  • 公历和农历相互转换
  • 滑动选择日期,根据 change 日期切换时,改变对应的 月份、天数

最终效果

实现思路

  • 使用 vue3 做状态管理
  • 使用 vant 的 Popup 组件显示底部弹窗
  • 使用 vant 的 Picker 组件显示时间数据 vant 文档地址
  • 使用 lunar.js 库实现公历和农历的转换 lunar.js 文档地址
    • lunar.js 提供的工具
      • Lunar
      • LunarUtil
      • SolarUtil
      • LunarMonth
      • LunarYear
  • 公历 solar - 年份不变,月份 12 月不变,天数 根据 月份 计算,小时和分钟不变
    • 闰年 2 月 29 天
  • 农历 lunar - 年份不变,月份根据 年份 变化,天数 据根据 月份 计算,小时和分钟不变
    • 闰年有 13个月,多一个闰月
    • 农历月份天数不固定

步骤

  • 创建默认数据
  • 实现公历和农历日期转换
    • 公历切换农历
      • 获取公历的日期转换为农历的日期,根据年份、月份获取当年的月份数组、当月的天数。
    • 农历切换公历
      • 获取农历的日期转换为公历的日期,根据月份获取当月的天数。
  • 实现 change 改变日期时,修改对应数据,如果当前选中的天数大于 change(修改年、月) 后对应的天数,则切换到修改后对应天数的最后一天。
    • 公历状态
      • 改变年份:特殊处理 2月的天数,其他月份、天数不需要处理
      • 改变月份:处理当月的天数
      • 改变天数:不需要处理
    • 农历状态:
      • 改变年份:处理月份数组、天数
        • 如果当前选中的月份为闰月,则根据当前月份索引切换到当前年份下 月份数组 对应的月份
      • 改变月份:处理当月天数
      • 改变天数:不需要处理

具体实现

基本结构(html + css)

html 复制代码
<!DOCTYPE html>
<html lang="en">

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <link rel="stylesheet" href="./test.css" />

    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/vant@4/lib/index.css" />

    <!-- 核心第三方库:Vue -> Vant -> 业务数据(lunar)。全部使用 defer 以避免阻塞解析,按声明顺序执行。 -->
    <script src="https://cdn.jsdelivr.net/npm/vue@3.5.22/dist/vue.global.prod.min.js" defer></script>
    <script src="https://fastly.jsdelivr.net/npm/vant@4/lib/vant.min.js" defer></script>
    <script src="https://cdn.jsdelivr.net/npm/lunar-javascript@1.7.6/lunar.min.js" defer></script>

  </head>

  <body style="height: 100vh">
    <div id="app" v-cloak>
      <van-popup v-model:show="birthDatePopupShow" destroy-on-close round position="bottom"
        style="overflow-y: visible; height: 8.89rem">
        <p class="cascader-tip">
          若出生时间不详,可选择默认12:00,会对结果有一定影响
        </p>

        <div class="modal-content">
          <div class="modal-header">
            <div class="btn-cancel" @click="birthDatePopupShow = false">
              取消
            </div>
            <div class="calendar-toggle">
              <div @click="toggleCalendar('solar')" class="toggle-btn"
                :class="{ active: calendar === 'solar' }">
                公历
              </div>
              <div @click="toggleCalendar('lunar')" class="toggle-btn"
                :class="{ active: calendar === 'lunar' }">
                农历
              </div>
            </div>
            <div class="btn-confirm" @click="onBirthDateConfirmText">完成</div>
          </div>

          <van-picker ref="birthRef" v-model="pickerBirth" :show-toolbar="false" :columns="birthDateColumns"
            @cancel="birthDatePopupShow = false" @confirm="onBirthDateConfirm" @change="onBirthDateChange" />
        </div>
      </van-popup>
    </div>

    <script>
      // 设置 rem 基准值
      (function () {
        var win = window;
        var html = document.documentElement;
        function setRem() {
          var width = html.getBoundingClientRect().width;
          var rem = width / 10;
          win.rem = rem;
          html.style.fontSize = rem + "px";
        }
        setRem();
        window.setRem = setRem;
        window.onresize = function () {
          setRem();
        };
      })();

      // 在 DOMContentLoaded 后执行,确保所有 defer 外链脚本已加载并初始化全局对象(Vue、vant、SolarUtil 等)
      window.addEventListener("DOMContentLoaded", function () {
        const { createApp, ref } = Vue;
        const { showToast, Popup, Picker } = vant;

        const app = createApp({
          setup() {
            const birthDatePopupShow = ref(true);

            return {
              birthDatePopupShow,
            }
          },
        });

        app.component("van-popup", Popup);
        app.component("van-picker", Picker);

        app.mount("#app");
      });
    </script>
  </body>

</html>
css 复制代码
[v-cloak] {
    display: none !important;
}

.cascader-tip {
    position: absolute;
    top: -0.78rem;
    height: 0.45rem;
    width: 100%;

    display: inline-flex;
    justify-content: center;
    text-align: center;
    font-size: 0.32rem;
    font-weight: 400;
    color: hsla(0, 0%, 100%, 0.88);
}

.cascader-tip:before {
    background-size: contain;
    content: "";
    height: 0.42rem;
    margin-right: 0.1rem;
    width: 0.42rem;
}

.modal-content {
    background: #fff;
    border-radius: 0.38rem 0.38rem 0 0;
    bottom: 0;
    box-sizing: border-box;
    height: 8.89rem;
    left: 0;
    overflow-y: auto;
    padding: 0.25rem 0.44rem 0;
    position: fixed;
    width: 10rem;
}


.modal-header {
    align-items: center;
    display: flex;
    justify-content: space-between;
    margin-bottom: .41rem;
}

.modal-header .btn-cancel,
.modal-header .btn-confirm {
    font-family: PingFang SC;
    font-size: 0.33rem;
    font-weight: 400;
}

.modal-header .btn-cancel {
    color: #1f1f1f;
}

.modal-header .btn-confirm {
    color: #a02600;
}

.modal-header .calendar-toggle {
    background: #f5f5f5;
    border: 0.02rem solid #dc4823;
    border-radius: 0.39rem;
    box-shadow: 0 1px 4px rgba(255, 107, 53, 0.08);
    display: flex;
}

.modal-header .calendar-toggle .toggle-btn {
    border-radius: 0.39rem;
    font-family: PingFang SC;
    font-size: 0.33rem;
    font-weight: 500;
    line-height: 100%;
    padding: 0.15rem 0.36rem;
}

.modal-header .calendar-toggle .toggle-btn.active {
    background: #dc4823;
    color: #fff;
}

创建默认数据

javascript 复制代码
const { createApp, ref } = Vue;
const { showToast, Popup, Picker } = vant;

const app = createApp({
  setup() {
    const birthDatePopupShow = ref(true);

    // 'solar' 或 'lunar' 表示当前日历类型
    const calendar = ref("solar");

    // 默认出生日期:2000年5月10日 12:30
    // picker 组件的值是一个数组,表示各列的选中项的值
    const defaultBirthDate = [2000, 5, 10, 12, 30];
    // 当前选择的出生日期
    const pickerBirth = ref(defaultBirthDate);

    const birthRef = ref(null); // picker 组件的引用
    const birthDate = ref(""); // 最终确认的出生日期字符串

    // 初始化 年份 列表: 1901年 到 2099年
    const yearsInit = Array.from({ length: 199 }, (_, i) => ({
      text: `${1901 + i}年`,
      value: 1901 + i,
    }));

    // 初始化 公历月份 列表 : 1月 到 12月
    const monthsInit = Array.from({ length: 12 }, (_, i) => ({
      text: `${1 + i}月`,
      value: 1 + i,
    }));

    // 初始化 天数 列表 : 1日 到 31日
    const daysInit = Array.from({ length: 31 }, (_, i) => ({
      text: `${i + 1}日`,
      value: i + 1,
    }));

    // 初始化 时分 列表 : 0时 到 23时
    const hoursInit = Array.from({ length: 24 }, (_, i) => ({
      text: `${i}时`,
      value: i,
    }));

    // 初始化 分钟 列表 : 0分 到 59分
    const minutesInit = Array.from({ length: 60 }, (_, i) => ({
      text: `${i}分`,
      value: i,
    }));

    // 农历日转中文
    function lunarDayToChinese(day) {
      // 通过 LunarUtil 获取农历日的中文表示
      return LunarUtil.DAY[day];
    }

    // 初始化 农历日 列表 : 初一 到 三十
    const lunarDaysInit = Array.from({ length: 30 }, (_, i) => ({
      text: lunarDayToChinese(i + 1),
      value: i + 1,
    }));

    /***
     * picker 组件的列数据 - 年、月、日、时、分
     * 数据格式 { text: string, value: number }[]
     * 默认为 公历 列数据
     */
    const birthDateColumns = ref([
      yearsInit,
      monthsInit,
      daysInit,
      hoursInit,
      minutesInit,
    ]);

    return {
      calendar,
      birthRef,
      birthDate,
      pickerBirth,
      birthDateColumns,
      birthDatePopupShow,
    };
  },
});

实现切换公历和农历

javascript 复制代码
// 农历月份转中文
function lunarMonthToChinese(month) {
  return (
    (month.isLeap() ? "闰" : "") +
    LunarUtil.MONTH[Math.abs(month.getMonth())] +
    "月"
  );
}

// 切换日历类型
const toggleCalendar = (type) => {
  // 如果当前类型与选择的相同,则不操作
  if (type === calendar.value) return;

  // 获取当前 picker 选中的值
  // const currentPickDate = pickerBirth.value; // [2000, 5, 10, 12, 30];
  const [year, month, day, hour, minute] = pickerBirth.value;

  switch (type) {
      // 阴历 切换为 公历
    case "solar":
      // 当前 阴历
      const curLunar = Lunar.fromYmd(year, month, day);

      // 转成 公历
      const solar = curLunar.getSolar();

      const solar_yearNumber = solar.getYear(); // 公历年
      const solar_monthNumber = solar.getMonth(); // 公历月
      const solar_dayNumber = solar.getDay(); // 公历日

      // 获取当月天数
      const daysCount = SolarUtil.getDaysOfMonth(
        solar_yearNumber,
        solar_monthNumber
      );

      // 更新 picker 组件的列数据
      birthDateColumns.value = [
        yearsInit,
        monthsInit,
         .slice(0, daysCount),
        hoursInit,
        minutesInit,
      ];

      // 更新 picker 组件的选中值
      pickerBirth.value = [
        solar_yearNumber,
        solar_monthNumber,
        solar_dayNumber,
        hour,
        minute,
      ];

      break;

      // 公历 切换为 农历
    case "lunar":
      // 创建 Date 对象,注意月份减1
      const curSolarDate = new Date(year, month - 1, day);

      // 根据 Date 对象创建 Lunar 对象
      const lunar = Lunar.fromDate(curSolarDate);

      const lunar_yearNumber = lunar.getYear(); // 农历年
      const lunar_monthNumber = lunar.getMonth(); // 农历月
      const lunar_dayNumber = lunar.getDay(); // 农历日

      // 阴历年工具 实例化,用于获取月份信息
      const lunarYear = LunarYear.fromYear(lunar_yearNumber);

      // 获取该年的所有 农历月 初始数组
      const monthArrayInit = lunarYear.getMonthsInYear();

      // 加工处理 农历月数组 为 picker 组件需要的格式
      const monthArray = monthArrayInit.reduce((acc, month) => {
        acc.push({
          text: lunarMonthToChinese(month),
          value: month.getMonth(),
        });

        return acc;
      }, []);

      // 当月的天数
      const curDayCount = LunarMonth.fromYm(
        lunar_yearNumber,
        lunar_monthNumber
      ).getDayCount();

      // 更新 picker 组件的列数据
      birthDateColumns.value = [
        yearsInit,
        monthArray,
        lunarDaysInit.slice(0, curDayCount),
        hoursInit,
        minutesInit,
      ];

      // 更新 picker 组件的选中值
      pickerBirth.value = [
        lunar_yearNumber,
        lunar_monthNumber,
        lunar_dayNumber,
        hour,
        minute,
      ];

      break;
  }

  // 更新当前日历类型
  calendar.value = type;
};

实现 change 事件

javascript 复制代码
// 监听 picker 组件的值变化
const onBirthDateChange = ({
  selectedValues, // 选中的值数组
  selectedOptions, // 选中的选项数组
  selectedIndexes, // 选中的索引数组
  columnIndex, // 当前变更的列索引
}) => {
  const [curYear, curMonth, curDay, curHour, curMinute] = selectedValues;

  // 公历 情况下
  if (calendar.value === "solar") {
    switch (columnIndex) {
      case 0: // 年 
        // 特殊处理:2月 天数变化
        if (curMonth === 2) {
          // 判断是否闰年
          const isLeap = SolarUtil.isLeapYear(curYear);

          // 更新天数列
          birthDateColumns.value[2] = isLeap
            ? daysInit.slice(0, 29)
            : daysInit.slice(0, 28);
        }

        break;

      case 1: // 月
        // 获取当月天数
        const daysCount = SolarUtil.getDaysOfMonth(
          curYear,
          curMonth
        );

        // 如果 天数列 长度 不等于 当月天数,则更新天数列
        if (birthDateColumns.value[2].length !== daysCount) {
          birthDateColumns.value[2] = daysInit.slice(0, daysCount);
        }

        break;
    }
  }

  // 农历 情况下
  if (calendar.value === "lunar") {
    // 获取该年 阴历年工具 实例
    const lunarYear = LunarYear.fromYear(curYear);

    // 获取该年的所有 农历月 初始数组
    const monthArrayInit = lunarYear.getMonthsInYear();

    switch (columnIndex) {
      case 0: // 年
        // 加工处理 农历月数组 为 picker 组件需要的格式
        const monthArray = monthArrayInit.reduce((acc, month) => {
          acc.push({
            text: lunarMonthToChinese(month),
            value: month.getMonth(),
          });

          return acc;
        }, []);

        // 考虑 闰年 情况下, curMonth 为负数, 则根据 选中的月索引 更新 月份
        const _month =
          curMonth > 0
          ? curMonth
          : monthArray[selectedIndexes[1]].value;

        // 当月的天数
        const _daysCount = LunarMonth.fromYm(
          curYear,
          _month
        ).getDayCount();

        // 更新 picker 组件的列数据
        birthDateColumns.value = [
          yearsInit,
          monthArray,
          lunarDaysInit.slice(0, _daysCount),
          hoursInit,
          minutesInit,
        ];

        // 更新 picker 组件的选中值
        pickerBirth.value = [
          curYear,
          _month,
          curDay,
          curHour,
          curMinute,
        ];

        break;

      case 1: // 月
        // 当月的天数
        const curDayCount = LunarMonth.fromYm(
          curYear,
          curMonth
        ).getDayCount();

        // 更新天数列
        birthDateColumns.value[2] = lunarDaysInit.slice(
          0,
          curDayCount
        );
        break;
    }
  }

  // 如果选中的天数大于该月的天数,则截取该月的最后一天
  if (curDay > birthDateColumns.value[2].length) {
    // 更新 picker 组件的选中值
    pickerBirth.value = [
      curYear,
      pickerBirth.value[1],
      birthDateColumns.value[2].length,
      curHour,
      curMinute,
    ];
  }
};

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>

    <link rel="stylesheet" href="./test.css" />

    <!-- 引入样式文件 -->
    <link
      rel="stylesheet"
      href="https://fastly.jsdelivr.net/npm/vant@4/lib/index.css"
    />

    <!-- 核心第三方库:Vue -> Vant -> 业务数据(lunar)。全部使用 defer 以避免阻塞解析,按声明顺序执行。 -->
    <script
      src="https://cdn.jsdelivr.net/npm/vue@3.5.22/dist/vue.global.prod.min.js"
      defer
    ></script>
    <script
      src="https://fastly.jsdelivr.net/npm/vant@4/lib/vant.min.js"
      defer
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/lunar-javascript@1.7.6/lunar.min.js"
      defer
    ></script>
  </head>

  <body style="height: 100vh">
    <div id="app" v-cloak>
      <van-popup
        v-model:show="birthDatePopupShow"
        destroy-on-close
        round
        position="bottom"
        style="overflow-y: visible; height: 8.89rem"
      >
        <p class="cascader-tip">
          若出生时间不详,可选择默认12:00,会对结果有一定影响
        </p>

        <div class="modal-content">
          <div class="modal-header">
            <div class="btn-cancel" @click="birthDatePopupShow = false">
              取消
            </div>
            <div class="calendar-toggle">
              <div
                @click="toggleCalendar('solar')"
                class="toggle-btn"
                :class="{ active: calendar === 'solar' }"
              >
                公历
              </div>
              <div
                @click="toggleCalendar('lunar')"
                class="toggle-btn"
                :class="{ active: calendar === 'lunar' }"
              >
                农历
              </div>
            </div>
            <div class="btn-confirm" @click="onBirthDateConfirmText">完成</div>
          </div>

          <van-picker
            ref="birthRef"
            v-model="pickerBirth"
            :show-toolbar="false"
            :columns="birthDateColumns"
            @cancel="birthDatePopupShow = false"
            @confirm="onBirthDateConfirm"
            @change="onBirthDateChange"
          />
        </div>
      </van-popup>
    </div>

    <script>
      // 设置 rem 基准值
      (function () {
        var win = window;
        var html = document.documentElement;
        function setRem() {
          var width = html.getBoundingClientRect().width;
          var rem = width / 10;
          win.rem = rem;
          html.style.fontSize = rem + "px";
        }
        setRem();
        window.setRem = setRem;
        window.onresize = function () {
          setRem();
        };
      })();

      // 在 DOMContentLoaded 后执行,确保所有 defer 外链脚本已加载并初始化全局对象(Vue、vant、SolarUtil 等)
      window.addEventListener("DOMContentLoaded", function () {
        const { createApp, ref } = Vue;
        const { showToast, Popup, Picker } = vant;

        const app = createApp({
          setup() {
            const birthDatePopupShow = ref(true);

            // 'solar' 或 'lunar' 表示当前日历类型
            const calendar = ref("solar");

            // 默认出生日期:2000年5月10日 12:30
            // picker 组件的值是一个数组,表示各列的选中的值
            const defaultBirthDate = [2000, 5, 10, 12, 30];
            // 当前选择的出生日期
            const pickerBirth = ref(defaultBirthDate);

            const birthRef = ref(null); // picker 组件的引用
            const birthDate = ref(""); // 最终确认的出生日期字符串

            // 初始化 年份 列表: 1901年 到 2099年
            const yearsInit = Array.from({ length: 199 }, (_, i) => ({
              text: `${1901 + i}年`,
              value: 1901 + i,
            }));

            // 初始化 公历月份 列表 : 1月 到 12月
            const monthsInit = Array.from({ length: 12 }, (_, i) => ({
              text: `${1 + i}月`,
              value: 1 + i,
            }));

            // 初始化 天数 列表 : 1日 到 31日
            const daysInit = Array.from({ length: 31 }, (_, i) => ({
              text: `${i + 1}日`,
              value: i + 1,
            }));

            // 初始化 时分 列表 : 0时 到 23时
            const hoursInit = Array.from({ length: 24 }, (_, i) => ({
              text: `${i}时`,
              value: i,
            }));

            // 初始化 分钟 列表 : 0分 到 59分
            const minutesInit = Array.from({ length: 60 }, (_, i) => ({
              text: `${i}分`,
              value: i,
            }));

            // 农历日转中文
            function lunarDayToChinese(day) {
              return LunarUtil.DAY[day];
            }

            // 初始化 农历日 列表 : 初一 到 三十
            const lunarDaysInit = Array.from({ length: 30 }, (_, i) => ({
              text: lunarDayToChinese(i + 1),
              value: i + 1,
            }));

            /***
             * picker 组件的列数据 - 年、月、日、时、分
             * 数据格式 { text: string, value: number }[]
             * 默认为 公历 列数据
             */
            const birthDateColumns = ref([
              yearsInit,
              monthsInit,
              daysInit,
              hoursInit,
              minutesInit,
            ]);

            // 农历月份转中文
            function lunarMonthToChinese(month) {
              return (
                (month.isLeap() ? "闰" : "") +
                LunarUtil.MONTH[Math.abs(month.getMonth())] +
                "月"
              );
            }

            // 切换日历类型
            const toggleCalendar = (type) => {
              // 如果当前类型与选择的相同,则不操作
              if (type === calendar.value) return;

              // 获取当前 picker 选中的值
              // const currentPickDate = pickerBirth.value; // [2000, 5, 10, 12, 30];
              const [year, month, day, hour, minute] = pickerBirth.value;

              switch (type) {
                // 阴历 切换为 公历
                case "solar":
                  // 当前 阴历
                  const curLunar = Lunar.fromYmd(year, month, day);

                  // 转成 公历
                  const solar = curLunar.getSolar();

                  const solar_yearNumber = solar.getYear(); // 公历年
                  const solar_monthNumber = solar.getMonth(); // 公历月
                  const solar_dayNumber = solar.getDay(); // 公历日

                  // 获取当月天数
                  const daysCount = SolarUtil.getDaysOfMonth(
                    solar_yearNumber,
                    solar_monthNumber
                  );

                  // 更新 picker 组件的列数据
                  birthDateColumns.value = [
                    yearsInit,
                    monthsInit,
                    daysInit.slice(0, daysCount),
                    hoursInit,
                    minutesInit,
                  ];

                  // 更新 picker 组件的选中值
                  pickerBirth.value = [
                    solar_yearNumber,
                    solar_monthNumber,
                    solar_dayNumber,
                    hour,
                    minute,
                  ];

                  break;

                // 公历 切换为 农历
                case "lunar":
                  // 创建 Date 对象,注意月份减1
                  const curSolarDate = new Date(year, month - 1, day);

                  // 根据 Date 对象创建 Lunar 对象
                  const lunar = Lunar.fromDate(curSolarDate);

                  const lunar_yearNumber = lunar.getYear(); // 农历年
                  const lunar_monthNumber = lunar.getMonth(); // 农历月
                  const lunar_dayNumber = lunar.getDay(); // 农历日

                  // 阴历年工具 实例化,用于获取月份信息
                  const lunarYear = LunarYear.fromYear(lunar_yearNumber);

                  // 获取该年的所有 农历月 初始数组
                  const monthArrayInit = lunarYear.getMonthsInYear();

                  // 加工处理 农历月数组 为 picker 组件需要的格式
                  const monthArray = monthArrayInit.reduce((acc, month) => {
                    acc.push({
                      text: lunarMonthToChinese(month),
                      value: month.getMonth(),
                    });

                    return acc;
                  }, []);

                  // 当月的天数
                  const curDayCount = LunarMonth.fromYm(
                    lunar_yearNumber,
                    lunar_monthNumber
                  ).getDayCount();

                  // 更新 picker 组件的列数据
                  birthDateColumns.value = [
                    yearsInit,
                    monthArray,
                    lunarDaysInit.slice(0, curDayCount),
                    hoursInit,
                    minutesInit,
                  ];

                  // 更新 picker 组件的选中值
                  pickerBirth.value = [
                    lunar_yearNumber,
                    lunar_monthNumber,
                    lunar_dayNumber,
                    hour,
                    minute,
                  ];

                  break;
              }

              // 更新当前日历类型
              calendar.value = type;
            };

            // 监听 picker 组件的值变化
            const onBirthDateChange = ({
              selectedValues, // 选中的值数组
              selectedOptions, // 选中的选项数组
              selectedIndexes, // 选中的索引数组
              columnIndex, // 当前变更的列索引
            }) => {
              const [curYear, curMonth, curDay, curHour, curMinute] =
                selectedValues;

              // 公历 情况下
              if (calendar.value === "solar") {
                switch (columnIndex) {
                  case 0: // 年
                    // 特殊处理:2月 天数变化
                    if (curMonth === 2) {
                      // 判断是否闰年
                      const isLeap = SolarUtil.isLeapYear(curYear);

                      // 更新天数列
                      birthDateColumns.value[2] = isLeap
                        ? daysInit.slice(0, 29)
                        : daysInit.slice(0, 28);
                    }

                    break;

                  case 1: // 月
                    // 获取当月天数
                    const daysCount = SolarUtil.getDaysOfMonth(
                      curYear,
                      curMonth
                    );

                    // 如果 天数列 长度 不等于 当月天数,则更新天数列
                    if (birthDateColumns.value[2].length !== daysCount) {
                      birthDateColumns.value[2] = daysInit.slice(0, daysCount);
                    }

                    break;
                }
              }

              // 农历 情况下
              if (calendar.value === "lunar") {
                // 获取该年 阴历年工具 实例
                const lunarYear = LunarYear.fromYear(curYear);

                // 获取该年的所有 农历月 初始数组
                const monthArrayInit = lunarYear.getMonthsInYear();

                switch (columnIndex) {
                  case 0: // 年
                    // 加工处理 农历月数组 为 picker 组件需要的格式
                    const monthArray = monthArrayInit.reduce((acc, month) => {
                      acc.push({
                        text: lunarMonthToChinese(month),
                        value: month.getMonth(),
                      });

                      return acc;
                    }, []);

                    // 考虑 闰年 情况下, curMonth 为负数, 则根据 选中的月索引 更新 月份
                    const _month =
                      curMonth > 0
                        ? curMonth
                        : monthArray[selectedIndexes[1]].value;

                    // 当月的天数
                    const _daysCount = LunarMonth.fromYm(
                      curYear,
                      _month
                    ).getDayCount();

                    // 更新 picker 组件的列数据
                    birthDateColumns.value = [
                      yearsInit,
                      monthArray,
                      lunarDaysInit.slice(0, _daysCount),
                      hoursInit,
                      minutesInit,
                    ];

                    // 更新 picker 组件的选中值
                    pickerBirth.value = [
                      curYear,
                      _month,
                      curDay,
                      curHour,
                      curMinute,
                    ];

                    break;

                  case 1: // 月
                    // 当月的天数
                    const curDayCount = LunarMonth.fromYm(
                      curYear,
                      curMonth
                    ).getDayCount();

                    // 更新天数列
                    birthDateColumns.value[2] = lunarDaysInit.slice(
                      0,
                      curDayCount
                    );
                    break;
                }
              }

              // 如果选中的天数大于该月的天数,则截取该月的最后一天
              if (curDay > birthDateColumns.value[2].length) {
                // 更新 picker 组件的选中值
                pickerBirth.value = [
                  curYear,
                  pickerBirth.value[1],
                  birthDateColumns.value[2].length,
                  curHour,
                  curMinute,
                ];
              }
            };

            // 监听 picker 组件的确认事件
            const onBirthDateConfirm = ({ selectedOptions }) => {
              const str = `${selectedOptions[0].text}${selectedOptions[1].text}${selectedOptions[2].text} ${selectedOptions[3].text}${selectedOptions[4].text}`;
              birthDate.value = str;
              // birthDatePopupShow.value = false;
              console.log("出生日期:", str);
            };

            // 点击 完成 按钮 触发
            const onBirthDateConfirmText = () => {
              // 触发 picker 组件 confirm 事件
              birthRef.value.confirm();
            };

            return {
              calendar,
              birthRef,
              birthDate,
              pickerBirth,
              birthDateColumns,
              birthDatePopupShow,

              toggleCalendar,
              onBirthDateChange,
              onBirthDateConfirm,
              onBirthDateConfirmText,
            };
          },
        });

        app.component("van-popup", Popup);
        app.component("van-picker", Picker);

        app.mount("#app");
      });
    </script>
  </body>
</html>
相关推荐
一道雷9 小时前
🚀 Vue Router 插件系统:让路由扩展变得简单优雅
前端·javascript·vue.js
半桶水专家11 小时前
Vue 3 插槽(Slot)详解
前端·javascript·vue.js
梵得儿SHI14 小时前
Vue 数据绑定深入浅出:从 v-bind 到 v-model 的实战指南
前端·javascript·vue.js·双向绑定·vue 数据绑定机制·单向绑定·v-bind v-model
十里-1 天前
在 Vue2 中为 Element-UI 的 el-dialog 添加拖拽功能
前端·vue.js·ui
D_C_tyu1 天前
Vue3 + Element Plus 实现前端手动分页
javascript·vue.js·elementui
千里码aicood1 天前
python+vue旅游购票管理系统设计(源码+文档+调试+基础修改+答疑)
vue.js·python·旅游
麦麦大数据1 天前
F038 vue+flask 微博舆情热搜情感分析大数据分析系统|前后端分离架构
vue.js·架构·flask·情感分析·微博·舆情分析
qq_339191141 天前
vue3 npm run dev局域网可以访问,vue启动设置局域网访问,
前端·vue.js·npm
橙某人1 天前
Vue3 + Pinia 移动端Web应用:页面缓存策略解决方案💡
前端·javascript·vue.js