@blocklet/ui-react
Version:
Some useful front-end web components that can be used in Blocklets.
225 lines (203 loc) • 6.3 kB
text/typescript
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc';
import { DurationEnum, UserMetadata } from '../../../@types';
// 扩展 dayjs 插件
dayjs.extend(utc);
dayjs.extend(timezone);
const HOUR = 3600;
const MINUTES_30 = 1800;
const MINUTES_10 = 600;
const MINUTES_5 = 300;
const MINUTES_1 = 60;
const SECOND = 1;
export const currentTimezone = dayjs.tz.guess();
// 常用时区列表,作为兼容性 fallback
const COMMON_TIMEZONES = [
'America/New_York',
'America/Chicago',
'America/Denver',
'America/Los_Angeles',
'Europe/London',
'Europe/Paris',
'Europe/Berlin',
'Europe/Rome',
'Asia/Tokyo',
'Asia/Shanghai',
'Asia/Hong_Kong',
'Asia/Singapore',
'Asia/Seoul',
'Asia/Kolkata',
'Australia/Sydney',
'Australia/Melbourne',
'Pacific/Auckland',
'America/Sao_Paulo',
'America/Mexico_City',
'Africa/Cairo',
'UTC',
];
// 获取时区列表的兼容性函数
const getTimezoneList = () => {
// 优先使用现代 API
if (typeof Intl !== 'undefined' && Intl.supportedValuesOf) {
try {
return Intl.supportedValuesOf('timeZone');
} catch (error) {
console.warn('Intl.supportedValuesOf not supported, falling back to common timezones');
}
}
// 尝试使用 Intl.DateTimeFormat 获取时区
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
try {
// 使用 resolvedOptions 检测当前时区是否可用
const formatter = new Intl.DateTimeFormat('en', { timeZone: 'UTC' });
if (formatter.resolvedOptions().timeZone) {
return COMMON_TIMEZONES;
}
} catch (error) {
console.warn('Intl.DateTimeFormat timezone support limited');
}
}
// 最后的 fallback
return COMMON_TIMEZONES;
};
export const getTimezones = () => {
const timezones = getTimezoneList();
const formattedTimezones = timezones
.map((tz) => {
try {
const offset = dayjs.tz(dayjs(), tz).utcOffset() / 60; // 计算 UTC 偏移 (小时)
const hours = Math.floor(offset);
const minutes = (offset % 1) * 60;
const label = `GMT${hours >= 0 ? '+' : ''}${hours}:${minutes === 30 ? '30' : '00'}`;
return { label, value: tz };
} catch (error) {
// 如果时区不支持,跳过
console.warn(`Timezone ${tz} not supported, skipping`);
return null;
}
})
.filter((tz): tz is { label: string; value: string } => tz !== null); // 类型守卫
return formattedTimezones
.sort((a, b) => {
const [hoursA, minutesA] = a.label.replace('GMT', '').split(':').map(Number);
const [hoursB, minutesB] = b.label.replace('GMT', '').split(':').map(Number);
const totalOffsetA = hoursA * 60 + minutesA; // 统一为分钟数
const totalOffsetB = hoursB * 60 + minutesB;
return totalOffsetB - totalOffsetA; // **降序排列**
})
.map((tz) => ({
label: `(${tz.label}) ${tz.value}`,
value: tz.value,
}));
};
export const isValidUrl = (url: string) => {
const urlPattern =
/^(https?:\/\/)?((([a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])*)\.)+[a-zA-Z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(\/[-a-zA-Z\d%_.~+]*)*(\?[;&a-zA-Z\d%_.~+=-]*)?(#[a-zA-Z\d_]*)?$/;
return urlPattern.test(url);
};
/**
* 根据 duration 类型,计算出date range
* @param status
* @returns
*/
export const getStatusDuration = (status: UserMetadata['status']) => {
let dateRange: dayjs.Dayjs[] = status?.dateRange?.map((d) => dayjs(d)) ?? [];
const current = dayjs();
switch (status?.duration) {
case DurationEnum.ThirtyMinutes:
dateRange = [current, current.add(30, 'minutes')];
break;
case DurationEnum.OneHour:
dateRange = [current, current.add(1, 'hour')];
break;
case DurationEnum.FourHours:
dateRange = [current, current.add(4, 'hours')];
break;
case DurationEnum.Today:
dateRange = [current, current.endOf('day')];
break;
case DurationEnum.ThisWeek:
dateRange = [current, current.endOf('week')];
break;
case DurationEnum.NoClear:
dateRange = [current, current];
break;
default:
break;
}
return dateRange.map((d) => d.toDate());
};
/**
* 根据状态的 duration,判断是否在时间范围内
* @param status
* @returns
*/
export const isWithinTimeRange = (dateRange: [Date, Date]) => {
const current = dayjs();
return current.isAfter(dayjs(dateRange[0])) && current.isBefore(dayjs(dateRange[1]));
};
/**
* 判断状态持续时间是否为不可清除
* @param status
* @returns
*/
export const isNotClear = (status: UserMetadata['status']) => {
const { duration, dateRange } = status ?? {};
if (!duration || !dateRange) {
return false;
}
return duration === DurationEnum.NoClear || dayjs(dateRange?.[0]).isSame(dayjs(dateRange?.[1]));
};
/**
* 获取当前时间距离结束时间还有多久
*/
export const getTimeRemaining = (date: Date) => {
const now = dayjs();
const end = dayjs(date);
const diffSeconds = end.diff(now, 'seconds');
// 转换为毫秒
const toMilliseconds = (seconds: number) => seconds * 1000;
if (diffSeconds >= HOUR) {
return toMilliseconds(HOUR); // 1小时 = 3600000ms
}
if (diffSeconds >= MINUTES_30) {
return toMilliseconds(MINUTES_30); // 30分钟 = 1800000ms
}
if (diffSeconds >= MINUTES_10) {
return toMilliseconds(MINUTES_10); // 10分钟 = 600000ms
}
if (diffSeconds >= MINUTES_5) {
return toMilliseconds(MINUTES_5); // 5分钟 = 300000ms
}
if (diffSeconds >= MINUTES_1) {
return toMilliseconds(MINUTES_1); // 1分钟 = 60000ms
}
if (diffSeconds >= SECOND) {
return toMilliseconds(SECOND); // 1秒 = 1000ms
}
return 0; // 如果时间已过期,返回0
};
// 只支持在 sx 中使用
export const defaultButtonStyle = {
color: 'text.primary',
borderColor: 'grey.100',
backgroundColor: 'background.default',
'&:hover': {
borderColor: 'grey.100',
backgroundColor: 'action.hover',
},
py: 0.5,
borderRadius: 1,
};
export const primaryButtonStyle = {
color: 'primary.contrastText',
borderColor: 'primary.main',
backgroundColor: 'primary.main',
'&:hover': {
borderColor: 'primary.main',
backgroundColor: 'primary.main',
},
py: 0.5,
borderRadius: 1,
};