全栈体验——搭建属于自己的个人网站

1,833 阅读15分钟

前言

最近终于把个人网站正式上线了,先放成果!
www.ssevenk.com

这是我的个人博客,主要记录自己的前端学习心得
项目前后端都是一个人自己运作起来的
脚手架:Vuecli
服务端:node的express
数据库:mongodb
第三方插件:element-ui,simpleMDE等等

本文会从构思,开发,上线,备案全流程记录一下网站的搭建过程
超长文预警!!!

基本架构

整个项目其实是一个单页面应用,所有的页面跳转都是前端控制的 后端只是提供数据接口,来对数据库进行增删查改

而从功能上来说,主要分成了四个板块

  • 文章:我的原创文章
  • 杂谈:与技术不太相关的个人杂谈
  • 收藏:别人的优秀文章,做成收藏夹的形式,点击直接跳转至对应网站链接
  • 留言板:供浏览者留言

因此,这里也就有四种数据表需要设计

后端

Mongodb-数据定义与存放

我用的数据库是mongodb
比较灵活,而且与node配合使用起来更为简洁,可以直接用js操作
如上文所说,我们需要四种数据表结构,于是可以直接用js建立

新建一个curd.js文件
引入mongoose(第三方的API库)并连接数据库(第一次连接并没有这个数据库,会帮我们自动创建)

//curd.js
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/gblog')

在其中定义数据结构,比如定义一个文章的表结构

const MonBlog = mongoose.model('monblog', {
    title: {
        type: String,
        required: true //表示这个属性是必需的
    },
    content: {
        type: String,
        required: true
    },
    date: {
        type: String,
        required: true
    },
    zan: {
        type: Number,
        required: true
    },
    comments:[]
})

其他三种表同理进行设计,然后将这四种数据模型导出给router.js,让其进行增删查改的数据接口设计

//curd.js
module.exports = {
    MonBlog: MonBlog,
    MonEssay: MonEssay,
    MonArticle: MonArticle
    MonMessage: MonMessage
}

增删查改

新建一个router.js引入curd.js导出的三种数据模型

const express = require('express')
const curd = require('./curd')
var router = express.Router()

const MonBlog = curd.MonBlog
const MonEssay = curd.MonEssay
const MonArticle = curd.MonArticle
const MonMessage = curd.MonMessage

然后就可以运用mongoose提供的API来进行增删查改 比如我们通过前端post的信息,来新增数据,可以设计这么一个接口

router.post('/data/createBlog', (req, res) => {
    new MonBlog(req.body).save((err) => {
        if (err) res.send(err)
    })
})

查询接口

    router.get('/data/blog', function (req, res) {
    MonBlog.find((err, data) => {
        if (err) {
            res.send(err)
            return
        }
        res.send(data)
    })
})

删除接口

router.post('/data/deleteBlog',(req, res) => {
        MonEssay.findByIdAndDelete(req.params.id, function (err, data) {
            if (err) res.send(err)
        })
    }))

增删查改的守护——token

很显然,类似增删改的接口,是不能让游客或者恶意攻击者去调用的
为了让后端能认识我,知道是管理员在调用
我们就要用到token
在前端设计一个登录页,我通过密码登陆后,把后端发给我的token存进localStorage中,在调用一些高风险级别的接口时带上这个token

这里我们先安装两个插件,express-jwt和jsonwebtoken
新建jwtAuth.js文件

const expressJwt = require("express-jwt");
const secretOrPrivateKey = "woshisiyao" 
const jwtAuth = expressJwt({
secret: secretOrPrivateKey
}).unless({
path: ['/data/blog',
    '/data/essay',
    '/data/article',
    '/data/message',
    /^\/data\/blog\/.*/,
    /^\/data\/essay\/.*/,
    /^\/data\/article\/.*/,
    '/data/password'
   ] });

module.exports = jwtAuth;

上面的secretOrPrivateKey自定义了一个私钥,这是安全性的保障
用它来给我们的token加上尾部签名

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输入产生的输出总是不一样的。
所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,就会拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

在项目中,并不是所有的接口都是需要被保护的,比如获取数据的接口,以及特定文章内容的接口都是需要向游客开放的
上面的unless就是用来告诉后端,哪些接口不用token验证
其中,/data/blog/:id,这种形式的url是不行的,需要用正则表达式来写 /^\/data\/blog\/.*/

然后在router.js中,引入jwtAuth文件和jsonwebtoken插件
设计密码登陆的接口

router.post('/data/password', (req, res) => {
if (req.body.password == '123456') {
    res.json({
        result: 'succeed',
        token: jwt.sign({
            name: "ssevenk"
        }, secretOrPrivateKey, {
                expiresIn: 60 * 60 * 24
            })
    })
}
else {
    res.send('验证失败')
}
})

如果密码正确,就向前端发送token,其中expiresln可以设置token的时效

前端收到token,存进localStorage中,比如

localstorage.setItem('token',token)

然后在发送请求的时候,需要写在headers中

    headers: {
      Authorization: `Bearer ${localStorage.getItem("token")}`,
    }

如果没有成功,后端会报401 Unauthorized的错误

相关知识可以参考这几篇文章
juejin.cn/post/684490…
blog.leapoahead.com/2015/09/06/…
www.ruanyifeng.com/blog/2018/0…

后端结构

最终的后端结构,就分为四个文件

其中入口文件是app.js,用它来引入其他文件,并安装中间件

//app.js
const express=require('express')
const bodyParser=require('body-parser')

const router=require('./router')
const app=express()

app.use(bodyParser.urlencoded({extended:false}))

app.use(bodyParser.json())

app.use('/',router)
	
//划分一个端口给后端,这里监听7000端口
app.listen(7000)

在命令行敲上node app.js,就成功启动了我们的后端

开发过程的跨域

可以注意到,虽然开发过程,前后端都在我自己的电脑上
但后端监听的是7000端口,而前台页面在8080端口访问
所以为了实现跨域请求 我们需要对config文件夹中的index.js文件进行一些修改

proxyTable: {
  '/data': {
    target: 'http://localhost:7000',
    changeORIGIN: true
  }
},

给proxyTable添加一种跨域访问规则,书写api的时候也都以/data开头 这样所有的请求都可以跨域访问到在7000端口的后端了

至此我们便完成了项目中的这一块部分

前端

后台管理系统

后端已经配置好了,但现在数据库里没有数据啊
于是需要有一个后台管理系统,来可视化管理数据

界面中的那个表格基本就是element-ui的官方示例
参考官网做法,很快就能撸一个出来
element.eleme.cn/#/zh-CN/com…

唯一需要额外提两句的,一个是右上角那个搜索
输入后可以即时显示搜索的结果在数据表格里
直接在el-table表格绑定的数据上进行操作

:data="((things.filter(data=>!search||data.title.toLowerCase().includes(search.toLowerCase())“

另外一个是分页,el-pagination

<el-pagination
        @current-change="handleCurrentChange"
        :page-size="pageSize"
        :current-page="currentPage"
        :total=" things.length"
        layout="total, prev, pager, next"
      ></el-pagination>

当点击页码切换的时候,把页码更新

handleCurrentChange(currentPage) {
  this.currentPage = currentPage;
}

然后再一次对我们之前的el-table标签上的数据进行改进

:data="((things.filter(data=>!search||data.title.toLowerCase().includes(search.toLowerCase())).slice((currentPage-1)*pageSize,currentPage*pageSize)))"

这里的逻辑先后要理清,是先对数据进行搜索的过滤再分页

路由守卫

这里你们应该也注意到了,管理的入口是出现在页面上的tab选项卡里的
所以其实后台管理也是单页面应用的一部分,只是这个入口没有对你们开放
为了避免游客在地址栏直接输入路由闯入管理系统,我们要给需要保护的路由添加守卫

{
  path: '/Back',
  name: 'Back',
  component: () => import('../components/back.vue'),
  beforeEnter: (to, from, next) => {
      next({ path: '/Login' })
  }}

使用beforeEnter,把需要守卫的路由引入到登陆页去
在登陆页,输入管理员密码提交给后台
前台把后台返回的token存进localStorage中,比如就叫做‘token’
那么如果在路由的时候,从localStorage中能取到这个符号,那么守卫就允许进行下一步跳转
所以上面的守卫改成

{
  path: '/Back',
  name: 'Back',
  component: () => import('../components/back.vue'),
  beforeEnter: (to, from, next) => {
		if(localStorage.getItem('token') { next() }
		else {
      next({ path: '/Login' })}
  }}

这种方式是需要给每个要守卫的路由都添加一遍beforeEnter
还有一种方法是进行全局守卫

  router.beforeEnter: (to, from, next) => {
		if(to.meta&&!localStorage.getItem('token') { next({ path: '/Login' })} }
		else {
      next()
  }}

添加全局守卫后,给要守卫的路由添加一个meta属性,值为true就可以了

markdown编辑器

管理系统最重要的功能就是写文章和修改文章内容

我用的是simpleMDE来实现markdown编辑器
并添加了本来没有的本地上传图片功能
可以参见我的另一篇文章
juejin.cn/post/684490…

前台路由

来到了我们的前端展示页

头部的header是不变的,所以直接封装了个组件放在了App.vue中

 <myHeader></myHeader>
 <router-view class="main"></router-view>

所谓的路由跳转,其实只是在更新main这块内容
可以看一下前端的路由文件router.js

export default new Router({
routes: [
// 前台页面路由
{
  path: '/',
  redirect: '/Blogs'
},
{
  path: '/Blogs',
  name: 'Blogs',
  component: () => import('../components/ShowBlogs.vue')
},
{
  path: '/Essays',
  name: 'Essays',
  component: () => import('../components/ShowEssays.vue')
},
{
  path: '/Articles',
  name: 'Articles',
  component: () => import('../components/ShowArticles.vue')
},
{
  path: '/Message',
  name: 'Message',
  component: () => import('../components/ShowMessages.vue')
}]

四大功能的路由,其中首页做了重定向,直接默认跳转到文章页面 而跳转的实现都在抬头的tab选项卡中

  <router-link to="/Blogs">文章</router-link>
  <router-link to="/Essays">杂谈</router-link>
  <router-link to="/Articles">收藏</router-link>
  <router-link to="/Message">留言板</router-link>
  <router-link v-if="admin" to="/Back">管理</router-link>

其实每一个按钮都是个router-link,其中如果能在localStorage中取到token,就把管理入口也暴露
选中的颜色样式都是通过.router-link-active:nth-of-type(x)来设计的,类似于这样

#myHeader-link .router-link-active:nth-of-type(1) {
border-bottom: solid rgb(255, 184, 126) 3px;}

列表页数据过滤

在列表页,我们只需要知道id,标题和时间,并不需要获取文章的具体内容(content)
为了避免不必要的数据传输浪费时间,我们在后端要对数据进行一次过滤

//router.js
router.get('/data/blog', function (req, res) {
MonBlog.find((err, data) => {
    if (err) {
        res.send(err)
        return
    }
    var simpleData = data.map(item => {
        return {
            title: item.title,
            date: item.date,
            _id: item._id
        }
    })
    res.send(simpleData.reverse())
})
})

使用map映射,只把需要的东西传过去

搜索功能

搜索功能有点和后台管理不一样
这一次我定义了一个show数组
点击搜索之后,调用函数来进行搜索,把搜索出来的结果存放在show中
所以我们展示的一直都是show数组

由于有三个功能用到了搜索框
所以我把搜索框单独做成了一个组件
并没有注册为全局组件
因为我们希望它是作为ShowBlogs、ShowEssays、ShowArticles这三个组件的子组件存在的,方便调用父组件提供的方法

import mySearch from "./mySearch";

每个父组件都引入一次
点击搜索时,向父组件发射搜索框里的内容,并调用父组件的方法

//mySearch.vue
 methods:{
      search() {
          this.$emit('search',this.content)
      }
  }

在父组件中

<mySearch @search="searchfor"></mySearch>

methods:{
searchfor(s) {
  this.show = (this.blogs.filter(item => {
    if (item.title.includes(s)) {
      return item;
    }
  }));
}}

针对每个组件,搜索框的颜色不一样 是通过$route.path来判断,动态绑定样式

computed: {
mySearch: function() {
  return {
    mySearch1: this.$route.path == "/Blogs",
    mySearch2: this.$route.path == "/Essays",
    mySearch3: this.$route.path == "/Articles"
  };
}
}

具体的文章内容

点进具体的文章或杂谈时,把id传进路由,页面拿着id去请求后端的数据
然后调用simpleMDE的原型方法将拿到的字符串转换为html格式

this.contentMarkdown=SimpleMDE.prototype.markdown(this.theOne.content)

v-html展示出来

<div id='markdownArticle' v-html="contentMarkdown"></div>

markdown的样式我用了少数派的一款css,大家也可以自己去寻找中意的,或者自己写

点赞&评论功能

点赞的动画组件我用的是vue-clap-button,一个第三方小组件 github.com/AJLoveChina…

<vue-clap-button v-if="flag" size="60" :clicked="isClicked" />

原组件缺少一个绑定的属性,来传递是否已经点过赞这个状态
所以改了一下源码,增加了一个clicked的属性,写在props里面

 props: {
  clicked: {
  type: Boolean,
  default: false
}
},

由于我的网页是没有游客登陆功能的,所以要判断该游客是否已经对这篇文章点过赞了,我用的是localStorage

//判断是否已经点过赞
ifClicked() {
  var zanList = JSON.parse(localStorage.getItem("zanList"));
  if (!zanList) return;
  if (
    zanList.some(item => {
      return item == this.theOne._id;
    })
  )
    this.isClicked = true;
}

当点赞的时候,给用户localStorage存一个数组,保存已经点过赞的文章的id
就可以在页面初始化的时候,通过判断文章id是否在这个数组中,来告诉点赞按钮呈现红色还是灰色状态

评论功能
从功能上来说其实没什么好讲的,不过由于comments不是一个数据,只是文章数据身上的一个属性
所以数据库并不会给每条评论自动分配一个id
而为了便于删除之类的操作,我们需要在游客提交评论的时候,给它加上一个随机的id

id:Math.random().toString(36).substr(2)

网站的整体风格就是简洁,从评论的头像来说,我也只是在用户提交的时候,随机给了一个整数

headIndex: Math.floor(Math.random() * 6)

用来分配一个匿名头像

 <img class="cmt-div-img" :src="require('../assets/img/head/'+item.headIndex+'.jpeg')" alt />

注意因为是动态引入,图片需要用require的方法引入

收藏&留言功能

收藏其实就是一个个超链接,引向外部的网站
留言功能类似评论,这里不再赘述

响应式设计

网站只做了非常简单的响应式设计,基本就是把列表页的横向布局设置成了竖向布局 而有一点需要注意,是原来如果竖着排会在最下面的搜索按钮和公告跑到了最上面

这里主要用的是order,可以在flex布局中改变元素的顺序

@media screen and(max-width:500px) {
    .mySlider {
         order: -1;
  }

至此,本网站的开发过程到一段落,下面就要把它上线了!

上线

购买服务器及域名

我买的是阿里云的服务器,学生党100多一年还挺划算的
要记住购买时设置的用户名和密码
然后买了ssevenk.com这个域名(ssevenk是我常用的用户名),本来想买真名的gaoyufeng.com,但已经被人买走了
然后我们就要在服务器上配置我们的环境了,相当于把本地的那一套搬到服务器上去

安装node

首先安装一个可以远程操纵服务器的软件 我用的是puTTy

输入服务器ip地址进行连接,然后输入用户名和密码,就可登陆操作我们的服务器

安装node的方法看了很多网上的教程,我只能说不靠谱
各种复杂,一顿操作,最后还报错

后来偶然发现阿里云的官方手册上就有安装node的教程 按照阿里云的步骤,简单又有效

安装mongodb

整个上线过程中最令人蛋疼的一步! 踩坑无数,整整装了一天才装好

直接上图

这其中,为了保证数据库的安全,我们要把上图配置中的noauth改为auth=true
然后设置文件夹权限

 $ cd /usr/mongodb
 $ chmd 777 db
 $ chmod 777 log

启动mongodb

$ cd ~
$ mongod -f /usr/mongodb/mongodb.conf

由于mongodb默认使用的是27017端口,所以我们登陆阿里云,开放这个端口

点击右上角,添加安全组规则即可

下面我们要给数据库增加权限 在mongodb已启动的情况下,命令行输入

use admin
db.createUser(
	{
	   user:'root',
		 pwd:'root',
		 roles:[ { role: 'userAdminAnyDatebase',db:'admin' } ]
	}

创建超级用户 不过mongodb有一点比较特殊,超级用户并不是在数据库和子数据库都是畅通无阻的

事实上,mongdb的用户权限和数据库是绑定的,也就是创建一个新的数据库,要想在这个新的数据库插入数据,是需要创建一个与之对应的用户的

说的很绕,操作逻辑也很诡异(所以在这一步卡了很久) 在创建超级用户后,正确的操作步骤是:

  1. use admin 进入admin数据库
  2. db.auth('root','root') 超级用户认证
  3. db.createUser({user:'gyf',pwd:'123456',roles:[{role:'readWrite',db:'gblog'}]}) 创建gyf用户,并为它指定新的数据库gblog
  4. db.auth('gyf','123456')切换成gyf用户
  5. use gblog 切换至gblog数据库
  6. db.repo.insert({'name':'hhh'}) 这样就可以正确插入一条数据了

单单只有一个超级用户是不能操作其他新建的数据库的

使用mongodb compass(就是安装时被捆绑下载的那个),可以可视化的看到这个过程
目标数据库的名字都是admin,但是root用户能看到所有的数据库,gyf用户只能看到gblog数据库

此时其实我们已经可以在本地连接远端的那个数据库了
在原来后端中连接数据库的地方,改成

mongoose.connect('mongodb://gyf:123456@0.0.0.0:27017/gblog?authSource=admin')

前面输入用户名和密码,0.0.0.0的地方输入ip地址,最后输入要操作的数据库和权限来源(都是admin给的权限)

部署服务器

环境已经配置好了,下面就要把我们的后端代码部署上去
上传代码我一开始用的是Xftp 6 这个软件,后来发现vscode里面有个sftp的插件
可以直接右键上传,更快捷
安装完插件后,ctrl+shift+p,然后输入sftp:congfig,进行配置,记得要把自动上传关闭

{
"name": "My Server",
"host": "IP地址",
"protocol": "sftp",
"port": 22,
"username": "用户名",
"password": "密码",
"remotePath": "路径",
"uploadOnSave": false
}

然后直接在左侧文件目录,右击sync Local -> Remote把后端代码上传

在本机上我们用的是node\ app.js来启动后端的,不过在服务器上,为了保证它能一直运行,同时提高cpu利用率,我们要使用进程守护工具pm2来启动
先安装pm2

npm install pm2@latest -g
cd /home/blog 切换到项目目录
ln -s /root/node-v10.2.0-linux-x64/bin/pm2 /user/local/bin/ 中间的路径是node的安装位置

然后启动我们的后端

pm2 start app.js --name 'app'

我们的服务器就跑起来了!
上一句话后面如果再跟一个--watch\ ,就会在文件或者文件夹变更时自动重启,我这里因为有上传图片的功能,会改变文件夹,导致图片传到一半服务器就重启了,因此没有使用这个功能
最后把我们的node服务加到进程,保证NodeJs一直在后台运行,就算重启也自动运行

pm2 startup centos #pm2 stratup ubuntu
pm2 save

网页部署

在vuecli中,npm build一下,就可以把我们的工程打包成html文件
不过在这之前,要把build配置中的assetsPublicPath改成“ ./ ” 本来是 ”/“,以避免图片缺失

打包结束后,把整个dist文件夹丢到服务器上 然后如何实现访问它呢,我们就需要在后端中做一些修改 在后端路由中,监听到首页的”/“就把html文件发给客户端

router.get('/', (req, res) => {
res.setHeader("Content-Type", "text/html;charset='utf-8'");

//读文件
fs.readFile("./dist/index.html", "utf-8", function (err, data) {
    if (err) {
        console.log("index.html loading is failed :" + err);
    }
    else {
        //返回index.html页面
        console.log(data)
        res.end(data);
    }
})
})

这里用到了node的fs模块,可以读取文件

还记得我们后端监听的是7000端口吗,所以此时,如果你的ip地址为1.2.3.4
那么在地址栏输入1.2.3.4:7000,就能访问到这个网页了(记得要去安全组开放7000端口)

DNS解析

不过没有哪个网站是直接让别人去访问ip地址的
这时就要把我们买的域名,通过DNS解析绑定到我们的ip地址上来了
这一步在阿里云的官网就可操作,比较简单

不过,我们只能绑定到ip地址,而默认的网页端口其实是80端口
所以在后端,我们把原来监听的7000端口,改成80端口(同样记得安全组开放80端口)

//app.js
app.listen(80)

就可以通过www.ssevenk.com访问到了!!

首屏空白优化

服务器运行不比本地,网络传输速度突然就重要了起来
一开始,网页刚部署上去,打开网址到完整看见内容,整整花了10s!!
那一刻才知道什么叫天荒地老

后来进行了一下打包的优化,可以看我的这篇文章
juejin.cn/post/684490…

不过还是存在首屏一定时间的空白
这时候,就有一个东西很重要:加载动画
比起3s的纯空白,游客反而更能接受4s带着加载动画的空白
你得让游客知道,我的网站可以打开的,你只要等一会就行

在index.html中(不是App.vue,因为等到加载App.vue已经过了一段时间了)添加

<div id="Loading">
    <div class="loader-inner ball-beat">
       <div></div>
       <div></div>
       <div></div>
     </div>
</div>
	
<style type="text/css">
#Loading {
  top: 50%;
  left: 50%;
  position: absolute;
  -webkit-transform: translateY(-50%) translateX(-50%);
  transform: translateY(-50%) translateX(-50%);
  z-index: 100;
}

@-webkit-keyframes ball-beat {
  50% {
    opacity: 0.2;
    -webkit-transform: scale(0.75);
    transform: scale(0.75);
  }

  100% {
    opacity: 1;
    -webkit-transform: scale(1);
    transform: scale(1);
  }
}

@keyframes ball-beat {
  50% {
    opacity: 0.2;
    -webkit-transform: scale(0.75);
    transform: scale(0.75);
  }

  100% {
    opacity: 1;
    -webkit-transform: scale(1);
    transform: scale(1);
  }
}

.ball-beat>div {
  background-color: rgb(255, 184, 126);
  width: 20px;
  height: 20px;
  border-radius: 100% !important;
  margin: 3px;
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
  display: inline-block;
  -webkit-animation: ball-beat 0.7s 0s infinite linear;
  animation: ball-beat 0.7s 0s infinite linear;
}

.ball-beat>div:nth-child(2n-1) {
  -webkit-animation-delay: 0.35s !important;
  animation-delay: 0.35s !important;
}
</style>

然后在最先出来的文章列表组件中,在mounted生命周期移除这个加载

mounted() {
  document.body.removeChild(document.getElementById("Loading"));
},

备案

由于我买的是国内的主机,所以还要去备案
而这,又是一部血泪史

ICP备案

首先进行的是ICP备案,直接在阿里云上申请
强烈建议即便网站还没做好,也先去把ICP备了
不然等你急着上线的时候,就知道什么叫度日如年了

跟着阿里云的流程走,只要符合规定就能通过
这里有一点很坑
因为我是在上海上学,家乡江苏,但是我没有上海居住证就无法在上海备案
填江苏省备案吧,我的江苏手机卡又早就不用了,不能用上海手机号备案
两难之下,只好又去买了张江苏的电话卡

总之真的是非常繁琐,还有幕布拍照什么的,前前后后花了我20天
不过,没想到麻烦还没结束

公安备案

现在好像有新规定,ICP备案好了,还要在30天内进行公安备案
看网上回馈,好像这个会比ICP快很多,而且不繁琐
但没想到我又掉坑了

无内容???why???
发给别的朋友看,也都可以看到网站内容啊

IE兼容

苦思冥想,终于想到了一种可能
他们还在使用IE!
打开ie11,输入ssevenk.com,果然,一片空白

于是,开始与这个老古董搏斗
vuecli其实有与ie浏览器兼容的办法,参考官网,利用polyfill cli.vuejs.org/zh/guide/br…

但是没想到官网的例子怎么也不能生效
我打开ie11永远是一片空白
后来无奈,从vuecli3又用回了vuecli2 在webpack.base.conf中设置

entry: {
app: ['babel-polyfill', './src/main.js']
},

在index.html中设置

<meta http-equiv="X-UA-Compatible" content="IE=edge">

终于成功了!!!!!!!
纪念一下

总结

网站的构思是在4月份,其实5月初网站雏形就做好了
后来增增改改,基本上6月份就基本完成了服务器部署
但是直到7月中旬,才正式合法地上线了

整个就是一血泪史,不过不得不说,只有实际操作了一下,才能更理解一个网站的创作流程
对于各种技能的使用,服务端客户端的理解也会上一个台阶
其实中间有不少小细节都没讲,挑了主要的部分,不过也已经是长篇大论了\

开发不易,不过最棒的还是那份正式上线的成就感!!
www.ssevenk.com

码字也不易,希望我的经历对你们有所帮助