| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- /* util.js —— 纯函数:转义、日期、排序、分组,不碰 DOM 也不需要数据 */
- (function () {
- "use strict";
- const ESC = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
- function esc(v) {
- if (v == null) return "";
- return String(v).replace(/[&<>"']/g, (c) => ESC[c]);
- }
- function fmtDate(iso, withTime) {
- const d = toDate(iso);
- if (!d) return "—";
- const p = (n) => String(n).padStart(2, "0");
- const s = `${d.getMonth() + 1}月${d.getDate()}日`;
- return withTime ? `${s} ${p(d.getHours())}:${p(d.getMinutes())}` : s;
- }
- /* 相对时间,用于"最后活动" */
- function fmtAgo(iso) {
- const t = timeOf(iso);
- if (!t) return "—";
- const m = Math.round((Date.now() - t) / 60000);
- if (m < 1) return "刚刚";
- if (m < 60) return `${m} 分钟前`;
- const h = Math.round(m / 60);
- if (h < 24) return `${h} 小时前`;
- return fmtDate(iso);
- }
- /* Parse 的 Date 字段在 REST 里是 {"__type":"Date","iso":"…"}。
- * 直接 new Date(对象) 得到 Invalid Date —— 之前"最后活动时间"全部丢失就是这个原因。
- * 统一在这里拆:Date 对象 / ISO 串 / Parse 日期对象都能读。 */
- function toDate(d) {
- if (!d) return null;
- if (d instanceof Date) return isNaN(d) ? null : d;
- if (typeof d === "object" && typeof d.iso === "string") d = d.iso;
- const dt = new Date(d);
- return isNaN(dt) ? null : dt;
- }
- function timeOf(d) {
- const dt = toDate(d);
- return dt ? dt.getTime() : 0;
- }
- /* 最近活动时间:Date 字段或 ISO 串,取最大 */
- function latest(dates) {
- let best = 0;
- for (const d of dates) {
- const t = timeOf(d);
- if (t > best) best = t;
- }
- return best ? new Date(best).toISOString() : null;
- }
- function countBy(arr, keyFn) {
- const m = new Map();
- for (const x of arr) {
- const k = keyFn(x);
- m.set(k, (m.get(k) || 0) + 1);
- }
- return m;
- }
- function groupBy(arr, keyFn) {
- const m = new Map();
- for (const x of arr) {
- const k = keyFn(x);
- if (!m.has(k)) m.set(k, []);
- m.get(k).push(x);
- }
- return m;
- }
- /* 排序工具:空值永远沉底 */
- function cmpNum(a, b, dir) {
- const ea = a == null || isNaN(a), eb = b == null || isNaN(b);
- if (ea && eb) return 0;
- if (ea) return 1;
- if (eb) return -1;
- return dir * (a - b);
- }
- function initials(name) {
- if (!name) return "?";
- return Array.from(String(name)).slice(-2).join("");
- }
- function shortId(prefix, idx) {
- return `${prefix}-${String(idx + 1).padStart(2, "0")}`;
- }
- /* 名称包含匹配,忽略大小写;空查询视为命中(用于 ?focus= 这种"定位某一条"的场景) */
- function matches(hay, q) {
- if (!q) return true;
- return String(hay || "").toLowerCase().includes(q);
- }
- window.KF_UTIL = { esc, toDate, timeOf, fmtDate, fmtAgo, latest, countBy, groupBy, cmpNum, initials, shortId, matches };
- })();
|