vue+ssr开发美团网PC版

使用 nuxt 编写美团 PC 端(vue+ssr+egg)

前置

nuxt 安装

npx create-nuxt-app <projectName> or yarn create nuxt-app <projectName>

nuxt 启动

为了使 node 文件支持 es6 语法和 async,await,需要使用 babel 配置一下。
npm i babel-cli -g,然后在 package.json 里面的 dev 和 start 命令的最后面加上--exec babel-node,根目录新建一个.babelrc文件。

1
2
3
4
5
6
{
"presets": ["es2015", "stage-0"],
"plugins": [
["transform-runtime"]
]
}

因为安装的 eslint-plugin-vue 这个 eslint 校验规则是能自闭和的元素就自闭和,我个人不习惯,所以要重新设置一下.eslintrc.js,rules 里面添加上这段代码

1
2
3
4
5
6
7
8
9
'vue/html-self-closing': ["error", {
"html": {
"void": "never",
"normal": "any",
"component": "any"
},
"svg": "always",
"math": "always"
}]

安装npm i babel-preset-es2015 babel-plugin-transform-runtime babel-runtime -D,然后就可以执行npm run dev了。

安装 redis

运行brew install redis安装。
Linux:cd 到 redis 的 src 目录 ./redis-server &(添加&后台启动) or ./redis-server & ../redis.conf(配置文件启动)
运行brew services start redis后台运行 redis 服务,运行brew services sop redis停止后台服务。
redis-server服务端运行命令,redis-cli运行客户端。quit退出服务端。

nrm

npm install -g nrm 一个能切换仓库源的工具。
nrm ls列出仓库源。
nrm use ***使用源。

mongoDB

在数据文件目录使用命令导入数据表 mongoimport -d meituan -c areas areas.dat meituan 是数据库名字 areas 是集合(表名) areas.dat 是数据文件。
导出导入操作集合

技巧

curl -d "name=lily" http://*****一个 linux 命令,发送 post 请求。
如果发送 get 请求则不用加-d。

项目中遇到的问题

让 node 支持 es6 导入语法

  1. npm i babel-cli babel-preset-es2015 --save

  2. 根目录新建一个文件.babelrc 内容

    1
    2
    3
    {
    "presets": ["es2015"]
    }
  3. package.json 的 script 启动项里面命令行最后面添加--exec babel-node

组件引入 css

如果使用 sass、less、stylus 一般平时可以使用@/alias的方式引入样式表,但是新版 nuxt 已经不支持如此引入方式。

请注意: 从 Nuxt 2.0 开始,〜/alias 将无法在 CSS 文件中正确解析。你必须在 url CSS 引用中使用assets(没有斜杠)或@别名,即 background:url(“assets/banner.svg”)

不过实际测试@不好使..
或者使用styleResources来处理样式的变量和mixin
yarn add @nuxtjs/style-resources && yarn add stylus-loader stylus,nuxt.config.js

1
2
3
4
5
6
7
8
export default {
modules: ["@nuxtjs/style-resources"],
styleResources: {
scss: "./assets/variables.scss",
less: "./assets/**/*.less"
// sass: ...
}
};

这样就能随处使用定义过的变量或者函数…

axios 封装及报错

axios 报错Cannot read property 'cancelToken' of undefined,原因是因为 request 拦截没有 return config
优雅的切换 baseUrl,由于这个项目的登录接口在本地做了一次转发,因此在登录的时候请求的是本地的接口,其他时候都是请求的 egg 接口,因此我们要根据情况适当的切换 baseUrl,但是由于前端项目对 axios 进行了一遍封装,因此简单的引入 axios 设置 baseUrl 是无效的,我们需要引入 request.js 里面的 request 对象,然后request.defaults.baseURL = url.Api设置 baseUrl,同理 token 也可以如此设置:request.defaults.headers.Authorization = 'Bearer ' + res.token

第三方模块全局引入

没有找到设置全局模块的 webpack 配置,不过 nuxt 的 plugin 使用说明里面提供了几种全局使用第三方模块的方式。例如将第三方模块注入 Vue 或者 ctx 实例,这样就能在客户端或者服务端使用该模块。
例子:
npm install good-storage --save

1
2
3
4
// plugin/storage.js
import Vue from "vue";
import storage from "good-storage";
Vue.prototype.$storage = storage;
1
2
3
4
5
6
7
8
// nuxt.config.js
plugins: [
'@/plugins/element-ui',
{
src: '@/plugins/storage',
ssr: false
}
],

使用:this.$storage.set(key,value)

页面字体图标引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@font-face {
font-family: "header";
src: url("https://s0.meituan.net/bs/fe-web-meituan/076a786/font/header.woff?t=1501036445556")
format("woff");
}

@font-face {
font-family: "home-category-iconfont";
src: url("https://s0.meituan.net/bs/fe-web-meituan/076a786/font/home-category-iconfont.woff?t=1503027869484")
format("woff");
}

@font-face {
font-family: "MFShangHei-Regular";
src: url("https://s0.meituan.net/bs/fe-web-meituan/076a786/font/MFShangHei-Regular.woff?t=1503027869484")
format("woff");
}

@font-face {
font-family: "numbers";
src: url("https://s0.meituan.net/bs/fe-web-meituan/076a786/font/numbers.woff?t=1503027869484")
format("woff");
}

首先引入对应的组件图标并重命名,然后

1
2
3
4
5
6
7
8
9
10
11
.home-category-iconfont {
font-family: home-category-iconfont !important;
font-size: 14px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

.hc-icon-food:before {
content: "\e622";
}

单独写一个字体图标对应的 css 文件,并且在需要用到字体图标的组件引入,每个图标有单独对应的 css 语句,需要去审查美团网并复制。

页面组件开发

使用template包裹代码块做v-for循环,不会产生多余的 dom 结构。
使用crypto-js模块进行密码加密。

  1. npm i -save crypto-js
  2. let CryptoJS = require('crypto-js')
  3. CryptoJS.MD5(***).toString()

问题

Nuxt 不支持组件获取异步数据,所以要么在 mounted 里面写(不支持 ssr),要么在 store 的 index 的 nuxtServerInit 方法里面写,但是这样会延迟首屏渲染时间(猜的,这个方法阻塞),要么在页面里面获取数据然后使用 prop 或者存到 vuex 里面,但是 Nuxt 只有页面是支持预渲染请求数据的,组件并不支持,因此若想获取并保存和渲染数据,需要在最一开始使用到次组件的页面获取该组件的数据。这里我选择存 vuex 里面。

vuex

vuex 使用模块功能时注意是否设置命名空间,注意 state 里面多了一层嵌套数据。

Nuxt 使用 vue-awesome-swiper 做轮播等设置

照一般的组件设置后,使用方式如下
使用方式
div 标签上面加v-swiper指令设置

后台业务开发

注册

用户表的几个字段,例如手机号、邮箱、昵称等字段,除了手机号是唯一但不必需的,手机号必需,因为这几个字段要用来进行登录等操作。同时因为注册的时候只使用手机号进行注册,为了避免因为邮箱、昵称等字段重复引起的注册失败,所以美团自己给用户的昵称设置了一个乱码,邮箱置为空,待以后填写。

登录

mongoose 进行一个值多个字段的查询,用途是根据用户输入的一个值来进行手机号、邮箱、昵称的查询:

1
2
3
4
5
6
7
let user = await ctx.model.User.find({
$or: [
{ phoneNum: parseInt(ctx.request.body.username) },
{ userName: ctx.request.body.username },
{ email: ctx.request.body.username }
]
});

问题

大部分页面和接口并不是一定要登录才能看到的,因此状态校验需要单独写在那些页面里面,包括后台路由校验 token 也是同理。

Nuxt 和 eggjs 的部署

部署 eggjs

  1. 首先编译

总结

emmm,这个项目新建的时候不该用 axios module,它将 axios 又处理了一下,跟平常的使用习惯有较大差别。
用 MongoDB 有点不大好,MongoDB 本身文档资料较少,初步上手问题较多。
Jokcy 老师说前后端分离开发约定的数据基本就是扯淡,后端会改,弄得前端很麻烦,建议前端定义自己的数据格式然后请求接口的时候做一层映射。

☆☆☆☆☆ 这个项目思想出了问题

nuxt 我选择 vue+koa 混合的方式创建项目,我的后端使用 eggjs 进行编写,然而我忽略了一个问题,那就是:这个项目本身是基于 koa(后端项目)做的前端 Vue 项目(前端占比大),前后端共用一个端口(cookie 不能跨域),共用一套路由,一些身份校验、token 保存要使用 cookie 来做,但是 cookie 一般不能跨域,即 3000 的 nuxt 端口无法直接向 7001 的 eggjs 服务端口发送 cookie,这样用户的登录态的保存就是个问题。
不过幸运的是可以换其他的方式进行解决。

Nuxt 前后端分离开发 前端刷新判断用户状态 nuxt 做接口转发

由于采用前后端分离式开发,nuxt 项目刷新的时候会将 vuex 数据刷新掉,页面渲染之前又无法取得 localStorage 保存的本地值,如果采用等页面渲染完成时候,再获取本地值请求接口获得用户数据的方式不安全也不优雅。经过网上查找之后我选择如下文章采用的方式,使用创建的 nuxt 项目自带的 koa 进行一下请求转发,然后由 koa 设置 cookie,用 nuxtServerInit 函数进行状态判断。

参考资料:Nuxt.js 服务端渲染实践,从开发到部署

Author: XavierShi
Link: https://blog.xaviershi.com/2018/12/26/vue+ssr开发美团网PC版/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.