Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
119
src/utils/business/calculate.ts
Normal file
119
src/utils/business/calculate.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 业务计算工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 计算佣金
|
||||
* @param amount 交易金额(分)
|
||||
* @param rate 佣金比例(0-100)
|
||||
* @param type 佣金类型:percentage-百分比, fixed-固定金额
|
||||
*/
|
||||
export function calculateCommission(
|
||||
amount: number,
|
||||
rate: number,
|
||||
type: 'percentage' | 'fixed' = 'percentage'
|
||||
): number {
|
||||
if (type === 'fixed') {
|
||||
return rate
|
||||
}
|
||||
return Math.floor((amount * rate) / 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算折扣价
|
||||
* @param originalPrice 原价(分)
|
||||
* @param discount 折扣(0-100)
|
||||
*/
|
||||
export function calculateDiscountPrice(originalPrice: number, discount: number): number {
|
||||
return Math.floor((originalPrice * discount) / 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算流量使用率
|
||||
* @param used 已用流量(MB)
|
||||
* @param total 总流量(MB)
|
||||
*/
|
||||
export function calculateFlowUsageRate(used: number, total: number): number {
|
||||
if (total === 0) return 0
|
||||
return Math.min(100, Math.round((used / total) * 100))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算剩余天数
|
||||
* @param expireTime 过期时间
|
||||
*/
|
||||
export function calculateRemainingDays(expireTime: string): number {
|
||||
const now = new Date().getTime()
|
||||
const expire = new Date(expireTime).getTime()
|
||||
const diff = expire - now
|
||||
|
||||
if (diff <= 0) return 0
|
||||
|
||||
return Math.ceil(diff / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算环比增长率
|
||||
* @param current 当前值
|
||||
* @param previous 上期值
|
||||
*/
|
||||
export function calculateGrowthRate(current: number, previous: number): number {
|
||||
if (previous === 0) return current > 0 ? 100 : 0
|
||||
|
||||
return Math.round(((current - previous) / previous) * 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算平均值
|
||||
* @param values 数值数组
|
||||
*/
|
||||
export function calculateAverage(values: number[]): number {
|
||||
if (values.length === 0) return 0
|
||||
|
||||
const sum = values.reduce((acc, val) => acc + val, 0)
|
||||
return Math.round((sum / values.length) * 100) / 100
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算提现手续费
|
||||
* @param amount 提现金额(分)
|
||||
* @param feeRate 手续费率(0-100)
|
||||
* @param feeType 手续费类型:percentage-百分比, fixed-固定
|
||||
*/
|
||||
export function calculateWithdrawalFee(
|
||||
amount: number,
|
||||
feeRate: number,
|
||||
feeType: 'percentage' | 'fixed' = 'percentage'
|
||||
): number {
|
||||
if (feeType === 'fixed') {
|
||||
return feeRate
|
||||
}
|
||||
return Math.floor((amount * feeRate) / 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算实际到账金额
|
||||
* @param amount 提现金额(分)
|
||||
* @param fee 手续费(分)
|
||||
*/
|
||||
export function calculateActualAmount(amount: number, fee: number): number {
|
||||
return Math.max(0, amount - fee)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算分页偏移量
|
||||
* @param page 当前页
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
export function calculateOffset(page: number, pageSize: number): number {
|
||||
return (page - 1) * pageSize
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算总页数
|
||||
* @param total 总记录数
|
||||
* @param pageSize 每页数量
|
||||
*/
|
||||
export function calculateTotalPages(total: number, pageSize: number): number {
|
||||
return Math.ceil(total / pageSize)
|
||||
}
|
||||
185
src/utils/business/format.ts
Normal file
185
src/utils/business/format.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 格式化工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化流量(MB -> GB/MB)
|
||||
* @param mb 流量(MB)
|
||||
* @param decimal 保留小数位数
|
||||
*/
|
||||
export function formatFlow(mb: number | undefined, decimal = 2): string {
|
||||
if (mb === undefined || mb === null) return '-'
|
||||
|
||||
if (mb >= 1024) {
|
||||
return `${(mb / 1024).toFixed(decimal)} GB`
|
||||
}
|
||||
return `${mb.toFixed(decimal)} MB`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化金额(分 -> 元)
|
||||
* @param fen 金额(分)
|
||||
* @param showSymbol 是否显示货币符号
|
||||
*/
|
||||
export function formatMoney(fen: number | undefined, showSymbol = true): string {
|
||||
if (fen === undefined || fen === null) return '-'
|
||||
|
||||
const yuan = (fen / 100).toFixed(2)
|
||||
return showSymbol ? `¥${yuan}` : yuan
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化手机号(中间4位隐藏)
|
||||
* @param phone 手机号
|
||||
*/
|
||||
export function formatPhone(phone: string | undefined): string {
|
||||
if (!phone) return '-'
|
||||
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化ICCID(显示前5位和后4位)
|
||||
* @param iccid ICCID
|
||||
*/
|
||||
export function formatIccid(iccid: string | undefined): string {
|
||||
if (!iccid) return '-'
|
||||
|
||||
if (iccid.length > 10) {
|
||||
return `${iccid.substring(0, 5)}...${iccid.substring(iccid.length - 4)}`
|
||||
}
|
||||
return iccid
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化ICCID(完整显示,带分隔符)
|
||||
* @param iccid ICCID
|
||||
*/
|
||||
export function formatIccidFull(iccid: string | undefined): string {
|
||||
if (!iccid) return '-'
|
||||
|
||||
// 每4位添加一个空格
|
||||
return iccid.replace(/(\d{4})(?=\d)/g, '$1 ')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化百分比
|
||||
* @param value 数值
|
||||
* @param total 总数
|
||||
* @param decimal 保留小数位数
|
||||
*/
|
||||
export function formatPercentage(
|
||||
value: number | undefined,
|
||||
total: number | undefined,
|
||||
decimal = 2
|
||||
): string {
|
||||
if (value === undefined || total === undefined || total === 0) return '-'
|
||||
|
||||
const percentage = ((value / total) * 100).toFixed(decimal)
|
||||
return `${percentage}%`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时长(秒 -> 时分秒)
|
||||
* @param seconds 秒数
|
||||
*/
|
||||
export function formatDuration(seconds: number | undefined): string {
|
||||
if (seconds === undefined || seconds === null) return '-'
|
||||
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = seconds % 60
|
||||
|
||||
const parts: string[] = []
|
||||
if (hours > 0) parts.push(`${hours}时`)
|
||||
if (minutes > 0) parts.push(`${minutes}分`)
|
||||
if (secs > 0 || parts.length === 0) parts.push(`${secs}秒`)
|
||||
|
||||
return parts.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param bytes 字节数
|
||||
* @param decimal 保留小数位数
|
||||
*/
|
||||
export function formatFileSize(bytes: number | undefined, decimal = 2): string {
|
||||
if (bytes === undefined || bytes === null) return '-'
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let size = bytes
|
||||
let unitIndex = 0
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
|
||||
return `${size.toFixed(decimal)} ${units[unitIndex]}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字(添加千分位)
|
||||
* @param num 数字
|
||||
*/
|
||||
export function formatNumber(num: number | undefined): string {
|
||||
if (num === undefined || num === null) return '-'
|
||||
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏银行卡号(显示前4位和后4位)
|
||||
* @param cardNo 银行卡号
|
||||
*/
|
||||
export function formatBankCard(cardNo: string | undefined): string {
|
||||
if (!cardNo) return '-'
|
||||
|
||||
if (cardNo.length > 8) {
|
||||
const stars = '*'.repeat(cardNo.length - 8)
|
||||
return `${cardNo.substring(0, 4)} ${stars} ${cardNo.substring(cardNo.length - 4)}`
|
||||
}
|
||||
return cardNo
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期范围
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
*/
|
||||
export function formatDateRange(startDate: string, endDate: string): string {
|
||||
if (!startDate || !endDate) return '-'
|
||||
|
||||
return `${startDate} ~ ${endDate}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期时间
|
||||
* @param date 日期字符串或时间戳
|
||||
* @param format 格式化模板,默认 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns 格式化后的日期字符串
|
||||
*/
|
||||
export function formatDateTime(
|
||||
date: string | number | Date | undefined | null,
|
||||
format: string = 'YYYY-MM-DD HH:mm:ss'
|
||||
): string {
|
||||
if (!date) return '-'
|
||||
|
||||
const d = new Date(date)
|
||||
if (isNaN(d.getTime())) return '-'
|
||||
|
||||
const year = d.getFullYear()
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
const hours = String(d.getHours()).padStart(2, '0')
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0')
|
||||
|
||||
return format
|
||||
.replace('YYYY', String(year))
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds)
|
||||
}
|
||||
7
src/utils/business/index.ts
Normal file
7
src/utils/business/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 业务工具函数统一导出
|
||||
*/
|
||||
|
||||
export * from './format'
|
||||
export * from './validate'
|
||||
export * from './calculate'
|
||||
126
src/utils/business/validate.ts
Normal file
126
src/utils/business/validate.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 业务验证工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 验证ICCID格式
|
||||
* @param iccid ICCID
|
||||
*/
|
||||
export function validateIccid(iccid: string): boolean {
|
||||
// ICCID通常是19-20位数字
|
||||
return /^\d{19,20}$/.test(iccid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IMSI格式
|
||||
* @param imsi IMSI
|
||||
*/
|
||||
export function validateImsi(imsi: string): boolean {
|
||||
// IMSI通常是15位数字
|
||||
return /^\d{15}$/.test(imsi)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号格式
|
||||
* @param phone 手机号
|
||||
*/
|
||||
export function validatePhone(phone: string): boolean {
|
||||
// 中国大陆手机号
|
||||
return /^1[3-9]\d{9}$/.test(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邮箱格式
|
||||
* @param email 邮箱
|
||||
*/
|
||||
export function validateEmail(email: string): boolean {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号
|
||||
* @param idCard 身份证号
|
||||
*/
|
||||
export function validateIdCard(idCard: string): boolean {
|
||||
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(
|
||||
idCard
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证银行卡号
|
||||
* @param cardNo 银行卡号
|
||||
*/
|
||||
export function validateBankCard(cardNo: string): boolean {
|
||||
// 一般16-19位
|
||||
return /^\d{16,19}$/.test(cardNo)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IP地址
|
||||
* @param ip IP地址
|
||||
*/
|
||||
export function validateIP(ip: string): boolean {
|
||||
return /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/.test(ip)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL
|
||||
* @param url URL
|
||||
*/
|
||||
export function validateURL(url: string): boolean {
|
||||
try {
|
||||
new URL(url)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码强度
|
||||
* @param password 密码
|
||||
* @returns 强度等级:weak, medium, strong
|
||||
*/
|
||||
export function validatePasswordStrength(password: string): 'weak' | 'medium' | 'strong' {
|
||||
if (password.length < 6) return 'weak'
|
||||
|
||||
let strength = 0
|
||||
|
||||
// 包含小写字母
|
||||
if (/[a-z]/.test(password)) strength++
|
||||
// 包含大写字母
|
||||
if (/[A-Z]/.test(password)) strength++
|
||||
// 包含数字
|
||||
if (/\d/.test(password)) strength++
|
||||
// 包含特殊字符
|
||||
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) strength++
|
||||
// 长度大于8
|
||||
if (password.length >= 8) strength++
|
||||
|
||||
if (strength <= 2) return 'weak'
|
||||
if (strength <= 3) return 'medium'
|
||||
return 'strong'
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证金额格式(正数,最多2位小数)
|
||||
* @param amount 金额
|
||||
*/
|
||||
export function validateAmount(amount: string | number): boolean {
|
||||
const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount
|
||||
return !isNaN(numAmount) && numAmount > 0 && /^\d+(\.\d{1,2})?$/.test(String(amount))
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证整数
|
||||
* @param value 值
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
*/
|
||||
export function validateInteger(value: number, min?: number, max?: number): boolean {
|
||||
if (!Number.isInteger(value)) return false
|
||||
if (min !== undefined && value < min) return false
|
||||
if (max !== undefined && value > max) return false
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user