/**
|
* 到期预警计算工具
|
*
|
* 各业务模块的预警阈值不同:
|
* 车辆年检/保险、房屋租赁合同、设备校准、报表有效期 → 30 天
|
* 宽带与通信下次缴费 → 15 天
|
* 销售台账合同原件约定回传日期 → 提前 N 天(由项目管理配置决定)
|
* 统一用 warnDays 传入,避免各页面各写一套日期差逻辑。
|
*/
|
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
/**
|
* 解析后端返回的日期,兼容时间戳 / Date / 'YYYY-MM-DD' / 'YYYY/MM/DD' / 'YYYY-MM-DD HH:mm:ss'
|
*/
|
export function parseDate(value) {
|
if (value === undefined || value === null || value === "") return null;
|
if (value instanceof Date) {
|
return Number.isNaN(value.getTime()) ? null : new Date(value.getTime());
|
}
|
if (typeof value === "number") {
|
const d = new Date(value);
|
return Number.isNaN(d.getTime()) ? null : d;
|
}
|
const matched = String(value)
|
.trim()
|
.replace(/\//g, "-")
|
.replace("T", " ")
|
.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
|
if (!matched) return null;
|
const date = new Date(
|
Number(matched[1]),
|
Number(matched[2]) - 1,
|
Number(matched[3])
|
);
|
return Number.isNaN(date.getTime()) ? null : date;
|
}
|
|
/**
|
* 距离到期日的剩余天数
|
* 正数=还有 N 天,0=今天到期,负数=已逾期 N 天,无法解析返回 null
|
*/
|
export function remainDays(dateStr) {
|
const target = parseDate(dateStr);
|
if (!target) return null;
|
const today = new Date();
|
today.setHours(0, 0, 0, 0);
|
target.setHours(0, 0, 0, 0);
|
return Math.round((target.getTime() - today.getTime()) / MS_PER_DAY);
|
}
|
|
/**
|
* 预警级别:overdue 已逾期 / warning 即将到期 / normal 正常 / none 无日期
|
*/
|
export function warnLevel(dateStr, warnDays = 30) {
|
const days = remainDays(dateStr);
|
if (days === null) return "none";
|
if (days < 0) return "overdue";
|
if (days <= warnDays) return "warning";
|
return "normal";
|
}
|
|
export function warnColor(level) {
|
const colors = {
|
overdue: "#f56c6c",
|
warning: "#ff9900",
|
normal: "#19be6b",
|
none: "#909399",
|
};
|
return colors[level] || colors.none;
|
}
|
|
export function warnText(level, days) {
|
if (level === "overdue") return `已逾期${Math.abs(days)}天`;
|
if (level === "warning") return days === 0 ? "今天到期" : `即将到期(${days}天)`;
|
if (level === "normal") return "正常";
|
return "";
|
}
|
|
/**
|
* 一次拿到渲染预警标记所需的全部信息
|
* @returns {{days: number|null, level: string, color: string, text: string}}
|
*/
|
export function getWarnInfo(dateStr, warnDays = 30) {
|
const days = remainDays(dateStr);
|
const level = warnLevel(dateStr, warnDays);
|
return {
|
days,
|
level,
|
color: warnColor(level),
|
text: warnText(level, days),
|
};
|
}
|
|
/**
|
* 是否已逾期或即将到期(用于列表筛选、高亮)
|
*/
|
export function isExpiringSoon(dateStr, warnDays = 30) {
|
const level = warnLevel(dateStr, warnDays);
|
return level === "overdue" || level === "warning";
|
}
|
|
/**
|
* 按天/月推算日期,用于"下次缴费日期 = 安装日期 + 缴费周期月"这类自动计算
|
* @returns {string} 'YYYY-MM-DD',无法计算返回 ''
|
*/
|
export function addMonths(dateStr, months) {
|
const base = parseDate(dateStr);
|
if (!base || !Number.isFinite(Number(months))) return "";
|
const day = base.getDate();
|
base.setDate(1);
|
base.setMonth(base.getMonth() + Number(months));
|
// 处理 1/31 + 1 月这类溢出,回退到当月最后一天
|
const lastDay = new Date(base.getFullYear(), base.getMonth() + 1, 0).getDate();
|
base.setDate(Math.min(day, lastDay));
|
const pad = n => String(n).padStart(2, "0");
|
return `${base.getFullYear()}-${pad(base.getMonth() + 1)}-${pad(base.getDate())}`;
|
}
|
|
/**
|
* 生成 [start, end] 之间的月份列表(含首尾),用于房屋租赁的月度费用子表
|
* @returns {string[]} ['2026-01', '2026-02', ...]
|
*/
|
export function monthRange(startStr, endStr) {
|
const start = parseDate(startStr);
|
const end = parseDate(endStr);
|
if (!start || !end || end < start) return [];
|
const months = [];
|
const cursor = new Date(start.getFullYear(), start.getMonth(), 1);
|
const last = new Date(end.getFullYear(), end.getMonth(), 1);
|
while (cursor <= last && months.length < 600) {
|
const pad = n => String(n).padStart(2, "0");
|
months.push(`${cursor.getFullYear()}-${pad(cursor.getMonth() + 1)}`);
|
cursor.setMonth(cursor.getMonth() + 1);
|
}
|
return months;
|
}
|