封装组件-vant日期选择,不简单真不简单

201 阅读2分钟

人生就像一场迷雾中的漫步,常常是后知后觉。

最近遇到一个页面要求维护,我打开代码一看我滴个天呀,全是代码所有逻辑都放在这个页面了。受不了了但是又能怎么办之前也是我写的,时间紧任务重我就不管三七二十一直接搞了。现在头疼的又是我自己哈哈哈哈,自作自受吧!

这个页面我想了想要优化我能想到的就是封装组件,我先封装了日期选择的组件,我自己写了一个基本的有功能的然后就把代码丢给了AI让他帮我分析了一下。

优化点

  1. 使用 v-model 替代 value + input(符合 Vue 2.2+ 或 Vue 3 的最佳实践)
  2. 避免直接修改 props(你的代码中没有直接修改 props,但可以进一步优化数据流)
  3. 优化 defaultDate 逻辑(避免在 created 和 openCalendar 中重复计算)
  4. 使用 computed 替代 watch(减少不必要的响应式开销)
  5. 增加 formatter 函数支持(让父组件自定义日期格式化逻辑)
  6. 优化 disabled 状态的 UI 反馈(如禁用点击效果)
  7. 增加 minDate 和 maxDate 的可配置性(目前是硬编码的)
  8. 优化日期格式化逻辑(避免重复创建 Date 对象)

优化后的代码

1. 模板部分 (<template>)

vue
<template>
  <div>
    <van-cell
      :title="title"
      :value="displayDate || placeholder"
      :is-link="!disabled"
      :class="{ 'disabled-cell': disabled }"
      @click="openCalendar"
    />
    <van-calendar
      v-model="showCalendar"
      color="#1989fa"
      type="range"
      :default-date="defaultDate"
      :min-date="minDate"
      :max-date="maxDate"
      @confirm="onConfirm"
    />
  </div>
</template>

2. 脚本部分 (<script>)

vue
<script>
export default {
  name: "RangeDatePicker",
  props: {
    title: {
      type: String,
      required: true,
    },
    placeholder: {
      type: String,
      default: "请选择",
    },
    value: {
      type: Array, // [startDate, endDate]
      default: () => [],
    },
    format: {
      type: String,
      default: "YYYY-MM-DD",
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    minDate: {
      type: Date,
      default: () => new Date(2020, 0, 31),
    },
    maxDate: {
      type: Date,
      default: () => new Date(2040, 0, 31),
    },
    formatter: {
      type: Function, // 自定义日期格式化函数
      default: null,
    },
  },
  data() {
    return {
      showCalendar: false,
      defaultDate: [], // 用于初始化日历选中状态
    };
  },
  computed: {
    // 显示的日期字符串(如 "2023-01-01 ~ 2023-01-05")
    displayDate() {
      if (!this.value || this.value.length < 2) return "";
      const [start, end] = this.value;
      return `${this.formatDate(start)} ~ ${this.formatDate(end)}`;
    },
  },
  watch: {
    // 监听 value 变化,更新 defaultDate(用于日历初始化)
    value: {
      immediate: true,
      handler(newVal) {
        if (newVal && newVal.length === 2) {
          this.defaultDate = newVal.map((date) => new Date(date));
        }
      },
    },
  },
  methods: {
    // 打开日历
    openCalendar() {
      if (this.disabled) return;
      this.showCalendar = true;
    },
    // 确认选择
    onConfirm(dates) {
      const formattedDates = dates.map((date) => this.formatDate(date));
      this.$emit("input", formattedDates); // 用于 v-model
      this.$emit("change", formattedDates); // 额外事件
      this.showCalendar = false;
    },
    // 格式化日期(优先使用 formatter,否则用 format)
    formatDate(date) {
      if (this.formatter) {
        return this.formatter(date);
      }
      const d = new Date(date);
      const year = d.getFullYear();
      const month = String(d.getMonth() + 1).padStart(2, "0");
      const day = String(d.getDate()).padStart(2, "0");
      return this.format
        .replace("YYYY", year)
        .replace("MM", month)
        .replace("DD", day);
    },
  },
};
</script>

3. 样式部分 (<style>)

vue
<style scoped>
.disabled-cell {
  opacity: 0.6;
  pointer-events: none;
}
</style>

ok就这样 🚀