场景解决方案(Solutions)
从真实业务出发,展示“常见需求的传统写法 vs 使用 nex-lib 的写法”,并给出代码节省量参考。
节日倒计时(多时区 + 缓存 TTL + 服务端校时)
目标:展示到指定节日 00:00:00 的实时倒计时,要求多时区准确、断网恢复、与服务端时间对齐。
场景说明:例如“3 天后的 18:00 为目标时间”,需要在刷新页面后仍保持倒计时继续(依赖 TTL 缓存),并通过服务端校时保证显示准确。
js
import { nowSeconds, startOfDay, addDays } from 'nex-lib'
// 以本地时区计算:从今天 00:00:00 起,向后偏移 3 天,再加上 18:00 的秒数
const base = startOfDay(nowSeconds())
const target = addDays(base, 3) + 18 * 3600ts
// 1)本地时区与目标日计算
function endOfDayLocal(seconds?: number) {
const d = seconds ? new Date(seconds * 1000) : new Date()
d.setHours(23, 59, 59, 999)
return Math.floor(d.getTime() / 1000)
}
// 2)服务端校时与网络失败重试
async function getServerNow() {
const ac = new AbortController()
const timer = setTimeout(() => ac.abort(), 3000)
try {
let n = 0
while (n < 2) {
try {
const res = await fetch('/api/time', { signal: ac.signal })
if (!res.ok) throw new Error('http')
const json = await res.json(); return json.now
} catch (e) { n++; await new Promise(r => setTimeout(r, 200)) }
}
} finally { clearTimeout(timer) }
return Math.floor(Date.now() / 1000)
}
// 3)TTL 缓存目标日期
localStorage.setItem('target', JSON.stringify({ value: 1760000000, expireAt: Date.now()+86400000 }))
const raw = localStorage.getItem('target')
let target = raw ? JSON.parse(raw).value : endOfDayLocal()
// 4)轮询与格式化
setInterval(async () => {
const now = await getServerNow()
const ms = Math.max(0, (target - now) * 1000)
const s = Math.floor(ms/1000), h = String(Math.floor((s%86400)/3600)).padStart(2,'0')
const m = String(Math.floor((s%3600)/60)).padStart(2,'0'), sec = String(s%60).padStart(2,'0')
document.querySelector('#countdown').textContent = `${h}:${m}:${sec}`
}, 1000)ts
import { startOfDay, addDays, nowSeconds, formatDuration } from 'nex-lib'
import { StorageUtils } from 'nex-lib'
import { Http } from 'nex-lib'
// 1)三天后 18:00 作为目标时间;缓存 TTL(24h)
const cached = StorageUtils.getWithTTL('holiday_target')
const base = startOfDay(nowSeconds())
const computedTarget = addDays(base, 3) + 18 * 3600
const target = typeof cached === 'number' ? cached : computedTarget
StorageUtils.setWithTTL('holiday_target', target, 24*60*60*1000)
// 2)服务端校时(超时 + 重试)
async function serverNow() {
try { const r = await Http.get<{ now: number }>('/api/time', { timeoutMs: 3000, retry: 2 }); return r.now }
catch { return nowSeconds() }
}
// 3)倒计时渲染(避免负数)
async function tick() {
const now = await serverNow()
const remainMs = Math.max(0, (target - now) * 1000)
document.querySelector('#countdown')!.textContent = formatDuration(remainMs)
}
setInterval(tick, 1000)text
约 45 行 → 16 行,节省 ~29 行;并集成 TTL、重试、超时与格式化。进阶:跨时区显示
- 若需展示 UTC 倒计时:目标时间用
formatUTC/ISO8601定义;本地展示仍用formatDuration。 - 获取本地时区:
getTimezone(),可在 UI 标注,例如 “Asia/Shanghai”。
营销落地页(参数策略 + 同源校验 + 持久化 + 分享)
需求:渠道参数 utm_* 与 sku 管理;同源校验防跳转;持久化 7 天;一键复制分享链接。
ts
const u = new URL(location.href)
// 1)解析与策略合并
const params = Object.fromEntries(u.searchParams.entries())
params.utm_source = params.utm_source || 'web'
params.sku = sku
// 2)同源校验
function isSameOrigin(a, b) { return new URL(a).origin === new URL(b).origin }
if (!isSameOrigin(location.href, target)) throw new Error('unsafe')
// 3)持久化与清理
localStorage.setItem('utm', JSON.stringify({ params, expireAt: Date.now()+7*86400000 }))
// 4)复制
const ta = document.createElement('textarea'); ta.value = target; document.body.appendChild(ta)
ta.select(); document.execCommand('copy'); document.body.removeChild(ta)ts
import { createWURL } from 'nex-lib'
import { StorageUtils } from 'nex-lib'
import { browserUtils } from 'nex-lib'
const w = createWURL(location.href)
const merged = w.replaceParams({
...w.parseQueryParams(),
utm_source: 'web',
sku,
})
if (!w.isSameOrigin(merged)) throw new Error('unsafe')
StorageUtils.setWithTTL('utm', merged, 7*24*60*60*1000)
await browserUtils.copyToClipboard(merged)text
约 20 行 → 8 行,节省 ~12 行;参数策略、同源校验、TTL 与复制统一。大表搜索交互(防抖 + 取消/超时 + 重试)
目标:输入防抖、网络异常重试、超时取消,避免阻塞 UI。
ts
let timer: any; let controller: AbortController | null = null
function onInput(e) {
const q = e.target.value
if (timer) clearTimeout(timer)
timer = setTimeout(async () => {
if (controller) controller.abort()
controller = new AbortController()
try {
let tries = 0
while (tries < 3) {
try {
const res = await fetch('/api/search?q='+encodeURIComponent(q), { signal: controller.signal })
if (!res.ok) throw new Error('http'); const list = await res.json(); render(list); break
} catch (e) { tries++; await new Promise(r=>setTimeout(r, 200)) }
}
} catch {}
}, 300)
}ts
import { ObjectUtils } from 'nex-lib'
import { Http } from 'nex-lib'
const doSearch = ObjectUtils.debounce(async (q: string) => {
const data = await Http.get('/api/search', {
query: { q }, timeoutMs: 3000, retry: { times: 3, delay: 200 }
})
render(data)
}, 300)
function onInput(e: Event) { doSearch((e.target as HTMLInputElement).value) }text
约 30 行 → 10 行,节省 ~20 行;集成查询、超时与重试。品牌主题生成(多层黄系 + 对比色自动)
目标:根据主色生成按钮/块背景与可读文本色。
ts
const brand = '#ffcc00'
function lighten(hex, ratio) { /* ...10+ 行实现... */ }
function darken(hex, ratio) { /* ... */ }
function contrast(hex) { /* yiq 计算 ... */ }
const btnBg = darken(brand, .1)
const blockBg = lighten(brand, .15)
const textColor = contrast(btnBg)
document.documentElement.style.setProperty('--btn-bg', btnBg)
document.documentElement.style.setProperty('--block-bg', blockBg)
document.documentElement.style.setProperty('--text', textColor)ts
import { ColorUtils } from 'nex-lib'
const brand = '#ffcc00'
const btnBg = ColorUtils.darken(brand, .1)
const blockBg = ColorUtils.lighten(brand, .15)
const textColor = ColorUtils.contrastColor(btnBg)
document.documentElement.style.setProperty('--btn-bg', btnBg)
document.documentElement.style.setProperty('--block-bg', blockBg)
document.documentElement.style.setProperty('--text', textColor)text
理论 20+ 行 → 7 行;复杂度显著下降且可读性提升。1)搜索框防抖(避免频繁请求)
ts
let timer: any
function onInput(e: Event) {
const q = (e.target as HTMLInputElement).value
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fetch(`/api/search?q=${encodeURIComponent(q)}`)
}, 300)
}ts
import { ObjectUtils } from 'nex-lib'
const doSearch = ObjectUtils.debounce((q: string) => {
fetch(`/api/search?q=${encodeURIComponent(q)}`)
}, 300)
function onInput(e: Event) { doSearch((e.target as HTMLInputElement).value) }text
约 12 行 → 4 行,节省 ~8 行2)列表分页请求(查询参数 + 超时 + 重试)
ts
const url = new URL('/api/list', location.origin)
url.searchParams.set('page', String(page))
url.searchParams.set('size', String(size))
const ac = new AbortController()
const t = setTimeout(() => ac.abort(), 3000)
try {
let tries = 0
while (tries < 3) {
try {
const res = await fetch(url.toString(), { signal: ac.signal })
if (!res.ok) throw new Error('http')
return await res.json()
} catch (e) { tries++; await new Promise(r => setTimeout(r, 500)) }
}
} finally { clearTimeout(t) }ts
import { Http } from 'nex-lib'
await Http.get('/api/list', {
query: { page, size },
timeoutMs: 3000,
retry: { times: 3, delay: 500 },
})text
约 28 行 → 6 行,节省 ~22 行3)产品页 URL 参数管理(追加/替换/移除)
ts
const u = new URL(location.href)
u.searchParams.set('sku', sku)
u.searchParams.set('ref', ref)
history.replaceState(null, '', u.toString())ts
import { createWURL } from 'nex-lib'
const u = createWURL(location.href)
history.replaceState(null, '', u.replaceParams({ sku, ref }))text
约 8 行 → 3 行,节省 ~5 行4)登录态存储与过期(TTL)
ts
localStorage.setItem('token', JSON.stringify({ v: token, expireAt: Date.now()+86400000 }))
const raw = localStorage.getItem('token')
if (raw) {
const obj = JSON.parse(raw)
if (Date.now() >= obj.expireAt) localStorage.removeItem('token')
}ts
import { StorageUtils } from 'nex-lib'
StorageUtils.setWithTTL('token', token, 86400000)
StorageUtils.getWithTTL('token')text
约 9 行 → 2 行,节省 ~7 行5)倒计时与相对时间(展示友好)
ts
function fmt(ms: number) {
const s = Math.floor(ms/1000)
const h = String(Math.floor((s%86400)/3600)).padStart(2,'0')
const m = String(Math.floor((s%3600)/60)).padStart(2,'0')
const sec = String(s%60).padStart(2,'0')
return `${h}:${m}:${sec}`
}ts
import { formatDuration, relativeTime, nowSeconds } from 'nex-lib'
formatDuration(65000)
relativeTime(nowSeconds()-120)text
约 8 行 → 2 行,节省 ~6 行6)复制分享链接(兼容权限)
ts
const ta = document.createElement('textarea')
ta.value = url
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)ts
import { browserUtils } from 'nex-lib'
await browserUtils.copyToClipboard(url)text
约 6 行 → 1 行,节省 ~5 行7)主题色工具(对比色与亮暗)
ts
function yiq(hex: string) {
const h = hex.replace('#','')
const r = parseInt(h.slice(0,2),16)
const g = parseInt(h.slice(2,4),16)
const b = parseInt(h.slice(4,6),16)
const v = (r*299 + g*587 + b*114)/1000
return v >= 128 ? '#000' : '#fff'
}ts
import { ColorUtils } from 'nex-lib'
ColorUtils.contrastColor('#ffcc00')
ColorUtils.lighten('#333333', 0.5)
ColorUtils.darken('#cccccc', 0.5)text
约 10 行 → 3 行,节省 ~7 行