Procházet zdrojové kódy

feat(components): 编写“时间范围选择器”组件

1. 支持年、月、日和自定义类型
2. 支持最近 1、7、30 天和自定义类型
wangcong před 1 měsícem
rodič
revize
caa0039f1a
1 změnil soubory, kde provedl 180 přidání a 0 odebrání
  1. 180 0
      src/components/TimeRangeSelect.vue

+ 180 - 0
src/components/TimeRangeSelect.vue

@@ -0,0 +1,180 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import dayjs from 'dayjs';
+
+import { t } from '@/i18n';
+
+import type { PickerMode } from 'ant-design-vue/es/vc-picker/interface';
+import type { Dayjs, OpUnitType } from 'dayjs';
+import type { OptionItem, RangeValue } from '@/types';
+
+type DateType = 'year' | 'month' | 'date' | 'custom';
+type LastType = '1d' | '7d' | '30d' | 'custom';
+
+interface Props {
+  isLast?: boolean;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  isLast: false,
+});
+
+const emit = defineEmits<{
+  change: [range: RangeValue];
+}>();
+
+const selectedType = ref<DateType | LastType>(props.isLast ? '1d' : 'month');
+const singleDate = ref<Dayjs>();
+const rangeDate = ref<RangeValue>();
+
+const isCustomType = computed(() => {
+  return selectedType.value === 'custom';
+});
+
+const typeOptions = computed<OptionItem<DateType | LastType>[]>(() => {
+  if (props.isLast) {
+    return [
+      { value: '1d', label: t('common.recent1Day') },
+      { value: '7d', label: t('common.recent7Day') },
+      { value: '30d', label: t('common.recent30Day') },
+      { value: 'custom', label: t('common.custom') },
+    ];
+  }
+
+  return [
+    { value: 'year', label: t('common.year') },
+    { value: 'month', label: t('common.month') },
+    { value: 'date', label: t('common.date') },
+    { value: 'custom', label: t('common.custom') },
+  ];
+});
+
+onMounted(() => {
+  handleTypeChange();
+});
+
+const disabledDate = (current: Dayjs) => {
+  return current > dayjs().endOf('day');
+};
+
+const handleTypeChange = () => {
+  if (props.isLast) {
+    handleLastTypeChange();
+  } else {
+    handleNormalTypeChange();
+  }
+};
+
+const handleNormalTypeChange = () => {
+  const now = dayjs();
+
+  if (isCustomType.value) {
+    if (!rangeDate.value) {
+      rangeDate.value = [now.startOf('month'), now.endOf('day')];
+    }
+
+    handleRangeChange();
+  } else {
+    if (!singleDate.value) {
+      singleDate.value = now;
+    }
+
+    handleDateChange();
+  }
+};
+
+const handleLastTypeChange = () => {
+  if (!isCustomType.value) {
+    const days = parseInt(selectedType.value);
+    const end = dayjs();
+    const start = end.subtract(days, 'day');
+    rangeDate.value = [start, end];
+    emitChange(start, end);
+  }
+};
+
+const handleDateChange = () => {
+  if (!singleDate.value) {
+    return;
+  }
+
+  const type = selectedType.value as OpUnitType;
+  emitChange(singleDate.value.startOf(type), singleDate.value.endOf(type));
+};
+
+const handleRangeChange = () => {
+  if (!rangeDate.value) {
+    return;
+  }
+
+  selectedType.value = 'custom';
+  emitChange(rangeDate.value[0].startOf('day'), rangeDate.value[1].endOf('day'));
+};
+
+const emitChange = (start: Dayjs, end: Dayjs) => {
+  emit('change', [start, end]);
+};
+</script>
+
+<template>
+  <div class="time-select-container">
+    <span class="time-select-label">{{ $t('common.selectTime') }}</span>
+    <ASelect class="time-select-type" v-model:value="selectedType" @change="handleTypeChange">
+      <ASelectOption v-for="item in typeOptions" :key="item.value" :value="item.value">
+        {{ item.label }}
+      </ASelectOption>
+    </ASelect>
+    <ADatePicker
+      v-show="!isLast && !isCustomType"
+      v-model:value="singleDate"
+      :picker="<PickerMode>selectedType"
+      :allow-clear="false"
+      :disabled-date="disabledDate"
+      @change="handleDateChange"
+    >
+      <template #suffixIcon>
+        <SvgIcon name="calendar" color="#333" />
+      </template>
+    </ADatePicker>
+    <ARangePicker
+      class="time-select-range-picker"
+      v-show="isLast || isCustomType"
+      v-model:value="rangeDate"
+      :allow-clear="false"
+      :separator="$t('common.to')"
+      :disabled-date="disabledDate"
+      @change="handleRangeChange"
+    >
+      <template #suffixIcon>
+        <SvgIcon name="calendar" color="#333" />
+      </template>
+    </ARangePicker>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.time-select-container {
+  display: inline-flex;
+  align-items: center;
+}
+
+.time-select-label {
+  margin-right: 16px;
+  font-size: 14px;
+  line-height: 22px;
+  color: #000;
+}
+
+.time-select-type {
+  width: 110px;
+  margin-right: 12px;
+}
+
+.time-select-range-picker {
+  width: 256px;
+
+  :deep(.ant-picker-range-separator) {
+    padding-right: 16px;
+  }
+}
+</style>