safety-eval-service-frontend/src/utils/index.js

226 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { useEffect, useRef, useState } from "react";
/**
* 根据身份证号计算年龄
* @param {string} idCard - 18位身份证号码
* @returns {number | null} 年龄(整数),无效身份证返回 null
*/
export function getAgeByIdCard(idCard) {
// 非空 & 类型校验
if (!idCard || typeof idCard !== "string") {
return null;
}
// 只处理18位身份证
const id = idCard.trim();
if (id.length !== 18) {
return null;
}
// 提取出生年月日第7~14位索引6~13
const birthStr = id.substring(6, 14); // 如 "19900101"
const year = Number.parseInt(birthStr.substring(0, 4), 10);
const month = Number.parseInt(birthStr.substring(4, 6), 10);
const day = Number.parseInt(birthStr.substring(6, 8), 10);
// 简单校验日期合法性(可选增强)
if (
year < 1900
|| year > new Date().getFullYear()
|| month < 1
|| month > 12
|| day < 1
|| day > 31
) {
return null;
}
// 构造出生日期
const birthDate = new Date(year, month - 1, day); // 月份从0开始
const today = new Date();
// 计算年龄
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
const dayDiff = today.getDate() - birthDate.getDate();
// 如果还没过生日减1岁
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
age -= 1;
}
return age >= 0 ? age : null; // 年龄不能为负
}
/**
* 从18位身份证号中提取出生年月日
* @param {string} idCard - 身份证号码
* @returns {{ year: number; month: number; day: number } | null} 出生年月日对象,无效则返回 null
*/
export function getBirthDateFromIdCard(idCard) {
if (!idCard || typeof idCard !== "string") {
return null;
}
const id = idCard.trim();
if (id.length !== 18) {
return null; // 仅支持18位身份证
}
// 提取第7~14位YYYYMMDD
const birthStr = id.substring(6, 14); // 如 "19900307"
const year = Number.parseInt(birthStr.substring(0, 4), 10);
const month = Number.parseInt(birthStr.substring(4, 6), 10);
const day = Number.parseInt(birthStr.substring(6, 8), 10);
// 简单合法性校验
const currentYear = new Date().getFullYear();
if (
year < 1900
|| year > currentYear + 1 // 允许未来1年录入误差
|| month < 1
|| month > 12
|| day < 1
|| day > 31
) {
return null;
}
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
}
export function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
const timerRef = useRef(null);
useEffect(() => {
// 清除上一次的定时器
if (timerRef.current) {
clearTimeout(timerRef.current);
}
// 设置新的定时器
timerRef.current = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// 组件卸载或 delay/value 变化时清理
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, [value, delay]);
return debouncedValue;
}
// 手机号脱敏
export function maskPhone(phone) {
if (!phone)
return "";
const str = String(phone).replace(/\s+/g, ""); // 去除空格
if (!/^1[3-9]\d{9}$/.test(str)) {
return phone; // 非标准手机号,原样返回
}
return `${str.substring(0, 3)}****${str.substring(7)}`;
}
// 身份证号脱敏
export function maskIdCard(idCard) {
if (!idCard)
return "";
// 转为字符串并去除空格
const str = String(idCard).replace(/\s+/g, "");
// 判断是否为 18 位身份证(支持末尾 X/x
if (!/^\d{17}[\dX]$/i.test(str)) {
return idCard; // 非标准身份证,原样返回
}
return `${str.substring(0, 6)}********${str.substring(14)}`;
}
// 属地
export const getAreaNamePath = (item) => {
const names = [
item.provinceName,
item.cityName,
item.countryName,
item.streetName,
item.villageName,
].filter(name => name != null && name !== "");
return names.join("/");
};
// 行业类型
export const getCorpTypeNamePath = (item) => {
const names = [
item.corpTypeName,
item.corpType2Name,
item.corpType3Name,
item.corpType4Name,
].filter(name => name != null && name !== "");
return names.join("/");
};
// 身份证解码
export const UseDecodeIdCard = (userIdCard) => {
if (!userIdCard)
return userIdCard;
try {
const decoded = atob(userIdCard);
if (/^\d{17}[\dX]$/.test(decoded)) {
return decoded;
}
}
catch {
console.warn("Not a valid Base64 string, keep as is:", userIdCard);
}
return userIdCard; // fallback
};
/** 列表请求失败时返回空数据,避免 loading 无法结束 */
export function safeListRequest(request) {
return (params) => Promise.resolve(request(params)).catch((err) => {
console.warn("[safeListRequest]", err);
return { data: [], totalCount: 0 };
});
}
const FILE_FETCH_TIMEOUT_MS = 3000;
const REQUEST_TIMEOUT_MS = 15000;
/** 详情/单条请求,失败或超时返回 null */
export async function safeRequest(request, params, timeoutMs = REQUEST_TIMEOUT_MS) {
if (typeof request !== "function") {
return null;
}
try {
const res = await Promise.race([
Promise.resolve(request(params)),
new Promise((_, reject) => {
setTimeout(() => reject(new Error("request timeout")), timeoutMs);
}),
]);
return res ?? null;
}
catch (err) {
console.warn("[safeRequest]", err);
return null;
}
}
/** 附件查询:本地无文件服务时不阻塞弹窗(超时后回退 mock 数据) */
export async function safeGetFiles(getFile, params, fallback = []) {
if (typeof getFile !== "function") {
return fallback;
}
try {
const files = await Promise.race([
Promise.resolve(getFile(params)).then((res) => (Array.isArray(res) ? res : [])),
new Promise((_, reject) => {
setTimeout(() => reject(new Error("getFile timeout")), FILE_FETCH_TIMEOUT_MS);
}),
]);
return files.length ? files : fallback;
}
catch (err) {
console.warn("[safeGetFiles]", err);
return fallback;
}
}