Chuyển đến nội dung chính

第 16 課:即時、WebSocket 和 i18n

WebSocket 集成,Socket.IO。伺服器發送的事件,輪詢。 @nuxtjs/i18n,區域設定路由,訊息格式。 @nuxt/CMS 的內容。 PWA 支援。

💻 程式設計 — 第 16 課 第 16 課:即時、WebSocket 和 i18n

Vue.js 和 Nuxt:從基礎到高級

第 4 部分:Nuxt 高級和全端

亞洲開發網

1. 伺服器發送的事件

// server/api/events.get.ts
export default defineEventHandler(async (event) => {
  setResponseHeaders(event, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  })

  const body = new ReadableStream({
    start(controller) {
      const encoder = new TextEncoder()
      const interval = setInterval(() => {
        const data = JSON.stringify({ time: new Date().toISOString() })
        controller.enqueue(encoder.encode(`data: ${data}\n\n`))
      }, 3000)

      event.node.req.on('close', () => {
        clearInterval(interval)
        controller.close()
      })
    },
  })

  return new Response(body)
})
<!-- Composable for SSE -->
<script setup lang="ts">
const messages = ref<string[]>([])

onMounted(() => {
  const source = new EventSource('/api/events')
  source.onmessage = (event) => {
    messages.value.push(JSON.parse(event.data))
  }
  onUnmounted(() => source.close())
})
</script>

2.帶有 Nitro 的 WebSocket

// server/routes/_ws.ts (Nitro experimental WebSocket)
export default defineWebSocketHandler({
  open(peer) {
    console.log('Connected:', peer.id)
    peer.subscribe('chat')
  },
  message(peer, message) {
    const data = JSON.parse(message.text())
    peer.publish('chat', JSON.stringify({
      user: peer.id,
      text: data.text,
      timestamp: new Date().toISOString(),
    }))
  },
  close(peer) {
    console.log('Disconnected:', peer.id)
  },
})
// nuxt.config.ts — enable experimental WebSocket
export default defineNuxtConfig({
  nitro: {
    experimental: {
      websocket: true,
    },
  },
})

3.@nuxtjs/i18n

npx nuxi module add @nuxtjs/i18n
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n'],
  i18n: {
    locales: [
      { code: 'vi', name: 'Tiếng Việt', file: 'vi.json' },
      { code: 'en', name: 'English', file: 'en.json' },
    ],
    defaultLocale: 'vi',
    langDir: 'locales/',
    strategy: 'prefix_except_default', // /en/about, /about (vi)
  },
})
// locales/vi.json
{
  "nav": {
    "home": "Trang chủ",
    "blog": "Blog",
    "about": "Giới thiệu"
  },
  "welcome": "Chào mừng {name}!",
  "items": "Không có item | 1 item | {count} items"
}
<script setup lang="ts">
const { t, locale, locales, setLocale } = useI18n()
const switchLocalePath = useSwitchLocalePath()
</script>

<template>
  <nav>
    <NuxtLink :to="localePath('/')">{{ t('nav.home') }}</NuxtLink>
    <NuxtLink :to="localePath('/blog')">{{ t('nav.blog') }}</NuxtLink>
  </nav>

  <p>{{ t('welcome', { name: 'Duy' }) }}</p>
  <p>{{ t('items', 5) }}</p>

  <!-- Language switcher -->
  <div>
    <NuxtLink
      v-for="loc in locales"
      :key="loc.code"
      :to="switchLocalePath(loc.code)"
    >
      {{ loc.name }}
    </NuxtLink>
  </div>
</template>

4.@nuxt/內容

npx nuxi module add @nuxt/content
<!-- pages/blog/[...slug].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: page } = await useAsyncData(
  route.path,
  () => queryContent(route.path).findOne()
)
</script>

<template>
  <article>
    <h1>{{ page?.title }}</h1>
    <ContentRenderer :value="page!" />
  </article>
</template>

下一篇: 動畫和過渡 — Vue 轉換、GSAP、頁面轉換。