本文目录
  1. 简介
  2. 创建项目
  3. 路由
    1. 静态路由
    2. 动态路由
    3. NuxtLink组件
  4. 布局
  5. 错误页面
  6. 生命周期
  7. 中间件
    1. 路由中间件
    2. 命名中间件
  8. 数据获取
    1. asyncData
    2. mouted
  9. 集成sass
  10. 集成elementui
  11. 国际化
    1. 基本使用
    2. 语言切换组件
    3. element ui 国际化
  12. SEO的处理
    1. 全局处理
    2. 单个页面或者布局组件中配置
  13. vuex的使用
  14. 配置环境变量
  15. 集成axios
  16. 配置组件自动导入
  17. 配置ip和端口
  18. 配置src目录
  19. svg组件封装
  20. 静态资源引入
  21. 集成tailwindcss
  22. nuxt2项目模板

分类: web前端 | 标签: nuxt

nuxt2笔记整理

发表于: 2023-12-12 17:44:28 | 字数统计: 4.8k | 阅读时长预计: 23分钟

Nuxt2官网

简介

nuxt是基于vue语法来实现SSR(服务器渲染)的框架,解决单页应用不利于SEO的问题

创建项目

由于国内网络问题经常下载失败,推荐采用沙盒下载环境项目模板

https://stackblitz.com/github/nuxt/starter/tree/v2-stackblitz?file=README.md

1702344800164

新建一个nuxt2-study文件夹,将下载后的文件解压到这个文件夹中

1702345551102

然后安装依赖

npm i

启动

npm run dev

访问:localhost:3000

路由

每个页面组件都是一个Vue组件 ,pages目录中的每个文件都是一个页面。

静态路由

假设文件结构如下

pages/
--| user/
-----| index.vue
-----| one.vue
--| index.vue

那么生成的路由如下

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'user',
      path: '/user',
      component: 'pages/user/index.vue'
    },
    {
      name: 'user-one',
      path: '/user/one',
      component: 'pages/user/one.vue'
    }
  ]
}

动态路由

假设文件结构如下

pages/
--| _slug/
-----| comments.vue
-----| index.vue
--| users/
-----| _id.vue
--| index.vue

那么生成的路由如下

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'users-id',
      path: '/users/:id?',
      component: 'pages/users/_id.vue'
    },
    {
      name: 'slug',
      path: '/:slug',
      component: 'pages/_slug/index.vue'
    },
    {
      name: 'slug-comments',
      path: '/:slug/comments',
      component: 'pages/_slug/comments.vue'
    }
  ]
}

动态路由参数的获取和vue-router一致:this.$route.params.xxx

NuxtLink组件

和router-link类似,用于跳转页面。 对于指向站点内页面的所有链接,请使用< NuxtLink >。如果有其他网站的链接,应该使用< a >标签

<NuxtLink to="/book/10001">查看详情</NuxtLink>

布局

功能类似vue中的router-view组件,一个项目可以有多个布局。默认使用的布局是 layouts/default.vue

  • 定义新布局 layouts/myLayout.vue
<template>
    <div>
        <h1>自定义页面头部</h1>
        <!-- Nuxt功能类似router-view组件 -->
        <Nuxt />
        <h1>自定义页面底部</h1>
    </div>
</template>
  • 组件中使用布局
<template>
    <div>
        <NuxtLink to="/book/10001">查看详情</NuxtLink>
    </div>
</template>

<script>
export default {
    name: 'Nuxt2StudyIndex',
    layout: 'myLayout',//使用自定义的布局
    //...
};
</script>

错误页面

当404和500的时候显示一个错误页面

layouts/error.vue

<template>
    <!-- 自定义错误页面 -->
    <div class="error-container">
        <div class="info">
            <div class="code">{{ error.statusCode }}</div>
            <div v-if="error.statusCode == 404" class="desc">抱歉!页面走丢了</div>
            <div v-else class="desc">抱歉!服务器繁忙</div>
            <el-button @click="$router.replace('/home')">返回首页</el-button>
        </div>
    </div>
</template>
  
<script>
export default {
    props: ['error']
}
</script>
<style lang="scss" scoped>
.error-container {
    background-color: #55aafe;
    height: 100vh;
    display: flex;
    .info {
        margin: auto;
        color: #fff;
        text-align: center;
        .code {
            font-size: 200px;
            font-weight: bold;
        }
        .desc{
            font-size: 30px;
            margin: 20px 0;
        }
    }
}
</style>

生命周期

生命周期如下: 重点掌握asyncData() 和 mouted()

lifecycle

中间件

在请求到达之前做一些处理、比如重定向、鉴权等操作

路由中间件

新建 middleware/middleware.js

export default function ({app,redirect,route }) {
    console.log('这个会在服务器的控制台打印')
    //默认跳转到 home
    if("/" == route.fullPath || "/zh" == route.fullPath){
        redirect('/home')
    }
}

nuxt.config.js 配置中间件

export default {
    router: {
        middleware: "middleware", //middleware对应middleware.js的文件名
    }
    //...
}

命名中间件

新建middleware/auth.js

//鉴权中间件
export default function ({ app,store, redirect, route }) {
  console.log("auth middleware", store.getters.token, "auth url", route.path);

  //白名单
  let whiteList = [
    "/sign-in", 
    '/register',
    '/home'
  ];
  //let whiteListZh = whiteList.map(m => app.localePath(m))
  //whiteList = [...whiteList,...whiteListZh] //生成中英

  if (process.client && !store.getters.token) {//客户端渲染的页面 没有token
    if (!(whiteList.includes(route.path) || whiteList.some(s => route.path.startsWith(s)))) {//不在白名单
       return redirect("/sign-in");//跳转登录
      //return redirect(app.localePath("/sign-in"));//跳转登录
    }
  }
}

在页面组件或者布局组件中

<template>

</template>

<script>
export default {
  name: 'MyLayout',
  middleware: ['auth'] //当前组件将要受到auth中间件控制
}
</script>

<style lang="scss" scoped>
</style>

数据获取

asyncData

asyncData只能用于页面级组件,asyncData不能访问组件实例(this)。它接收上下文作为其参数。Nuxt会自动将返回的对象与组件数据进行浅层合并。

<template>
    <div>
        <ul>
            <li v-for="car in carList">{{ car.id + '-' + car.name }}</li>
        </ul>
    </div>
</template>

<script>
export default {
    name: 'Nuxt2StudyIndex',
    data() {
        return {
            carList: []
        };
    },
    //服务端渲染:服务器请求并渲染数据 这个钩子内部不能使用this
    async asyncData(ctx) {
         //query 等价于 this.$route.query  params 等价于 this.$route.params
        console.log('ctx', ctx.query,ctx.params)
        //模拟发送请求
        let carList = await new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve([
                    { id: 1, name: '宝马' },
                    { id: 2, name: '奔驰' },
                    { id: 3, name: '五菱' },
                ])
            }, 1000);
        })
        return { carList } //这个数据将自动合并到组件data()
    }
};
</script>

mouted

用于客户端渲染,和vue中的一致

<template>
    <div>
        <ul>
            <li v-for="car in carList">{{ car.id + '-' + car.name }}</li>
        </ul>
    </div>
</template>

<script>
export default {
    name: 'Nuxt2StudyIndex',

    data() {
        return {
            carList: []
        };
    },

    //客户端渲染
    mounted() {
        let cartList = [
            {id: 1,name: '宝马'},
            {id: 2,name: '奔驰'},
            {id: 3,name: '五菱'},
        ] 
        this.carList = cartList
    }
};
</script>

集成sass

安装

npm i sass@1.32.13
npm i sass-loader@10.1.1

页面使用

<template>
  <div class="container">
    <h1>hello nuxt2</h1>
  </div>
</template>
<style lang="scss" scoped>
.container{
  h1{
    color: red;
  }
}
</style>

集成elementui

安装

npm i element-ui -S

新建plugins/elementui.js

import Vue from 'vue';
import ElementUI from 'element-ui';
Vue.use(ElementUI);

nuxt.config.js引入elementui.js插件和样式

export default {
    css: [
        "element-ui/lib/theme-chalk/index.css",
        'element-ui/lib/theme-chalk/display.css'
      ],
    plugins: [
        "~/plugins/elementui.js",
    ],
    //...
}

页面使用

<template>
  <div class="container">
    <el-button type="primary">test</el-button>
  </div>
</template>

国际化

基本使用

安装

npm i @nuxtjs/i18n@7.3.1

项目根目录新建lang/zh.js

export default {
    name: '姓名',
    age: '年龄'
}

项目根目录新建lang/en.js

export default {
    name: 'name',
    age: 'age'
}

项目根目录新建i18n.js

import en from "./lang/en.js";
import zh from "./lang/zh.js";
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);

const i18n = {
  locales: ["en", "zh"], //有多少地区的语言就添加多少种
  defaultLocale: "en", //默认的地区语言
  vueI18n: {
    fallbackLocale: "en", //回退策略,指定的locale中没有找到对应资源的情况下使用的locale
    messages: {
      //要渲染的信息,有多少语言就添加多少种
      en,
      zh,
    }
  },
}

nuxt.config.js

import i18n from "./i18n.js"; //导入上面的i18n配置文件
export default {
    //模块
  modules: [["@nuxtjs/i18n", i18n]],
    //...
}

页面中使用

this.$t(‘key’) 获取当前语言下对应的value值

this.localePath(url) 根据语言处理路径

this.$i18n.locale 获取当前语言

<template>
  <div class="container">
    <el-tag type="primary">{{ $t('name') }}</el-tag>
    <el-tag type="success">{{ $t('age') }}</el-tag>
    <nuxt-link :to="url">本地化路径</nuxt-link>
  </div>
</template>

<script>
export default {
  name: 'Nuxt2StudyIndex',
  data() {
    return {
      url: ''
    };
  },
  mounted() {
    //localePath方法可根据语言处理路径 英文:/book/1001 中文:/zh/book/1001
    this.url = this.localePath('/book/1001')
  }
};
</script>

访问

英文:http://localhost:3000/
中文:http://localhost:3000/zh

语言切换组件

components/Language.vue

<template>
    <a :href="webUrl">
        <img :src="require('@/assets/images/zh.svg')" v-if="$i18n.locale == 'en'" @click.prevent="changeLang('zh')"
            style="cursor: pointer" />
        <img :src="require('@/assets/images/en.svg')" v-if="$i18n.locale == 'zh'" @click.prevent="changeLang('en')"
            style="cursor: pointer" />
    </a>
</template>

<script>
export default {
    name: 'Language',

    data() {
        return {
        }
    },

    computed: {
        webUrl() {
            return this.$i18n.locale == 'en' ? "/zh" + this.$route.fullPath : this.$route.fullPath
        }
    },

    methods: {
        // 切换语言
        changeLang(lang) {
            let url = this.$route.fullPath
            url = url.includes('/zh') ? url.replace('/zh', '') : '/zh' + url
            url = url.replace('null', '')
            // location.href = url
            this.$router.replace(url)
        }
    },
};
</script>

<style lang="scss" scoped>
a:hover{
    background: #f2f4f7;
}
a{
    padding: 8px!important;
}
img {
    vertical-align: middle;
}
</style>

element ui 国际化

layouts/default.vue

<template>
  <!-- 默认布局组件 -->
  <div>
    <!-- 渲染内容 -->
    <Nuxt />
  </div>
</template>
<script>
import en from 'element-ui/lib/locale/lang/en'
import zh from 'element-ui/lib/locale/lang/zh-CN'
import locale from 'element-ui/lib/locale'
export default {
  name: 'Default',
  mounted() {
    //elementui国际化
    let lang = this.$i18n.locale == 'en'? en :zh
    locale.use(lang)
  }
};
</script>

SEO的处理

全局处理

在nuxt.config.js 中可以给所有页面配置

export default {
  //全局SEO设置
  head: {
    title: "Tranalysis system",
    htmlAttrs: {
      lang: "en",
    },
    meta: [
      { charset: "utf-8" },
      { name: "viewport", content: "width=device-width, initial-scale=1" },
      { hid: "description", name: "description", content: "Tranalysis system" },
    ],
    link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
  },
}

单个页面或者布局组件中配置

这种方式更好,可以支持国际化

<template></template>
<script>
export default {
    head() {
        let title = '标题111'
        let description = '描述2222'
        return {
          title,//网页标题
          meta: [
            {
              hid: 'keywords',
              name: 'keywords', //网页关键字
              content: title
            },
            {
              hid: 'description',
              name: 'description',//网页描述
              content: description
            },
          ]
        }
    },
}
</script>
<style lang="scss" scoped></style>

vuex的使用

和原来的用法一致,nuxt中已经内置vuex,所以不需要安装vuex,只需要安装vuex持久化插件即可

安装vuex持久化插件

npm i vuex-persistedstate@4.1.0

新建 plugins/vuex-persistedstate.js

import createPersistedState from 'vuex-persistedstate'

//vuex持久化
export default ({ store }) => {
    createPersistedState({
        storage: localStorage
    })(store)
}

nuxt.config.js

export default {
  plugins: [
    //...
    { src: '~/plugins/vuex-persistedstate.js', ssr: false } //引入vuex-persistedstate插件
  ],
}

store/modules/user.js

const user = {
    state: {
        count: 0
    },
    mutations: {
        SET_COUNT: (state, data) => {
          state.count = data
        },
    },
    actions: {}
}
export default user;

store/getters.js

const getters = {
    count: state => state.user.count
}

export default getters

store/index.js

import Vue from "vue";
import Vuex from "vuex";
import user from "./modules/user";
import getters from "./getters";
Vue.use(Vuex);
//主要这里要用箭头函数的形式导出,否则会报错
export default () => new Vuex.Store({
  getters,
  modules: {
    user
  },
});

组件中使用vuex

<template>
    <div>
        <el-button @click="add" type="primary">{{ count }}</el-button>
    </div>
</template>
<script>
import {mapGetters,mapMutations} from 'vuex'
export default {
    name: 'Nuxt2StudyIndex',
    computed:{
        ...mapGetters(['count'])
    },
    methods: {
        ...mapMutations(['SET_COUNT']),
        add(){
            this.SET_COUNT(this.count+1)
        }
    },
}
</script>

配置环境变量

安装

npm i cross-env@7.0.3 -D

项目根目录新建env.js

export default {
    //开发环境
    dev:{
      NODE_ENV: 'dev',
      PROJECT_TITLE: 'NUXT-DEV',
      ENV_API:'http://dev.com/api',//API地址
    },
    //测试环境
    stage:{
      NODE_ENV: 'test',
      PROJECT_TITLE: 'NUXT-TEST',
      ENV_API:'http://test.com/api',//API地址
    },
    //正式环境
    prod:{
      NODE_ENV: 'prod',
      PROJECT_TITLE: 'NUXT-PRODUCTION',
      ENV_API:'http://prod.com/api',//API地址
    }
}

nuxt.config.js

import env from "./env.js"; //导入上面的环境变量
export default {
    env: env[process.env.NODE_ENV] //配置环境变量
    //...
}

package.json 配置

{
  "scripts": {
    "dev": "cross-env NODE_ENV=dev nuxt dev",
    "build:stage": "cross-env NODE_ENV=stage nuxt build",
    "start:stage": "cross-env NODE_ENV=stage nuxt start",
    "build:prod": "cross-env NODE_ENV=prod nuxt build",
    "start:prod": "cross-env NODE_ENV=prod nuxt start",
    "generate:prod": "cross-env NODE_ENV=prod nuxt generate",
    "generate:stage": "cross-env NODE_ENV=stage nuxt generate"
  },
}

页面中使用

<template>
    <div>
        {{ title }}-{{ baseAPI }}
    </div>
</template>
<script>
export default {
    name: 'Nuxt2StudyIndex',
    data() {
        return {
            title: process.env.PROJECT_TITLE,
            baseAPI: process.env.ENV_API
        }
    }
}

集成axios

安装

npm i @nuxtjs/axios@5.13.6

plugins/axios.js 参考下面按需修改即可

import { Message } from "element-ui";
import { getToken } from "@/utils/auth";
//get请求参数处理
function tansParams(params) {
  let result = "";
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    var part = encodeURIComponent(propName) + "=";
    if (value !== null && value !== "" && typeof value !== "undefined") {
      if (typeof value === "object") {
        for (const key of Object.keys(value)) {
          if (
            value[key] !== null &&
            value[key] !== "" &&
            typeof value[key] !== "undefined"
          ) {
            let params = propName + "[" + key + "]";
            var subPart = encodeURIComponent(params) + "=";
            result += subPart + encodeURIComponent(value[key]) + "&";
          }
        }
      } else {
        result += part + encodeURIComponent(value) + "&";
      }
    }
  }
  return result;
}
export default function ({ $axios, redirect, app, store, $router, $route }) {
  //如果需要国际化可以:app.i18n.t("key")

  //请求拦截
  $axios.interceptors.request.use(
    (config) => {
      const isToken = (config.headers || {}).isToken === false;
      if (getToken() && !isToken) {
        config.headers["Authorization"] = "Bearer " + getToken(); // 让每个请求携带自定义token 请根据实际情况自行修改
      }
      // get请求映射params参数
      if (config.method === "get" && config.params) {
        let url = config.url + "?" + tansParams(config.params);
        url = url.slice(0, -1);
        config.params = {};
        config.url = url;
      }
      return config;
    },
    (error) => {
      console.log("axios request error", error);
      return Promise.reject(error);
    }
  );
  //响应拦截
  $axios.interceptors.response.use(
    (response) => {
      const code = response.data.code || 200;
      console.log("code", code, "axios response:" + response);
      const res = response.data;
      //文件下载
      if ("business/product/downloadProductData" == response.config.url) {
        return res;
      }
      if (code === 401) {
        //登录过期
        // if(app.i18n.t(res.msg)) Message({type: 'error', message: app.i18n.t(res.msg)})
        // store.dispatch("LogOut").then(() => {
        //   app.router.push(app.localePath("/"));
        // });
        Message({ type: "error", message: "登录已过期,请重新登录!" });
        app.router.push(app.localePath("/"));
      } else if (code === 200) {
        //普通请求
        return res;
      } else if (code === 500) {
        Message({ type: "error", message: app.i18n.t(res?.msg) });
        return Promise.reject(new Error(res?.msg || "Error"));
      } else {
        return res;
      }
    },
    (error) => {
      console.log("err" + error);
      let { message } = error;
      Message({ message, type: "error", duration: 5 * 1000 });
      return Promise.reject(error);
    }
  );
}

plugins/api.js

import login from '@/api/login'
import menu from '@/api/menu'
export default function({ $axios },inject) {
    inject('loginAPI',login($axios)) //组件中使用this.$loginAPI.xxx方法名调用
    inject('menuAPI',menu($axios)) //组件中使用this.$menuAPI.xxx方法名调用
}

api/menu.js

export default function menu(request) {
  /**
   * 获取菜单
   */
  const getRouters = (lang,menuSuit=1) => {
    return request({
      url: `/system/menu/getRouters/${lang}/${menuSuit}`,
      method: "get",
    });
  };

  return {
    getRouters,
  };
}

api/login.js

export default function login(request) {
    // 注册方法
      function register(data) {
        return request({
          url: "/auth/register",
          headers: {
            isToken: false, //无需携带token
          },
          method: "post",
          data: data,
        });
      }
        // 获取用户详细信息
      function getInfo() {
        return request({
          url: "/system/user/getInfo",
          method: "get",
        });
      }
    return {
        register,
        getInfo
      };
}

nuxt.config.js 中配置

export default {
    //第一步:配置axios请求前缀
    axios: {
        // proxyHeaders: false
        baseURL: env[process.env.NODE_ENV].ENV_API 
        //...
    }
    //第二步:增加axios相关的两个配置
    plugins: [
        "~/plugins/axios.js",
        "~/plugins/api.js", 
        //...
    ],
    //第三步:模块中增加axios
    modules: ["@nuxtjs/axios", ["@nuxtjs/i18n", i18n]],
    //...
}

页面中使用

export default {
    methods: {
        async test(){
          //调用方法:plugins/api.js中的 inject('loginAPI',login($axios)) 第一个参数前面+$调用
          const res = await this.$loginAPI.register({username: 'tom',password: 123456})
          //await this.$loginAPI.getInfo()
          //this.$menuAPI.getRouters()
        }
    }
}

配置组件自动导入

nuxt.config.js

export default {
    components: true
    //...
}

配置ip和端口

nuxt.config.js

export default {
    server: {
        port: 81, // default: 3000
        host: "0.0.0.0", // default: localhost
    }
    //...
}

配置src目录

默认不使用src目录,如果需要配置像下面这样

---| node_modules/
---| nuxt.config.js
---| package.json
---| src/
------| assets/
------| components/
------| layouts/
------| middleware/
------| pages/
------| plugins/
------| static/
------| store/

则需要在nuxt.config.js中配置

export default {
    srcDir: "./src"
    //...
}

svg组件封装

封装svg组件,方便后续svg图片的使用

首先安装

npm i svg-sprite-loader@6.0.11 -D

在nuxt.config.js中配置

const path = require('path'); //导入path模块
export default {
    build: {
        vendor: ["element-ui"],
        extend(config, ctx) {
          // ...
          const svgRule = config.module.rules.find((rule) =>
            rule.test.test(".svg")
          );
          //这里将svg图片拷贝到 》 根目录/static/svg文件夹中
          svgRule.exclude = [path.resolve(__dirname, "./static/svg")];
          config.module.rules.push({
            test: /\.svg$/,
            include: [path.resolve(__dirname, "./static/svg")],
            loader: "svg-sprite-loader",
            options: {
              symbolId: "icon-[name]",
            },
          });
        },
    },
    //...
}

新建 components/SvgIcon.vue 组件

<!-- nuxt中使用svg https://blog.csdn.net/qq_26373925/article/details/108405038 -->
<template>
    <svg :class="svgClass" aria-hidden="true">
        <use :xlink:href="iconName" />
    </svg>
</template>
  
<script>
export default {
    name: 'SvgIcon',
    props: {
        iconClass: {
            type: String,
            required: true
        },
        className: {
            type: String,
            default: ''
        }
    },
    computed: {
        iconName() {
            console.log('iconClass', this.iconClass)
            return `#icon-${this.iconClass}`
        },
        svgClass() {
            return this.className ? 'svg-icon ' + this.className : 'svg-icon'
        }
    }
}
</script>
  
<style scoped>
.svg-icon {
    width: 1em;
    height: 1em;
    font-size: 1.2em;
    vertical-align: -0.15em;
    fill: currentColor;
    overflow: hidden;
}
</style>

页面中使用

<template>
    <div>
        <!--dashboard 对应 static/svg/dashboard.svg-->
        <svg-icon icon-class="dashboard" /> <span slot="title">首页</span>
    </div>
</template>
<script>
export default {}
</script>
<style scoped></style>

静态资源引入

static目录和assets目录,都是用来存储静态文件的

assets中的资源会被webpack处理,打包后会在dist中合并成一个文件;static中的资源不会被webpack处理,打包后直接复制到dist(默认是dist/static)下

推荐assets中存放自己的资源(css、images、utils等),static中放第三方资源(pdf.js、iconfont等)

动态绑定中,assets的图片会加载失败,因为webpack使用commonJS规范,需要使用require引入图片(可以通过import的方式引入)

assets 需要 require引入

<!--assets/images/zh.svg-->
<img :src="require('@/assets/images/zh.svg')" />

static直接映射到服务器根目录,可以通过项目根URL访问

<!--static/images/pay/ali.png-->
<img src="/images/pay/ali.png" width="40">

集成tailwindcss

安装

npm i @nuxtjs/tailwindcss@6.10.1

项目根目录 创建 tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [],
  theme: {
    extend: {},
  },
  plugins: [],
}

项目根目录 创建 assets/css/tailwind.css

@tailwind base;
@tailwind components;
@tailwind utilities;

在nuxt.config.js中配置

export default {
    buildModules: ['@nuxtjs/tailwindcss'],
    tailwindcss: {
        cssPath: '~/assets/css/tailwind.css',
        configPath: 'tailwind.config.js',
        exposeConfig: false,
        config: {}
    },
    //...
}

nuxt2项目模板

自建nuxt2项目模板 nuxt2-template

------ 本文结束,感谢您的阅读 ------
本文作者: 程序员青阳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。