This commit is contained in:
oneao committed 2026-01-13 22:18:01 +08:00
1 parent 6277fb2f9e
commit 8ecac0d7dd
270 files changed
+40438 -529

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
// utils/math.ts
import Big from 'big.js'
/**
* 加法(保留两位小数)
*/
export function add(a: string | number, b: string | number): string {
try {
return new Big(a).plus(new Big(b)).toFixed(2) // ⭐ 保留两位
}
catch {
return '0.00'
}
}
/**
* 减法(保留两位小数)
*/
export function sub(a: string | number, b: string | number): string {
try {
return new Big(a).minus(new Big(b)).toFixed(2) // ⭐ 保留两位
}
catch {
return '0.00'
}
}
+107
View File
@@ -0,0 +1,107 @@
import dayjs from 'dayjs'
/**
* 获取当前日期,默认格式: YYYY-MM-DD
*/
export function getCurrentDate(format = 'YYYY-MM-DD'): string {
return dayjs().format(format)
}
/**
* 获取当前日期时间,默认格式: YYYY-MM-DD HH:mm:ss
*/
export function getCurrentDateTime(format = 'YYYY-MM-DD HH:mm:ss'): string {
return dayjs().format(format)
}
/**
* 获取当前时间,默认格式: HH:mm:ss
*/
export function getCurrentTime(format = 'HH:mm:ss'): string {
return dayjs().format(format)
}
/**
* 获取当前年月,默认格式: YYYY-MM
*/
export function getCurrentYearMonth(format = 'YYYY-MM'): string {
return dayjs().format(format)
}
/** 字符串 / 数字 / Date → Date(无效返回 null) */
export function toDate(input: string | number | Date | undefined | null): Date {
if (!input)
return new Date()
if (input instanceof Date)
return input
const d = dayjs(input)
return d.isValid() ? d.toDate() : new Date()
}
/** Date → YYYY-MM-DD */
export function toDateString(date: string | number | Date): string {
const d = dayjs(toDate(date))
return d.isValid() ? d.format('YYYY-MM-DD') : ''
}
/** Date → YYYY-MM */
export function toYearMonthString(date: string | number | Date): string {
const d = dayjs(toDate(date))
return d.isValid() ? d.format('YYYY-MM') : ''
}
/** Date → HH:mm:ss */
export function toTimeString(date: string | number | Date): string {
const d = dayjs(toDate(date))
return d.isValid() ? d.format('HH:mm:ss') : ''
}
/** Date → YYYY-MM-DD HH:mm:ss */
export function toDateTimeString(date: string | number | Date): string {
const d = dayjs(toDate(date))
return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : ''
}
export function isInMonth(date: string, month: string): boolean {
// date 形如 "2025-01-12"
// month 形如 "2025-01"
return dayjs(date).format('YYYY-MM') === month
}
export function formatRelativeTime(date: string | Date | number): string {
if (typeof date === 'string' && !date.trim()) {
return '暂无活跃'
}
const d = dayjs(date)
if (!d.isValid())
return '暂无活跃'
const now = dayjs()
const diff = now.diff(d, 'second') // 相差秒数
if (diff < 60) {
return '刚刚'
}
else if (diff < 3600) {
const minutes = Math.floor(diff / 60)
return `${minutes}分钟前`
}
else if (diff < 86400) {
const hours = Math.floor(diff / 3600)
return `${hours}小时前`
}
else if (diff < 2592000) { // 30天以内
const days = Math.floor(diff / 86400)
return `${days}天前`
}
else if (diff < 31536000) { // 1年以内
const months = Math.floor(diff / 2592000)
return `${months}个月前`
}
else {
const years = Math.floor(diff / 31536000)
return `${years}年前`
}
}
+89
View File
@@ -0,0 +1,89 @@
import type { IToastProps } from '@gluestack-ui/core/lib/esm/toast/creator/types'
import React, { useEffect, useRef } from 'react'
import { Toast, ToastDescription, ToastTitle, useToast as useGluestackToast } from '@/components/ui/toast'
type ToastVariant = 'solid' | 'outline'
type ToastAction = 'error' | 'warning' | 'success' | 'info' | 'muted'
interface ShowToastOptions extends IToastProps {
title?: React.ReactNode
description?: React.ReactNode
variant?: ToastVariant
type?: ToastAction
}
// 全局 toast 实例
let globalToast: {
show: (options: ShowToastOptions) => string
close: (id: string) => void
closeAll: () => void
isActive: (id: string) => boolean
} | null = null
export function ToastProviderWrapper({ children }: { children: React.ReactNode }) {
const toast = useGluestackToast()
const toastRef = useRef(toast)
toastRef.current = toast
useEffect(() => {
globalToast = {
show: (options: ShowToastOptions) => {
const id = Math.random().toString()
toast.show({
id,
placement: options.placement ?? 'top',
duration: options.duration ?? 3000,
avoidKeyboard: options.avoidKeyboard ?? false,
containerStyle: options.containerStyle,
onCloseComplete: options.onCloseComplete,
render: options.render
? () => options.render!({ id })
: ({ id }) => (
<Toast
nativeID={`toast-${id}`}
variant={options.variant ?? 'solid'}
action={options.type ?? 'muted'}
>
{options.title && <ToastTitle className="font-blod">{options.title}</ToastTitle>}
{options.description && <ToastDescription>{options.description}</ToastDescription>}
</Toast>
),
})
return id
},
close: (id: string) => {
toast.close(id)
},
closeAll: () => {
toast.closeAll()
},
isActive: (id: string) => toast.isActive(id),
}
}, [toast])
return <>{children}</>
}
/**
* 全局 toast 对象,组件内外都可以使用
*/
export const toast = {
show: (options: ShowToastOptions) => {
if (!globalToast) {
console.warn('Toast not initialized. Make sure ToastProviderWrapper is rendered.')
return ''
}
return globalToast.show(options)
},
close: (id: string) => {
if (globalToast)
globalToast.close(id)
},
closeAll: () => {
if (globalToast)
globalToast.closeAll()
},
isActive: (id: string) => {
return globalToast ? globalToast.isActive(id) : false
},
}