Skip to content

场景解决方案(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 * 3600
ts
// 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 行

Released under the ISC License.