Skip to content

Vue 场景(Solutions for Vue)

针对 Vue(Composition API)业务的复杂方案,结合 nex-lib 组件化使用,提供更少代码与更高可维护性。

搜索框(防抖 + 超时 + 重试)

js
import { debounce, Http } from 'nex-lib'
const search = debounce(async (q) => {
  const data = await Http.get('/api/search', { query: { q }, timeoutMs: 3000, retry: { times: 3, delay: 200 } })
  // render(data)
}, 300)
ts
import { ref } from 'vue'
import { debounce, Http } from 'nex-lib'
const q = ref('')
const search = debounce(async (kw: string) => {
  const data = await Http.get('/api/search', {
    query: { q: kw },
    timeoutMs: 3000,
    retry: { times: 3, delay: 200 },
  })
  // 更新列表数据
}, 300)
function onInput(e: Event) { q.value = (e.target as HTMLInputElement).value; search(q.value) }

节日倒计时(TTL + 校时)

js
import { nextAt, nowSeconds, formatDuration, Http, StorageUtils } from 'nex-lib'
// 目标:三天后 18:00(本地时区);刷新页面保持倒计时(TTL 缓存)
const cached = StorageUtils.getWithTTL('holiday_target')
const computedTarget = nextAt(3, 18)
const target = typeof cached === 'number' ? cached : computedTarget
StorageUtils.setWithTTL('holiday_target', target, 24*60*60*1000)
setInterval(async () => {
  // 校时:优先服务端时间,失败回退本地时间
  const now = await Http.get('/api/time', { timeoutMs: 3000, retry: 2 })
    .then(r => r.now).catch(() => nowSeconds())
  // 格式化显示,避免出现负数
  document.querySelector('#countdown').textContent = formatDuration(Math.max(0, (target - now) * 1000))
}, 1000)
ts
import { ref, onMounted, onUnmounted } from 'vue'
import { nextAt, nowSeconds, formatDuration, Http, StorageUtils } from 'nex-lib'
const remain = ref('00:00:00')
// 目标:三天后 18:00(本地时区);刷新页面保持倒计时(TTL 缓存)
const cached = StorageUtils.getWithTTL('holiday_target')
const computedTarget = nextAt(3, 18)
const target = typeof cached === 'number' ? cached : computedTarget
StorageUtils.setWithTTL('holiday_target', target, 24*60*60*1000)
let timer: any
async function tick() {
  // 校时:优先服务端时间,失败回退本地时间
  const now = await Http.get<{ now: number }>('/api/time', { timeoutMs: 3000, retry: 2 })
    .then(r => r.now).catch(() => nowSeconds())
  // 格式化显示,避免出现负数
  remain.value = formatDuration(Math.max(0, (target - now) * 1000))
}
onMounted(() => { timer = setInterval(tick, 1000) })
onUnmounted(() => { clearInterval(timer) })

渠道参数(同源 + 持久化 + 分享)

js
import { createWURL, StorageUtils, 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)
ts
import { createWURL, StorageUtils, 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)

主题色应用(CSS 变量)

js
import { ColorUtils } from 'nex-lib'
const brand = '#ffcc00'
document.documentElement.style.setProperty('--btn-bg', ColorUtils.darken(brand, .1))
document.documentElement.style.setProperty('--block-bg', ColorUtils.lighten(brand, .15))
document.documentElement.style.setProperty('--text', ColorUtils.contrastColor(ColorUtils.darken(brand, .1)))
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)

国际化(IP/语言/时区)

I18n Highlights

  • 自动 Locale 与语言/国家英文名
  • 本地时区与 UTC 偏移
  • 一行获取公共 IP(带超时与重试)
  • 示例展示统一 JSON 输出
js
import { browserUtils, TimeUtils } from 'nex-lib'
const locale = browserUtils.getPreferredLocale()
const li = browserUtils.getLocaleInfo(locale)
const timezone = TimeUtils.getTimezone()
const offsetMin = TimeUtils.getTimezoneOffsetMinutes()
const ip = await browserUtils.getPublicIP()
const info = { ip, locale: li.code, language: li.language.name, country: li.country.name || '', timezone, offsetMin, languages: browserUtils.getLanguage() }
// 渲染到页面:<pre id="i18n">...</pre>
document.querySelector('#i18n').textContent = JSON.stringify(info, null, 2)
ts
import { ref, onMounted } from 'vue'
import { browserUtils, TimeUtils } from 'nex-lib'
type Info = { ip: string; locale: string; language: string; country: string; timezone: string; offsetMin: number; languages: string }
const info = ref<Info>({ ip: '-', locale: '', language: '', country: '', timezone: '', offsetMin: 0, languages: '' })
onMounted(async () => {
  const locale = browserUtils.getPreferredLocale()
  const li = browserUtils.getLocaleInfo(locale)
  const timezone = TimeUtils.getTimezone()
  const offsetMin = TimeUtils.getTimezoneOffsetMinutes()
  const ip = await browserUtils.getPublicIP()
  info.value = { ip, locale: li.code, language: li.language.name, country: li.country.name || '', timezone, offsetMin, languages: browserUtils.getLanguage() }
})

Released under the ISC License.