<!-- vant的picker组件只支持年月日，有些情况下需要一个能支持选择年月日时分秒的picker组件 -->
<!-- 本组件可支持“年月日时分秒”任意格式（dateFormat） -->
<template>
  <!-- 弹出层 -->
  <van-picker
    ref="dateTimePicker"
    v-model="data.selectedValues"
    :title="title"
    :columns="data.columns"
    swipe-duration="500"
    @cancel="cancelOn"
    @confirm="onConfirm"
    @change="onChange"
  />
</template>
<script setup lang="ts">
import { ref, reactive, watch, computed } from 'vue';

// 所有可能用到的日期格式
type dateFormat =
  | 'YYYY'
  | 'YYYY-MM'
  | 'YYYY-MM-DD'
  | 'YYYY-MM-DD HH'
  | 'YYYY-MM-DD HH:mm'
  | 'YYYY-MM-DD HH:mm:ss';
/** 声明props类型 */
interface Props {
  // 传入的显示隐藏状态
  showPicker: boolean;
  // picker标题
  title?: string;
  // 传入的日期值
  values: string;
  // 自定义格式
  format?: dateFormat;
  // 自定义单位
  units?: string[];
  // 最小日期时间
  minDate?: string;
  // 最大日期时间
  maxDate?: string;
}

interface IntColumnsItem {
  text: string;
  value: string;
  children?: IntColumnsItem[];
}

const props = withDefaults(defineProps<Props>(), {
  showPicker: false,
  title: '请选择日期',
  values: '',
  format: 'YYYY-MM-DD HH:mm',
  units: () => [],
  minDate: '',
  maxDate: ''
});

// 定义要向父组件传递的事件
const emit = defineEmits(['cancel', 'confirm']);

const dateTimePicker = ref(null);

// 定义数据
const data = reactive<{
  isPicker: boolean;
  columns: IntColumnsItem[];
  selectedValues: any[];
  tempSelectedValues: any[];
}>({
  // 是否显示弹出层
  isPicker: false,
  // 所有时间列
  columns: [],
  // 控件选择的时间值
  selectedValues: [],
  // 临时数据,数据变化过程中选中的值
  tempSelectedValues: []
});
// 获取数据类型
const getType = (v: any) => {
  return Object.prototype.toString.call(v).slice(8, -1).toLowerCase();
};
// 返回指定格式的日期时间
const getDateByFormat = (dateParam: Date | string, fmt: dateFormat) => {
  // 避免直接修改参数
  let date = dateParam;
  const thisDateType = getType(date);
  if (date === '' || (thisDateType !== 'date' && thisDateType !== 'string')) {
    date = new Date();
  } else if (thisDateType === 'string') {
    date = new Date((date as string).replace(/-/g, '/'));
  } else {
    date = new Date(date);
  }
  const Y = date.getFullYear();
  const M = date.getMonth() + 1;
  const D = date.getDate();
  const h = date.getHours();
  const m = date.getMinutes();
  const s = date.getSeconds();
  // 个位数补0
  const _M = M < 10 ? `0${M}` : M.toString();
  const _D = D < 10 ? `0${D}` : D.toString();
  const _h = h < 10 ? `0${h}` : h.toString();
  const _m = m < 10 ? `0${m}` : m.toString();
  const _s = s < 10 ? `0${s}` : s.toString();
  return fmt
    .replace(/YYYY/g, Y.toString())
    .replace('MM', _M)
    .replace('DD', _D)
    .replace('HH', _h)
    .replace('mm', _m)
    .replace('ss', _s);
};

// 比较两个日期大小
const dateRangeLegal = (sDateParam: string | Date, eDateParam: string | Date) => {
  // 避免直接修改参数
  let sDate = sDateParam;
  let eDate = eDateParam;
  if (!sDate || !eDate) {
    return false;
  }
  // 补全模板
  const complateStr = '0000-01-01 00:00:00';
  if (typeof sDate === 'string') {
    sDate = (sDate + complateStr.slice(sDate.length)).replace(/-/g, '/');
  }
  if (typeof eDate === 'string') {
    eDate = (eDate + complateStr.slice(eDate.length)).replace(/-/g, '/');
  }
  return new Date(sDate) <= new Date(eDate);
};

/**
 * 空闲位自动补全0
 * @param num 数据
 * @param len 设定长度
 */
const autoFillZero = (num: number, len: number) => {
  return (Array(len).join('0') + num).slice(-len);
};

// 获取某年某月有多少天
const getCountDays = (year: number, month: number) => {
  // 获取某年某月多少天
  const day = new Date(year, month, 0);
  return day.getDate();
};

// 获取最小时间范围
const getMinDateLimit = computed(() => {
  return getDateByFormat(
    props.minDate && props.minDate.length >= 0
      ? props.minDate
      : `${new Date().getFullYear() - 10}-01-01 00:00:00`,
    props.format
  );
});

// 获取最大时间范围
const getMaxDateLimit = computed(() => {
  const thisMax = getDateByFormat(
    props.maxDate && props.maxDate.length >= 0
      ? props.maxDate
      : `${new Date().getFullYear() + 10}-12-31 23:59:59`,
    props.format
  );
  const tempStr = '0000-12-31 23:59:59';
  const result =
    props.maxDate.length !== 0 && thisMax.length > props.maxDate.length
      ? thisMax.slice(0, props.maxDate.length) + tempStr.slice(props.maxDate.length)
      : thisMax;
  return result.slice(0, props.format.length);
});
function onChange({ selectedValues, columnIndex }) {
  // 更新渲染变化的列之后的所有列（columnIndex）
  const splitArr = ['-', '-', ' ', ':', ':'];
  let iiindex = -1;
  const changeValue = selectedValues.reduce((a, b) => {
    iiindex += 1;
    return a + splitArr[iiindex] + b;
  });
  // 当前列变化后此时此刻时间
  // 更新当前列之后的所有列（包括当前列）
  const updateColumns = [
    renderYearColumns,
    renderMonthColumns,
    renderDayColumns,
    renderHourColumns,
    renderMinuteColumns,
    renderSecondColumns
  ];
  updateColumns[columnIndex] &&
    updateColumns[columnIndex](changeValue, getMinDateLimit.value, getMaxDateLimit.value, false);
}

// 渲染全部列
const getcolumns = () => {
  // 先清空全部列
  data.columns = [];
  // 清空年月日时分秒时间值
  data.tempSelectedValues = [];
  data.selectedValues = [];
  // 获取props.values转换成指定格式后的日期时间
  const defaultDateTime = getDateByFormat(props.values, props.format);
  let usefullDateTime = defaultDateTime;
  if (!dateRangeLegal(getMinDateLimit.value, defaultDateTime)) {
    usefullDateTime = getMinDateLimit.value;
  } else if (!dateRangeLegal(defaultDateTime, getMaxDateLimit.value)) {
    usefullDateTime = getMaxDateLimit.value;
  }
  // 渲染修正“年”列
  renderYearColumns(usefullDateTime, getMinDateLimit.value, getMaxDateLimit.value, true);
};
/**
 * 渲染年所在列，并自动修正在“年”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderYearColumns = (v: string, s: string, e: string, isFirst: boolean) => {
  // 设置年范围
  // 获取前后十年数组
  const listArr: any = [];
  const Currentday = new Date().getFullYear();
  // 最小月份
  let forStart = 2020;
  // 最小月份
  let forEnd = 2040;
  if (s && e) {
    const startYearLimit = s.slice(0, 4);
    const endYearLimit = e.slice(0, 4);
    // 如果最小、最大日期设定的范围错误
    if (!dateRangeLegal(s, e)) {
      forStart = Currentday - 10;
      forEnd = Currentday + 10;
    } else {
      if (startYearLimit === endYearLimit) {
        forStart = Number(startYearLimit);
        forEnd = Number(startYearLimit);
      } else {
        forStart = Number(startYearLimit);
        forEnd = Number(endYearLimit);
      }
    }
  } else if (s) {
    const startYearLimit = s.slice(0, 4);
    if (Currentday <= Number(startYearLimit)) {
      forStart = Number(startYearLimit);
      forEnd = Number(startYearLimit) + 10;
    } else {
      forStart = Number(startYearLimit);
      forEnd = Currentday + 10;
    }
  } else if (e) {
    const endYearLimit = e.slice(0, 4);
    if (Currentday <= Number(endYearLimit)) {
      forStart = Currentday - 10;
      forEnd = Number(endYearLimit);
    } else {
      forStart = Number(endYearLimit) - 10;
      forEnd = Number(endYearLimit);
    }
  } else {
    forStart = Currentday - 10;
    forEnd = Currentday + 10;
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: `${m}${props.units[0] || ''}`,
      value: m.toString()
    });
  }

  // 判断当前“年”是否在以上合理范围之内
  const thisYear = Number(v.slice(0, 4));
  // 当前定位到的年份
  let vmValue: string | number = '';
  if (thisYear < forStart) {
    vmValue = forStart;
  } else if (thisYear > forEnd) {
    vmValue = forEnd;
  } else {
    // 范围正确
    vmValue = thisYear;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 4));
  } else {
    data.columns[0] = listArr;
    data.tempSelectedValues[0] = autoFillZero(vmValue, 4);
  }

  if (props.format.length >= 7) {
    // 至少是“YYYY-MM”格式，则渲染“月”
    // 根据当前年渲染“月”这一列
    renderMonthColumns(autoFillZero(vmValue, 4) + v.slice(4), s, e, isFirst);
  } else {
    // 确定选择结果
    data.selectedValues = [...data.tempSelectedValues];
  }
};
/**
 * 渲染月所在列，并自动修正在“月”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderMonthColumns = (
  v: string,
  s: string,
  e: string,
  isFirst: boolean,
  outRangeParam: boolean = false
) => {
  let outRange = outRangeParam;
  const thisY = Number(v.slice(0, 4));
  const thisM = Number(v.slice(5, 7));
  const minY = Number(s.slice(0, 4));
  const maxY = Number(e.slice(0, 4));
  const listArr: any = [];
  let forStart = -1;
  let forEnd = -1;
  if (thisY === minY && thisY === maxY) {
    forStart = Number(s.slice(5, 7));
    forEnd = Number(e.slice(5, 7));
  } else if (thisY === minY) {
    forStart = Number(s.slice(5, 7));
    forEnd = 12;
  } else if (thisY === maxY) {
    forStart = 1;
    forEnd = Number(e.slice(5, 7));
  } else {
    forStart = 1;
    forEnd = 12;
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: autoFillZero(m, 2) + (props.units[1] || ''),
      value: autoFillZero(m, 2)
    });
  }
  // 判断当前月是否在此范围之内
  let vmValue: string | number = '';
  if (thisM < forStart || outRange) {
    vmValue = forStart;
    outRange = true;
  } else if (thisM > forEnd) {
    vmValue = forEnd;
    outRange = true;
  } else {
    // 范围正确
    vmValue = thisM;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 2));
  } else {
    data.columns[1] = listArr;
    data.tempSelectedValues[1] = autoFillZero(vmValue, 2);
  }

  if (props.format.length >= 10) {
    // 至少是“YYYY-MM-DD”格式，则渲染“日”
    // 根据当前年渲染“日”这一列
    renderDayColumns(v.slice(0, 5) + autoFillZero(vmValue, 2) + v.slice(7), s, e, isFirst);
  } else {
    // 确定选择结果
    data.selectedValues = [...data.tempSelectedValues];
  }
};
/**
 * 渲染日所在列，并自动修正在“日”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderDayColumns = (
  v: string,
  s: string,
  e: string,
  isFirst: boolean,
  outRangeParam: boolean = false
) => {
  let outRange = outRangeParam;
  const thisYM = v.slice(0, 7);
  const thisD = Number(v.slice(8, 10));
  const startYM = s.slice(0, 7);
  const endYM = e.slice(0, 7);
  const listArr: any = [];
  let forStart = -1;
  let forEnd = -1;
  if (thisYM === startYM && thisYM === endYM) {
    forStart = Number(s.slice(8, 10));
    forEnd = Number(e.slice(8, 10));
  } else if (thisYM === startYM) {
    forStart = Number(s.slice(8, 10));
    forEnd = getCountDays(Number(v.slice(0, 4)), Number(v.slice(5, 7)));
  } else if (thisYM === endYM) {
    forStart = 1;
    forEnd = Number(e.slice(8, 10));
  } else {
    forStart = 1;
    forEnd = getCountDays(Number(v.slice(0, 4)), Number(v.slice(5, 7)));
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: autoFillZero(m, 2) + (props.units[2] || ''),
      value: autoFillZero(m, 2)
    });
  }
  // 判断当前日是否在此范围之内
  let vmValue: string | number = '';
  if (thisD < forStart || outRange) {
    vmValue = forStart;
    outRange = true;
  } else if (thisD > forEnd) {
    vmValue = forEnd;
    outRange = true;
  } else {
    // 范围正确
    vmValue = thisD;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 2));
  } else {
    data.columns[2] = listArr;
    data.tempSelectedValues[2] = autoFillZero(vmValue, 2);
  }

  if (props.format.length >= 13) {
    // 至少是“YYYY-MM-DD HH”格式，则渲染“时”
    // 根据当前年渲染“日”这一列
    renderHourColumns(v.slice(0, 8) + autoFillZero(vmValue, 2) + v.slice(10), s, e, isFirst);
  } else {
    // 确定选择结果
    data.selectedValues = [...data.tempSelectedValues];
  }
};
/**
 * 渲染小时所在列，并自动修正在“时”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderHourColumns = (
  v: string,
  s: string,
  e: string,
  isFirst: boolean,
  outRangeParam: boolean = false
) => {
  // 避免直接修改参数
  let outRange = outRangeParam;
  // 获取当前年月日
  const thisYMD = v.slice(0, 10);
  // 开始时间临界值
  const startYMD = s.slice(0, 10);
  // 结束时间临界值
  const endYMD = e.slice(0, 10);
  // 获取当前小时
  const thisH = Number(v.slice(11, 13));
  const listArr: any = [];
  let forStart = -1;
  let forEnd = -1;
  if (thisYMD === startYMD && thisYMD === endYMD) {
    forStart = Number(s.slice(11, 13));
    forEnd = Number(e.slice(11, 13));
  } else if (thisYMD === startYMD) {
    forStart = Number(s.slice(11, 13));
    forEnd = 23;
  } else if (thisYMD === endYMD) {
    forStart = 0;
    forEnd = Number(e.slice(11, 13));
  } else {
    forStart = 0;
    forEnd = 23;
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: autoFillZero(m, 2) + (props.units[3] || ''),
      value: autoFillZero(m, 2)
    });
  }
  // 判断当前小时是否在此范围之内
  let vmValue: string | number = '';
  if (thisH < forStart || outRange) {
    vmValue = forStart;
    outRange = true;
  } else if (thisH > forEnd) {
    vmValue = forEnd;
    outRange = true;
  } else {
    // 范围正确
    vmValue = thisH;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 2));
  } else {
    data.columns[3] = listArr;
    data.tempSelectedValues[3] = autoFillZero(vmValue, 2);
  }

  if (props.format.length >= 16) {
    // 至少是“YYYY-MM-DD HH:mm”格式，则渲染“分”
    // 根据当前年渲染“分”这一列
    renderMinuteColumns(v.slice(0, 11) + autoFillZero(vmValue, 2) + v.slice(13), s, e, isFirst);
  } else {
    // 确定选择结果
    data.selectedValues = [...data.tempSelectedValues];
  }
};
/**
 * 渲染分钟所在列，并自动修正在“分”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderMinuteColumns = (
  v: string,
  s: string,
  e: string,
  isFirst: boolean,
  outRangeParam: boolean = false
) => {
  // 避免直接修改参数
  let outRange = outRangeParam;
  // 获取当前年月日小时
  const thisYMDH = v.slice(0, 13);
  // 开始时间临界值
  const startYMDH = s.slice(0, 13);
  // 结束时间临界值
  const endYMDH = e.slice(0, 13);
  const thisM = Number(v.slice(14, 16));
  const listArr: any = [];
  let forStart = -1;
  let forEnd = -1;
  if (thisYMDH === startYMDH && thisYMDH === endYMDH) {
    forStart = Number(s.slice(14, 16));
    forEnd = Number(e.slice(14, 16));
  } else if (thisYMDH === startYMDH) {
    forStart = Number(s.slice(14, 16));
    forEnd = 59;
  } else if (thisYMDH === endYMDH) {
    forStart = 0;
    forEnd = Number(e.slice(14, 16));
  } else {
    forStart = 0;
    forEnd = 59;
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: autoFillZero(m, 2) + (props.units[4] || ''),
      value: autoFillZero(m, 2)
    });
  }
  // 判断当前小时是否在此范围之内
  let vmValue: string | number = '';
  if (thisM < forStart || outRange) {
    vmValue = forStart;
    outRange = true;
  } else if (thisM > forEnd) {
    vmValue = forEnd;
    outRange = true;
  } else {
    // 范围正确
    vmValue = thisM;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 2));
  } else {
    data.columns[4] = listArr;
    data.tempSelectedValues[4] = autoFillZero(vmValue, 2);
  }

  if (props.format.length === 19) {
    // 至少是“YYYY-MM-DD HH:mm:ss”格式，则渲染“秒”
    // 根据当前年渲染“秒”这一列
    renderSecondColumns(v.slice(0, 14) + autoFillZero(vmValue, 2) + v.slice(16), s, e, isFirst);
  } else {
    // 确定选择结果
    data.selectedValues = [...data.tempSelectedValues];
  }
};
/**
 * 渲染秒钟所在列，并自动修正在“秒”这一列列范围之外的数据
 * @param v 需要做比较的数据，dateFormat格式
 * @param s 比较条件，开始时间临界值，dateFormat格式
 * @param e 比较条件，结束时间临界值，dateFormat格式
 * @param isFirst 是否是第一次渲染,需要定位到当前
 * @param outRange 是否超出范围之外
 */
const renderSecondColumns = (
  v: string,
  s: string,
  e: string,
  isFirst: boolean,
  outRangeParam: boolean = false
) => {
  let outRange = outRangeParam;
  const thisYMDHM = v.slice(0, 16);
  const startYMDHM = s.slice(0, 16);
  const endYMDHM = e.slice(0, 16);
  const thisS = Number(v.slice(17, 19));
  const listArr: any = [];
  let forStart = -1;
  let forEnd = -1;
  if (thisYMDHM === startYMDHM && thisYMDHM === endYMDHM) {
    forStart = Number(s.slice(17, 19));
    forEnd = Number(e.slice(17, 19));
  } else if (thisYMDHM === startYMDHM) {
    forStart = Number(s.slice(17, 19));
    forEnd = 59;
  } else if (thisYMDHM === endYMDHM) {
    forStart = 0;
    forEnd = Number(e.slice(17, 19));
  } else {
    forStart = 0;
    forEnd = 59;
  }
  for (let m = forStart; m <= forEnd; m++) {
    listArr.push({
      text: autoFillZero(m, 2) + (props.units[5] || ''),
      value: autoFillZero(m, 2)
    });
  }
  // 判断当前小时是否在此范围之内
  let vmValue: string | number = '';
  if (thisS < forStart || outRange) {
    vmValue = forStart;
    outRange = true;
  } else if (thisS > forEnd) {
    vmValue = forEnd;
    outRange = true;
  } else {
    // 范围正确
    vmValue = thisS;
  }
  // 插入/更新到data中
  if (isFirst) {
    data.columns.push(listArr);
    data.tempSelectedValues.push(autoFillZero(vmValue, 2));
  } else {
    data.columns[5] = listArr;
    data.tempSelectedValues[5] = autoFillZero(vmValue, 2);
  }
  // 确定选择结果
  data.selectedValues = [...data.tempSelectedValues];
};

watch(
  () => props.showPicker,
  (val) => {
    data.isPicker = val;
    if (val) {
      // console.log("当前最大最小值判断结果", getMinDateLimit.value, getMaxDateLimit.value);
      // 每次显示前重新渲染全部列
      getcolumns();
    }
  },
  {
    immediate: true
  }
);

// 时间选择器关闭 值不改变并关闭弹框
function cancelOn() {
  emit('cancel');
}

// 时间选择器确定 值改变
function onConfirm() {
  // 注意：data.selectedValues比selectedValues更准确
  // 拼接数据
  const splitArr = ['-', '-', ' ', ':', ':'];
  let iiindex = -1;
  const changeValue = data.selectedValues.reduce((a, b) => {
    iiindex += 1;
    return a + splitArr[iiindex] + b;
  });
  emit('confirm', changeValue);
}
</script>
