动态

详情 返回 返回

Nuxt.js實戰:Vue.js的服務器端渲染框架 - 动态 详情

創建Nuxt.js項目

首先,確保你已經安裝了Node.js和yarn或npm。然後,通過命令行創建一個新的Nuxt.js項目:

yarn create nuxt-app my-nuxt-project
cd my-nuxt-project

在創建過程中,你可以選擇是否需要UI框架、預處理器等選項,根據需要配置。

目錄結構

Nuxt.js遵循特定的目錄結構,其中一些關鍵目錄如下:

├── .nuxt/                # 自動生成的文件,包含編譯後的代碼和配置
├── assets/               # 用於存放未編譯的靜態資源,如CSS、圖片、字體
├── components/           # 自定義Vue組件
├── layouts/              # 應用的佈局文件,定義頁面的通用結構
│   └── default.vue       # 默認佈局
├── middleware/           # 中間件文件
├── pages/                # 應用的路由和視圖,每個文件對應一個路由
│   ├── index.vue         # 默認首頁
│   └── [slug].vue        # 動態路由示例
├── plugins/              # 自定義Vue.js插件
├── static/               # 靜態資源,會被原樣複製到輸出目錄
├── store/                # Vuex狀態管理文件
│   ├── actions.js        # Vuex actions
│   ├── mutations.js      # Vuex mutations
│   ├── getters.js        # Vuex getters
│   └── index.js          # Vuex store入口文件
├── nuxt.config.js        # Nuxt.js配置文件
├── package.json          # 項目依賴和腳本
└── yarn.lock             # 或者npm.lock,記錄依賴版本
  • .nuxt/:這個目錄是自動生成的,包含了編譯後的代碼,一般不需要直接修改。
  • assets/:存放未編譯的靜態資源,比如CSS、JavaScript和圖片。在構建時,Nuxt.js會處理這些資源。
  • components/:存儲自定義Vue組件,可以複用在應用的不同部分。
  • layouts/:定義頁面的佈局,可以有一個默認佈局,也可以有多個特定佈局。
  • pages/:每個文件對應一個路由,文件名就是路由名稱。動態路由使用方括號[]表示。
  • middleware/:放置自定義的中間件,可以在頁面渲染前後執行邏輯。
  • plugins/:自定義Vue.js插件的入口文件。
  • static/:直接複製到構建輸出目錄,不做任何處理,常用於存放robots.txt或favicon.ico等。
  • store/:Vuex狀態管理的目錄,存放actions、mutations、getters和整個store的入口文件。
  • nuxt.config.js:Nuxt.js的配置文件,用於定製項目的設置。
  • package.json:項目依賴和腳本配置。
  • yarn.locknpm.lock:記錄項目依賴的精確版本,確保不同環境下的依賴一致性。

頁面渲染

pages/目錄下創建一個index.vue文件,這是應用的首頁:

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Hello from Nuxt.js SSR</h1>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'This content is server-rendered!'
    };
  },
  asyncData() {
    // 這裏可以在服務器端獲取數據
    // 返回的數據會作為data的默認值
    return { message: 'Data fetched on server' };
  }
};
</script>

Nuxt.js 頁面渲染的過程分為兩個主要階段:服務器端渲染 (SSR) 和客户端渲染 (CSR)。以下是Nuxt.js頁面渲染的詳細步驟:

初始化:

用户在瀏覽器中輸入URL併發送請求到服務器。
服務器接收到請求後,開始處理。

路由解析:

Nuxt.js 使用 nuxt.config.js 中的 routes 配置(如果存在)或自動從 pages/ 目錄生成路由。
對應的頁面文件被識別,例如 pages/index.vuepages/about.vue

數據預取:

Nuxt.js 查找頁面組件中的 asyncDatafetch 方法(如果存在)。
這些方法會在服務器端運行,用於從API或其他數據源獲取數據。
數據獲取後,會被序列化並注入到頁面模板中。

模板渲染:

Nuxt.js 使用 Vue.js 的渲染引擎將組件和預取的數據轉換為HTML字符串。
HTML字符串中包含了客户端需要的所有初始數據,以JSON格式內聯在<script>標籤中。

返回HTML:

服務器將生成的HTML響應發送回客户端(瀏覽器)。
客户端初始化:

瀏覽器接收到HTML後,開始解析和執行內聯的JavaScript。
Nuxt.js客户端庫(nuxt.js)被加載並初始化。

客户端渲染:

客户端庫接管渲染,Vue.js實例被創建,數據從內聯的JSON注入到Vue實例。
頁面完成初始渲染,用户可以看到完整的頁面內容。
此時,頁面是交互式的,用户可以觸發事件和導航。

後續導航:

當用户導航到其他頁面時,Nuxt.js 使用客户端路由(Vue Router)進行無刷新跳轉。
如果新頁面需要數據,asyncDatafetch 方法會在客户端運行,獲取新的數據並更新視圖。

SSG(靜態站點生成):

在開發之外,可以使用 nuxt generate 命令生成靜態HTML文件。
每個頁面都會被預渲染為獨立的HTML文件,其中包含所有必要的數據和資源。

使用asyncData

asyncData方法是Nuxt.js特有的,它允許你在服務器端預取數據並在客户端複用這些數據。在上面的示例中,我們簡單地更改了message的值,但在實際應用中,你可能會在這裏調用API獲取數據。

中間件

中間件(Middleware)是一種功能,允許你在路由變更前後執行特定的邏輯。中間件可以全局、頁面級或佈局級使用,以處理諸如認證、數據預加載、路由守衞等任務。

1. 全局中間件

全局中間件是在nuxt.config.js文件中配置的,影響應用中的所有頁面:

// nuxt.config.js
export default {
  // ...
  router: {
    middleware: ['globalMiddleware1', 'globalMiddleware2'] // 可以是字符串數組
  }
};
中間件文件通常位於middleware/目錄下,例如middleware/globalMiddleware1.js:

javascript
// middleware/globalMiddleware1.js
export default function (context) {
  // context 包含 req, res, redirect, app, route, store 等信息
  console.log('Global Middleware 1 executed');
}

2. 頁面級中間件

頁面級中間件隻影響特定的頁面。在頁面組件中聲明中間件:

// pages/about.vue
export default {
  middleware: 'pageMiddleware' // 可以是字符串或函數
};
對應的中間件文件位於middleware/目錄,例如middleware/pageMiddleware.js:

javascript
// middleware/pageMiddleware.js
export default function (context) {
  console.log('Page Middleware executed');
}

3. 佈局級中間件

佈局級中間件類似於頁面級,但作用於使用該佈局的所有頁面。在佈局組件中聲明中間件:

// layouts/default.vue
export default {
  middleware: ['layoutMiddleware1', 'layoutMiddleware2']
};
對應的中間件文件位於middleware/目錄:

javascript
// middleware/layoutMiddleware1.js
export default function (context) {
  console.log('Layout Middleware 1 executed');
}

// middleware/layoutMiddleware2.js
export default function (context) {
  console.log('Layout Middleware 2 executed');
}

中間件的上下文(Context)

中間件函數接收一個上下文對象作為參數,該對象包含以下屬性:

  • req(HTTP請求對象,僅在服務器端有效)
  • res(HTTP響應對象,僅在服務器端有效)
  • redirect(用於重定向的函數)
  • app(Vue實例)
  • route(當前路由信息)
  • store(Vuex Store,如果已啓用)
  • payload(如果有asyncData返回的數據)

中間件可以順序執行,每個中間件可以決定是否繼續執行鏈中的下一個中間件,或者通過redirect函數中斷路由。

動態路由

Nuxt.js支持動態路由,這對於處理如博客文章、用户資料等具有動態ID的內容非常有用。在pages/目錄下創建一個動態路由文件,如[id].vue:

<!-- pages/post/[id].vue -->
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script>
export default {
  async asyncData({ params, $axios }) {
    const response = await $axios.$get(`/api/posts/${params.id}`);
    return { post: response.post };
  }
};
</script>

這裏的[id]表示動態參數,asyncData會自動處理這個參數並獲取對應ID的博客文章。

佈局
佈局允許你定義全局或特定頁面的通用結構。在layouts/目錄下創建一個default.vue文件:

<!-- layouts/default.vue -->
<template>
  <div>
    <header>
      <nav>
        <!-- 導航鏈接等 -->
      </nav>
    </header>
    <main>
      <nuxt /> <!-- 這裏將插入頁面內容 -->
    </main>
    <footer>
      <!-- 底部信息等 -->
    </footer>
  </div>
</template>

默認情況下,所有頁面都將使用此佈局。如果你想為特定頁面設置不同的佈局,可以在頁面組件中指定:

// pages/about.vue
export default {
  layout: 'custom' // 在layouts/下創建custom.vue
};
插件與庫集成
Nuxt.js支持Vue.js的插件,你可以在nuxt.config.js中配置:

javascript
// nuxt.config.js
export default {
  plugins: [
    { src: '~plugins/vuetify.js', ssr: true },
    { src: '~plugins/vue-chartjs.js', mode: 'client' } // 僅在客户端運行
  ]
};

然後在plugins/目錄下創建相應的文件,如vuetify.js

// plugins/vuetify.js
import Vue from 'vue';
import Vuetify from 'vuetify';
import 'vuetify/dist/vuetify.min.css';

Vue.use(Vuetify);

配置與優化

Nuxt.js 配置文件(nuxt.config.js)

nuxt.config.js 是Nuxt應用的主要配置文件,用於定製化應用的行為。以下是一些常用的配置項:

  • 模式(mode):設置應用的運行模式,可選值有 'spa'(單頁面應用)、'universal'(服務端渲染)和 'static'(靜態生成)。默認為 'universal'。
  • head:配置頁面的 <head> 部分,如標題、元數據、鏈接等。
  • css:指定全局CSS文件,可以是文件路徑數組。
  • build:配置構建過程,如transpile、extractCSS、extend等。例如,可以在這裏添加Babel插件或調整Webpack配置。
  • router:自定義路由配置,如base路徑、模式等。
  • axios:配置axios模塊,包括基礎URL、代理設置等。
  • plugins:註冊全局Vue插件,可以指定在客户端或服務器端加載。
  • modules:加載外部模塊,如@nuxtjs/axios、@nuxtjs/proxy等。
  • env:定義環境變量,這些變量將在構建時注入到客户端和服務器端。
// nuxt.config.js
export default {
  // 項目名稱
  name: 'my-nuxt-app',
  
  // 項目模式:spa, universal, static
  mode: 'universal', // 默認值,支持服務器端渲染
  
  // 應用元信息
  head: {
    title: 'My Nuxt App',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: 'App description' }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  },
  
  // CSS樣式
  css: [
    '@/assets/css/main.css'
  ],
  
  // 路由配置
  router: {
    base: '/my-nuxt-app/', // 應用的基礎路徑
    extendRoutes(routes, resolve) {
      // 手動擴展或修改路由
    }
  },
  
  // 構建配置
  build: {
    transpile: [/^my-vue-component/], // 需要轉譯的模塊
    vendor: ['lodash'], // 公共庫,提前打包
    extractCSS: true, // 提取CSS到單獨文件
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10,
            filename: 'vendors.js'
          }
        }
      }
    }
  },
  
  // Axios配置
  axios: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000/api', // API基礎URL
    browserBaseURL: 'https://api.example.com' // 客户端的API基礎URL
  },
  
  // Plugins
  plugins: [
    { src: '@/plugins/vue-my-plugin', ssr: false } // 異步加載的插件,ssr: false 表示僅客户端加載
  ],
  
  // Modules
  modules: [
    '@nuxtjs/axios', // 安裝並配置axios模塊
    '@nuxtjs/pwa' // 安裝並配置PWA模塊
  ],
  
  // 環境變量
  env: {
    apiKey: 'your-api-key',
    apiUrl: 'https://api.example.com'
  },
  
  // Vuex Store配置
  store: true, // 自動創建Vuex store
  loading: { color: '#3B8070' }, // 加載指示器顏色
  
  // 服務端中間件
  serverMiddleware: [
    { path: '/api', handler: '~/api/index.js' } // 使用自定義的服務器端中間件
  ],
  
  // 靜態生成配置
  generate: {
    dir: 'dist', // 輸出目錄
    fallback: true, // 對未預渲染的動態路由生成404頁面
    routes: () => ['/about', '/contact'] // 預渲染的指定路由
  }
};

優化策略

  • 異步數據預取(asyncData/fetch):利用asyncData或fetch方法在服務器端預取數據,減少客户端渲染的負擔。
  • 代碼拆分:Nuxt.js自動進行代碼拆分,確保只有當路由被訪問時才加載相關代碼。
  • 靜態站點生成(SSG):使用nuxt generate命令生成靜態HTML文件,適用於內容不頻繁變動的站點,提高加載速度和SEO友好性。
  • 緩存策略:利用HTTP緩存策略,如ETag、Last-Modified,減少重複請求。
  • Vue.js優化:確保Vue組件的優化,如避免無用的watcher、使用v-once減少重新渲染等。
  • 圖片優化:使用正確的圖片格式(如WebP),並確保圖片尺寸適當,使用懶加載技術。
  • Service Worker:集成PWA支持,使用Service Worker進行離線緩存和推送通知。
  • Tree Shaking:確保你的依賴庫支持Tree Shaking,以剔除未使用的代碼。
  • 分析與監控:使用nuxt build --analyze或集成第三方工具(如Google Lighthouse)進行性能分析,持續監控應用性能。

靜態站點生成(SSG)

Nuxt.js 的靜態站點生成(Static Site Generation, SSG)是通過 nuxt generate 命令實現的。這個命令會遍歷應用的路由,為每個路由生成一個預渲染的 HTML 文件,這些文件可以直接部署到任何靜態文件託管服務上。以下是關於SSG的一些關鍵點:

1. 配置: 在 nuxt.config.js 文件中,可以配置 generate 選項來控制靜態生成的行為:

   export default {
     generate: {
       dir: 'dist', // 輸出目錄,默認為dist
       fallback: true, // 對未預渲染的動態路由生成404頁面
       routes: () => ['/about', '/contact'], // 預定義的靜態路由
       exclude: ['/admin/*'], // 排除某些路由
       interval: 5000, // 生成間隔,單位毫秒
       concurrency: 10 // 併發生成的路由數量
     }
   }

2. 生成: 運行 npm run generateyarn generate 來啓動靜態生成過程。Nuxt.js 會根據 generate.routes 裏的配置生成對應的 HTML 文件。如果沒有顯式定義,它會自動掃描 pages/ 目錄下的所有文件來生成路由。

3. 數據預取: 在頁面組件中,可以使用 asyncData 或 fetch 方法來預取數據。這些數據會在生成靜態頁面時被注入到 HTML 中,使頁面在客户端加載時無需額外請求:

   // pages/about.vue
   export default {
     async asyncData({ params, $axios }) {
       const aboutInfo = await $axios.$get('/api/about')
       return { aboutInfo }
     }
   }

4. 中間件處理:服務器端的中間件不會在SSG過程中執行,因為SSG是在沒有服務器環境的情況下生成靜態文件。所以,如果需要在生成時執行某些邏輯,最好在 asyncData 或 fetch 中處理。

5. 部署: 生成的靜態文件可以部署到任何靜態文件託管服務,如 Netlify、Vercel、GitHub Pages 或 AWS S3。這些服務通常不需要運行任何服務器端代碼,只需上傳生成的 dist 文件夾即可。

6. SEO 優化: SSG 提高了SEO,因為搜索引擎爬蟲可以讀取預渲染的 HTML 內容,而無需等待JavaScript執行。

7. 動態路由: 對於動態路由,Nuxt.js 會嘗試生成所有可能的組合。如果無法預測所有可能的動態路由,可以手動在 generate.routes 中指定,或者使用 generate.includePaths 和 generate.excludePaths 來控制。

8. 404 頁面: 設置 generate.fallback 為 true 會為未預渲染的動態路由生成一個404頁面,當用户訪問這些路由時,Nuxt.js 會嘗試在客户端渲染它們。

運行nuxt generate命令,Nuxt.js將生成靜態HTML文件。

驗證和錯誤處理

驗證(Validation)

驗證通常涉及表單數據或API請求的輸入驗證。Nuxt.js本身不直接提供驗證庫,但你可以集成像Vuelidate、vee-validate這樣的第三方庫,或者使用TypeScript等進行類型檢查。

使用Vee-Validate

1. 安裝: 首先,你需要安裝vee-validate庫:

   npm install vee-validate

2. 配置: 在nuxt.config.js中添加Vue插件配置:

   export default {
     plugins: [{ src: '~/plugins/vee-validate', ssr: false }]
   };

3. 創建插件: 在plugins/vee-validate.js中配置Vee-Validate:

   import Vue from 'vue';
   import VeeValidate from 'vee-validate';

   Vue.use(VeeValidate);

4. 使用: 在你的組件中使用Vee-Validate進行表單驗證:

   <template>
     <form @submit.prevent="submitForm">
       <input v-model="email" name="email" v-validate="'required|email'"/>
       <span>{{ errors.first('email') }}</span>
       <button type="submit">Submit</button>
     </form>
   </template>
   <script>
   export default {
     data() {
       return {
         email: ''
       };
     },
     methods: {
       submitForm() {
         this.$validator.validateAll().then(result => {
           if (result) {
             // 驗證成功邏輯
           } else {
             // 驗證失敗邏輯
           }
         });
       }
     }
   };
   </script>

錯誤處理

Nuxt.js提供了幾種處理錯誤的方法,包括全局錯誤處理和頁面特定的錯誤處理。

全局錯誤處理

  • 自定義錯誤頁面: 在layouts目錄下創建error.vue文件,用於自定義錯誤頁面佈局。
  • 捕獲全局錯誤: 在nuxt.config.js中配置error屬性來捕獲全局錯誤:
   export default {
     error: {
       // 頁面不存在時的處理
       pageNotFound({ error, store, app, env }) {
         // 處理邏輯
       },
       // 任何錯誤的處理
       handler(error, { error: nuxtError, store, app, env }) {
         // 處理邏輯
       }
     }
   };

頁面特定錯誤處理

在頁面組件中,可以使用asyncData或fetch方法的try-catch結構來處理錯誤:

export default {
  async asyncData({ params, error }) {
    try {
      const data = await fetchSomeData(params.id);
      return { data };
    } catch (err) {
      error({ statusCode: 500, message: '數據獲取失敗' });
    }
  }
};

API請求錯誤處理

對於API請求,如果你使用了@nuxtjs/axios模塊,可以在請求攔截器中統一處理錯誤:

// plugins/axios.js
import axios from 'axios';
import { toast } from '~/utils/toast';

axios.interceptors.response.use(null, (error) => {
  const { status } = error.response;
  if (status === 401) {
    // 處理未授權錯誤
  } else if (status >= 500) {
    // 處理服務器錯誤
    toast.error('服務器錯誤');
  }
  return Promise.reject(error);
});

export default ({ $axios }, inject) => {
  inject('axios', $axios);
};

確保在nuxt.config.js中註冊此插件。

Vue生態系統集成

Vue Router:

Nuxt.js 自動為你的應用生成了一個基於文件結構的路由系統。路由配置通常不需要手動編寫,但可以通過 nuxt.config.jsrouter 屬性進行擴展。

Vuex:

Nuxt.js 自動創建了一個 Vuex store。在 store 目錄下,你可以創建模塊化的 state、mutations、actions 和 getters。例如,創建一個 store/modules/users.js 文件來管理用户數據。

   // store/modules/users.js
   export const state = () => ({
     users: []
   });

   export const mutations = {
     SET_USERS(state, payload) {
       state.users = payload;
     }
   };

   export const actions = {
     async fetchUsers({ commit }) {
       const response = await this.$axios.get('/api/users');
       commit('SET_USERS', response.data);
     }
   };

Vue CLI:

Nuxt.js 提供了自己的構建工具,但它也基於 Vue CLI。這意味着你可以使用類似 Vue CLI 的命令行工具,如 npx nuxt generate(靜態生成)或 npx nuxt build(構建應用)。

Babel:

Nuxt.js 默認配置了 Babel,以便支持最新的 JavaScript 特性。你通常不需要手動配置 Babel,除非有特殊需求。

TypeScript:

若要使用 TypeScript,設置 typescript: true 在 nuxt.config.js 中,Nuxt.js 會自動配置 TypeScript 支持。

ESLint:

為了代碼質量檢查,可以在項目中安裝 ESLint 並配置 .eslintrc.js。Nuxt.js 提供了 @nuxt/eslint-module 插件來簡化集成。

   // nuxt.config.js
   module.exports = {
     buildModules: [
       '@nuxt/typescript-build',
       '@nuxtjs/eslint-module' // 添加 ESLint 集成
     ],
     eslint: {
       fix: true, // 自動修復錯誤
       ignoreDuringBuilds: true // 忽略構建期間的錯誤
     }
   };

VueUse:

VueUse 是一個包含各種實用功能的 Vue 用例庫。要集成,首先安裝 @vueuse/core,然後在組件中導入並使用功能。

   npm install @vueuse/core
   // 在組件中
   import { useCounter } from '@vueuse/core';

   export default {
     setup() {
       const count = useCounter(0); // 使用計數器功能
       // ...
     }
   };

Vue插件:

可以通過 nuxt.config.js 的 plugins 配置項來全局註冊 Vue 插件。例如,集成 Vue Toastify 用於顯示通知:

   // nuxt.config.js
   export default {
     plugins: [{ src: '~plugins/toastify.js', ssr: false }]
   };
   // plugins/toastify.js
   import Vue from 'vue';
   import Toastify from 'toastify-js';

   Vue.use(Toastify);

使用Nuxt.js工作流

Nuxt.js提供了開發、構建和部署的完整工作流。使用nuxt命令啓動開發服務器,nuxt build進行生產構建,nuxt start啓動生產服務器,nuxt generate生成靜態文件。

性能優化

  1. 靜態生成(SSG): 使用 nuxt generate 命令生成預渲染的HTML文件,這可以大大提高首屏加載速度,對SEO友好。
  2. 代碼分割: Nuxt.js 默認會進行代碼分割,將應用分為多個小塊,只加載當前頁面需要的代碼,減少了初始加載的體積。
  3. 延遲加載(Lazy Loading): 對於大型應用,可以考慮延遲加載組件或模塊,只在需要時加載。可以使用<nuxt-child :lazy="true">或<component :is="...">結合async組件來實現。
  4. 優化資源:
  5. 圖片:使用正確的格式(如WebP),壓縮圖片,使用懶加載(<img :src="..." :loading="lazy">),或者使用nuxt-image或nuxt-picture組件。
  6. CSS:提取CSS到單獨文件,減少內聯樣式。
  7. JS:利用Tree Shaking剔除未使用的代碼。
  8. 異步數據預取: 使用 asyncData 或 fetch 方法預加載數據,確保數據在渲染之前已經準備好。
  9. 服務端緩存: 使用 nuxt-ssr-cache 模塊來緩存服務器端渲染的結果,減少不必要的API調用。
  10. HTTP緩存: 設置正確的緩存頭(如Cache-Control),利用瀏覽器緩存靜態資源。
  11. 路由守衞: 使用 beforeRouteEnter 等路由守衞,避免在不需要時加載數據。
  12. 減少HTTP請求: 合併多個CSS和JS文件,減少HTTP請求數量。
  13. 優化API性能: 優化後端接口,減少響應時間,使用分頁、過濾和緩存策略。
  14. 利用CDN: 將靜態資源託管在CDN上,加快全球用户的加載速度。
  15. 優化Vuex狀態管理: 避免不必要的計算屬性和監聽器,減少狀態改變的開銷。
  16. 性能審計: 使用Lighthouse、Chrome DevTools或其他性能審計工具定期檢查應用性能,並根據報告進行改進。
  17. Service Worker: 如果適用,集成PWA特性,利用Service Worker進行離線緩存和資源預加載。
  18. 模塊優化: 選擇性能高效的第三方模塊,並確保它們已經針對SSR進行了優化。

2500G計算機入門到高級架構師開發資料超級大禮包免費送!

user avatar linlinma 头像 nihaojob 头像 kobe_fans_zxc 头像 longlong688 头像 inslog 头像 shuirong1997 头像 ccVue 头像 nznznz 头像 xiaohe0601 头像 momeak9 头像 kasong 头像 snowwolfarden 头像
点赞 68 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.