Showing
8 changed files
with
413 additions
and
0 deletions
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { ref, computed, Ref, unref } from 'vue'; | ||
| 3 | + import { Select, Switch, InputNumber } from 'ant-design-vue'; | ||
| 4 | + import { latestOptions, LatestOptionsEnum } from './config'; | ||
| 5 | + import { dateUtil } from '/@/utils/dateUtil'; | ||
| 6 | + | ||
| 7 | + type DateUnit = 'days' | 'hours' | 'minutes' | 'seconds'; | ||
| 8 | + | ||
| 9 | + type RangeObject = Record<DateUnit, number>; | ||
| 10 | + | ||
| 11 | + const emit = defineEmits(['update:value']); | ||
| 12 | + | ||
| 13 | + const props = withDefaults( | ||
| 14 | + defineProps<{ | ||
| 15 | + value?: number; | ||
| 16 | + }>(), | ||
| 17 | + { | ||
| 18 | + value: 1000 * 60 * 60 * 24 * 30, | ||
| 19 | + } | ||
| 20 | + ); | ||
| 21 | + | ||
| 22 | + const advancedMode = ref(false); | ||
| 23 | + | ||
| 24 | + const getRangeObject = (milliseconds: number) => { | ||
| 25 | + const methods: { method: string; key: moment.unitOfTime.Base }[] = [ | ||
| 26 | + { method: 'asDays', key: 'days' }, | ||
| 27 | + { method: 'asHours', key: 'hours' }, | ||
| 28 | + { method: 'asMinutes', key: 'minutes' }, | ||
| 29 | + { method: 'asSeconds', key: 'seconds' }, | ||
| 30 | + ]; | ||
| 31 | + | ||
| 32 | + return methods.reduce((prev, next) => { | ||
| 33 | + const { method, key } = next; | ||
| 34 | + const value = dateUtil.duration(milliseconds, 'millisecond')[method]?.(); | ||
| 35 | + | ||
| 36 | + if (value >= 1) { | ||
| 37 | + milliseconds -= dateUtil.duration(parseInt(value), key).asMilliseconds(); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + return { ...prev, [key]: value >= 1 ? parseInt(value) : 0 }; | ||
| 41 | + }, {} as Record<DateUnit, number>); | ||
| 42 | + }; | ||
| 43 | + | ||
| 44 | + const getMillisecondsByObject = (value: RangeObject | Ref<RangeObject>) => { | ||
| 45 | + value = unref(value); | ||
| 46 | + return Object.keys(unref(value)).reduce((prev, next) => { | ||
| 47 | + return ( | ||
| 48 | + prev + | ||
| 49 | + dateUtil | ||
| 50 | + .duration(value[next], next as moment.unitOfTime.DurationConstructor) | ||
| 51 | + .asMilliseconds() | ||
| 52 | + ); | ||
| 53 | + }, 0); | ||
| 54 | + }; | ||
| 55 | + | ||
| 56 | + const getValue = computed(() => { | ||
| 57 | + const { value } = props; | ||
| 58 | + const dateRange = getRangeObject(value); | ||
| 59 | + const { days, hours, minutes, seconds } = dateRange; | ||
| 60 | + | ||
| 61 | + const filterList = latestOptions.filter((item) => value >= item.value); | ||
| 62 | + return { | ||
| 63 | + days, | ||
| 64 | + hours, | ||
| 65 | + minutes, | ||
| 66 | + seconds, | ||
| 67 | + selectValue: filterList[filterList.length - 1].value, | ||
| 68 | + }; | ||
| 69 | + }); | ||
| 70 | + | ||
| 71 | + const handleSyncValue = (value: LatestOptionsEnum) => { | ||
| 72 | + emit('update:value', value); | ||
| 73 | + }; | ||
| 74 | + | ||
| 75 | + const handleChange = (value: number, unit: DateUnit) => { | ||
| 76 | + if (isNaN(value)) return; | ||
| 77 | + let { days, hours, minutes, seconds } = unref(getValue); | ||
| 78 | + const dateRangeObject: RangeObject = { | ||
| 79 | + days, | ||
| 80 | + hours, | ||
| 81 | + minutes, | ||
| 82 | + seconds, | ||
| 83 | + [unit]: value, | ||
| 84 | + }; | ||
| 85 | + | ||
| 86 | + const milliseconds = getMillisecondsByObject(dateRangeObject); | ||
| 87 | + emit('update:value', milliseconds); | ||
| 88 | + }; | ||
| 89 | +</script> | ||
| 90 | + | ||
| 91 | +<template> | ||
| 92 | + <section class="flex justify-center items-end gap-4 h-14 latest-date-picker"> | ||
| 93 | + <section v-if="advancedMode" class="flex flex-auto custom-date-range"> | ||
| 94 | + <div> | ||
| 95 | + <div>天</div> | ||
| 96 | + <InputNumber | ||
| 97 | + :value="getValue.days" | ||
| 98 | + @change="(event) => handleChange(event, 'days')" | ||
| 99 | + :step="1" | ||
| 100 | + :max="7300" | ||
| 101 | + :min="0" | ||
| 102 | + /> | ||
| 103 | + </div> | ||
| 104 | + <div> | ||
| 105 | + <div>小时</div> | ||
| 106 | + <InputNumber | ||
| 107 | + :value="getValue.hours" | ||
| 108 | + @change="(event) => handleChange(event, 'hours')" | ||
| 109 | + :step="1" | ||
| 110 | + :max="23" | ||
| 111 | + :min="0" | ||
| 112 | + /> | ||
| 113 | + </div> | ||
| 114 | + <div> | ||
| 115 | + <div>分钟</div> | ||
| 116 | + <InputNumber | ||
| 117 | + :value="getValue.minutes" | ||
| 118 | + @change="(event) => handleChange(event, 'minutes')" | ||
| 119 | + :step="1" | ||
| 120 | + :max="59" | ||
| 121 | + :min="0" | ||
| 122 | + /> | ||
| 123 | + </div> | ||
| 124 | + <div> | ||
| 125 | + <div>秒</div> | ||
| 126 | + <InputNumber | ||
| 127 | + :value="getValue.seconds" | ||
| 128 | + @change="(event) => handleChange(event, 'seconds')" | ||
| 129 | + :step="1" | ||
| 130 | + :max="59" | ||
| 131 | + :min="0" | ||
| 132 | + /> | ||
| 133 | + </div> | ||
| 134 | + </section> | ||
| 135 | + <Select | ||
| 136 | + v-if="!advancedMode" | ||
| 137 | + class="!flex-auto" | ||
| 138 | + v-model:value="getValue.selectValue" | ||
| 139 | + :options="latestOptions" | ||
| 140 | + placeholder="请选择最后时间段" | ||
| 141 | + @change="handleSyncValue" | ||
| 142 | + allow-clear | ||
| 143 | + /> | ||
| 144 | + <div class="w-14 flex flex-col justify-between h-full text-center"> | ||
| 145 | + <div>高级</div> | ||
| 146 | + <Switch class="w-14" v-model:checked="advancedMode" /> | ||
| 147 | + </div> | ||
| 148 | + </section> | ||
| 149 | +</template> | ||
| 150 | + | ||
| 151 | +<style scoped lang="less"> | ||
| 152 | + .latest-date-picker { | ||
| 153 | + :deep(.ant-input-number) { | ||
| 154 | + min-width: 0; | ||
| 155 | + width: 100%; | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + :deep(.ant-select-selection-item) { | ||
| 159 | + pointer-events: none; | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | +</style> | 
| 1 | +import { dateUtil } from '/@/utils/dateUtil'; | ||
| 2 | + | ||
| 3 | +export enum ModeEnum { | ||
| 4 | + LATEST = 'LATEST', | ||
| 5 | + RANGE = 'RANGE', | ||
| 6 | + INTERVAL = 'INTERVAL', | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +export enum ModeNameEnum { | ||
| 10 | + LATEST = '最后', | ||
| 11 | + RANGE = '时间段', | ||
| 12 | + INTERVAL = '时间间隔', | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +export enum UnitEnum { | ||
| 16 | + SECONDS = '秒', | ||
| 17 | + MINUTES = '分', | ||
| 18 | + HOURS = '时', | ||
| 19 | + DAYS = '天', | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +export enum LatestOptionsEnum { | ||
| 23 | + '1_SECONDS' = '1_SECONDS', | ||
| 24 | + '5_SECONDS' = '5_SECONDS', | ||
| 25 | + '10_SECONDS' = '10_SECONDS', | ||
| 26 | + '15_SECONDS' = '15_SECONDS', | ||
| 27 | + '30_SECONDS' = '30_SECONDS', | ||
| 28 | + | ||
| 29 | + '1_MINUTES' = '1_MINUTES', | ||
| 30 | + '2_MINUTES' = '2_MINUTES', | ||
| 31 | + '5_MINUTES' = '5_MINUTES', | ||
| 32 | + '10_MINUTES' = '10_MINUTES', | ||
| 33 | + '15_MINUTES' = '15_MINUTES', | ||
| 34 | + '30_MINUTES' = '30_MINUTES', | ||
| 35 | + | ||
| 36 | + '1_HOURS' = '1_HOURS', | ||
| 37 | + '2_HOURS' = '2_HOURS', | ||
| 38 | + '5_HOURS' = '5_HOURS', | ||
| 39 | + '10_HOURS' = '10_HOURS', | ||
| 40 | + '12_HOURS' = '12_HOURS', | ||
| 41 | + | ||
| 42 | + '1_DAYS' = '1_DAYS', | ||
| 43 | + '7_DAYS' = '7_DAYS', | ||
| 44 | + '30_DAYS' = '30_DAYS', | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +const getLatesetOptionLabel = (string: string) => { | ||
| 48 | + const [value, unit] = string.split('_'); | ||
| 49 | + return `${value} ${UnitEnum[unit]}`; | ||
| 50 | +}; | ||
| 51 | + | ||
| 52 | +const getLatesetOptionValue = (string: string) => { | ||
| 53 | + const [value, unit] = string.split('_'); | ||
| 54 | + return dateUtil | ||
| 55 | + .duration(Number(value), unit.toLowerCase() as moment.unitOfTime.DurationConstructor) | ||
| 56 | + .asMilliseconds(); | ||
| 57 | +}; | ||
| 58 | + | ||
| 59 | +export const latestOptions: { label: string; value: number }[] = Object.keys(LatestOptionsEnum).map( | ||
| 60 | + (key) => ({ label: getLatesetOptionLabel(key), value: getLatesetOptionValue(key) }) | ||
| 61 | +); | ||
| 62 | + | ||
| 63 | +[ | ||
| 64 | + 'Yesterday', | ||
| 65 | + 'Day before yesterday', | ||
| 66 | + 'This day last week', | ||
| 67 | + 'Previous week (Sun - Sat)', | ||
| 68 | + 'Previous week (Mon - Sun)', | ||
| 69 | + 'Previous month', | ||
| 70 | + 'Previous year', | ||
| 71 | + 'Current hour', | ||
| 72 | + 'Current day', | ||
| 73 | + 'Current day so far', | ||
| 74 | + 'Current week (Sun - Sat)', | ||
| 75 | + 'Current week (Mon - Sun)', | ||
| 76 | + 'Current week so far (Sun - Sat)', | ||
| 77 | + 'Current week so far (Mon - Sun)', | ||
| 78 | + 'Current month', | ||
| 79 | + 'Current month so far', | ||
| 80 | + 'Current year', | ||
| 81 | + 'Current year so far', | ||
| 82 | +]; | 
| 1 | +export { default as DateRangeButton } from './index.vue'; | 
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { Popconfirm, Button, Radio, RadioGroup, RangePicker } from 'ant-design-vue'; | ||
| 3 | + import { ref } from 'vue'; | ||
| 4 | + import { Icon } from '/@/components/Icon'; | ||
| 5 | + import { ModeEnum, ModeNameEnum } from './config'; | ||
| 6 | + import LatestDate from './LatestDate.vue'; | ||
| 7 | + | ||
| 8 | + const mode = ref(ModeEnum.LATEST); | ||
| 9 | + | ||
| 10 | + const visible = ref(true); | ||
| 11 | + | ||
| 12 | + const latestValue = ref<number>(1000 * 60 * 60 * 24 * 30); | ||
| 13 | +</script> | ||
| 14 | + | ||
| 15 | +<template> | ||
| 16 | + <Popconfirm :visible="visible"> | ||
| 17 | + <template #icon> | ||
| 18 | + <span></span> | ||
| 19 | + </template> | ||
| 20 | + <template #title> | ||
| 21 | + <RadioGroup v-model:value="mode" class="!flex flex-col gap-2 w-96"> | ||
| 22 | + <Radio :value="ModeEnum.LATEST"> | ||
| 23 | + <span>{{ ModeNameEnum.LATEST }}</span> | ||
| 24 | + <LatestDate v-model:value="latestValue" v-if="mode === ModeEnum.LATEST" /> | ||
| 25 | + </Radio> | ||
| 26 | + <Radio :value="ModeEnum.RANGE"> | ||
| 27 | + <span>{{ ModeNameEnum.RANGE }}</span> | ||
| 28 | + <section v-if="mode === ModeEnum.RANGE" class="mt-2 flex flex-col justify-end"> | ||
| 29 | + <RangePicker :showTime="true" /> | ||
| 30 | + </section> | ||
| 31 | + </Radio> | ||
| 32 | + <Radio :value="ModeEnum.INTERVAL"> | ||
| 33 | + <span>{{ ModeNameEnum.INTERVAL }}</span> | ||
| 34 | + <section class="mt-2 flex flex-col justify-end"> | ||
| 35 | + <!-- --> | ||
| 36 | + </section> | ||
| 37 | + </Radio> | ||
| 38 | + </RadioGroup> | ||
| 39 | + </template> | ||
| 40 | + <Button type="primary" @click="visible = true"> | ||
| 41 | + <Icon icon="mdi:clock-outline" /> | ||
| 42 | + </Button> | ||
| 43 | + </Popconfirm> | ||
| 44 | +</template> | 
| 1 | +<script lang="ts" setup> | ||
| 2 | + import { useTable, BasicTable } from '/@/components/Table'; | ||
| 3 | + import { columns, formSchemas } from './debugEvent.config'; | ||
| 4 | + | ||
| 5 | + const [register] = useTable({ | ||
| 6 | + columns, | ||
| 7 | + showIndexColumn: false, | ||
| 8 | + useSearchForm: true, | ||
| 9 | + formConfig: { | ||
| 10 | + layout: 'inline', | ||
| 11 | + baseColProps: { | ||
| 12 | + span: 12, | ||
| 13 | + }, | ||
| 14 | + labelWidth: 80, | ||
| 15 | + schemas: formSchemas, | ||
| 16 | + }, | ||
| 17 | + }); | ||
| 18 | +</script> | ||
| 19 | + | ||
| 20 | +<template> | ||
| 21 | + <BasicTable @register="register" /> | ||
| 22 | +</template> | 
| 1 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | ||
| 2 | + | ||
| 3 | +export const columns: BasicColumn[] = [ | ||
| 4 | + { | ||
| 5 | + title: '事件时间', | ||
| 6 | + dataIndex: '', | ||
| 7 | + }, | ||
| 8 | + { | ||
| 9 | + title: '服务器', | ||
| 10 | + dataIndex: '', | ||
| 11 | + }, | ||
| 12 | + { | ||
| 13 | + title: '类型', | ||
| 14 | + dataIndex: '', | ||
| 15 | + }, | ||
| 16 | + { | ||
| 17 | + title: '实体类型', | ||
| 18 | + dataIndex: '', | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + title: '消息ID', | ||
| 22 | + dataIndex: '', | ||
| 23 | + }, | ||
| 24 | + { | ||
| 25 | + title: '消息类型', | ||
| 26 | + dataIndex: '', | ||
| 27 | + }, | ||
| 28 | + { | ||
| 29 | + title: '关联类型', | ||
| 30 | + dataIndex: '', | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + title: '数据', | ||
| 34 | + dataIndex: '', | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + title: '原数据', | ||
| 38 | + dataIndex: '', | ||
| 39 | + }, | ||
| 40 | + { | ||
| 41 | + title: '错误', | ||
| 42 | + dataIndex: '', | ||
| 43 | + }, | ||
| 44 | +]; | ||
| 45 | + | ||
| 46 | +export const formSchemas: FormSchema[] = [ | ||
| 47 | + { | ||
| 48 | + field: '', | ||
| 49 | + label: '服务器', | ||
| 50 | + component: 'Input', | ||
| 51 | + }, | ||
| 52 | + { | ||
| 53 | + field: '', | ||
| 54 | + label: '类型', | ||
| 55 | + component: 'Select', | ||
| 56 | + }, | ||
| 57 | + { | ||
| 58 | + field: '', | ||
| 59 | + label: '实体ID', | ||
| 60 | + component: 'Input', | ||
| 61 | + }, | ||
| 62 | + { | ||
| 63 | + field: '', | ||
| 64 | + label: '实体类型', | ||
| 65 | + component: 'Select', | ||
| 66 | + }, | ||
| 67 | + { | ||
| 68 | + field: '', | ||
| 69 | + label: '消息类型', | ||
| 70 | + component: 'Input', | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + field: '', | ||
| 74 | + label: '关联类型', | ||
| 75 | + component: 'Input', | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + field: '', | ||
| 79 | + label: '数据', | ||
| 80 | + component: 'Input', | ||
| 81 | + }, | ||
| 82 | + { | ||
| 83 | + field: '', | ||
| 84 | + label: '元数据', | ||
| 85 | + component: 'Input', | ||
| 86 | + }, | ||
| 87 | + { | ||
| 88 | + field: '', | ||
| 89 | + label: '有错误', | ||
| 90 | + component: 'Checkbox', | ||
| 91 | + }, | ||
| 92 | +]; | 
| 1 | +export { default as BasicEvents } from './index.vue'; |