基于vue-cli实现一个全栈项目

使用js实现一个全栈的项目:向我提问,以前使用html静态页面实现的(https://github.com/AprilJoy/bulma),这次使用vue实现前端代码,效果如下

image.png

创建vue cli 工程

vue create example

启动vue 的server

npm run serve

安装vue 依赖

npm install axios vue-router vue-axios --save

npm install bootstrap --save

在main.js 里引入bootstrap 4 的css文件

import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "bootstrap/dist/css/bootstrap.min.css";


Vue.config.productionTip = false;


new Vue({
  router,
  store,
  render: h => h(App)

}).$mount("#app");

创建 vue component

在src > components 文件夹下创建文件

  1. HomeComponent.vue
  2. CreateComponent.vue
  3. EditComponent.vue
  4. IndexComponent.vue

在 HomeComponent.vue 文件中添加如下代码

//Home
<template>
  <div class="row justify-content-center">
    <div class="col-md-8">
      <div class="card card-default">
        <div class="card-header">Home Component</div>


        <div class="card-body">I'm the Home Component component.</div>
      </div>
    </div>
  </div>
</template>
<script>
export default {};

</script>

在 App.vue 中引入 HomeComponent.vue 文件

<template>
  <div id="app">
    <HomeComponent />
  </div>
</template>


<script>
import HomeComponent from "./components/HomeComponent.vue";


export default {
  name: "app",
  components: {
    HomeComponent
  }
};

</script>

其他几个component文件类似

配置 vue-router

在router.js 文件中添加以下内容

import Vue from "vue";
import Router from "vue-router";


Vue.use(Router);


import HomeComponent from "./components/HomeComponent.vue";
import CreateComponent from "./components/CreateComponent.vue";
import IndexComponent from "./components/IndexComponent.vue";
import EditComponent from "./components/EditComponent.vue";


const routes = [
  {
    name: "home",
    path: "/",
    component: HomeComponent
  },
  {
    name: "create",
    path: "/create",
    component: CreateComponent
  },
  {
    name: "posts",
    path: "/posts",
    component: IndexComponent
  },
  {
    name: "edit",
    path: "/edit/:id",
    component: EditComponent
  }
];


export default new Router({
  mode: "history",
  routes: routes

});

在App.vue中添加 <router-view></router-view>,这样就回根据路由的地址渲染出对应的component

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>


<script>
export default {
  name: "app",
  components: {}
};

</script>

可以通过下列地址验证一下效果

  1. http://localhost:8080/create
  2. http://localhost:8080/posts
  3. http://localhost:8080/edit/21

创建导航栏

在 App.vue 中添加如下代码

// App.vue


<template>
  <div class="container">
    <nav class="navbar navbar-expand-sm bg-dark navbar-dark">
      <ul class="navbar-nav">
        <li class="nav-item">
          <router-link to="/" class="nav-link">Home</router-link>
        </li>
        <li class="nav-item">
          <router-link to="/create" class="nav-link">Create Post</router-link>
        </li>
        <li class="nav-item">
          <router-link to="/posts" class="nav-link">Posts</router-link>
        </li>
      </ul>
    </nav>
    <br />
    <transition name="fade">
      <router-view></router-view>
    </transition>
  </div>
</template>


<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s;
}
.fade-enter,
.fade-leave-active {
  opacity: 0;
}
</style>


<script>
export default {
  name: "app",
  components: {}
};
</script>

创建想我提问的表单

// CreateComponent.vue

<template>
  <div>
    <h1>Create A Post</h1>
    <form @submit.prevent="addPost">
      <div class="row">
        <div class="col-md-6">
          <div class="form-group">
            <label>Post Title:</label>
            <input type="text" class="form-control" v-model="post.title">
          </div>
        </div>
        </div>
        <div class="row">
          <div class="col-md-6">
            <div class="form-group">
              <label>Post Body:</label>
              <textarea class="form-control" v-model="post.body" rows="5"></textarea>
            </div>
          </div>
        </div><br />
        <div class="form-group">
          <button class="btn btn-primary">Create</button>
        </div>
    </form>
  </div>
</template>

<script>
    export default {
        data(){
        return {
          post:{}
        }
    },
    methods: {
      addPost(){
        console.log(this.post);
      }
    }
  }
</script>

创建nodejs的后台server

安装插件

npm install nodemon --save-dev

body-parser:用于解析数据
core: 用于解决跨域的问题

nodemon:实时更新后台server代码,不用每次更改代码后刷新

在api目录下新建server.js文件

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const PORT = 4000;
const cors = require('cors');

app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

app.listen(PORT, function(){
  console.log('Server is running on Port:',PORT);
});

新建mongo数据库并连接

新建DB.js,存放数据库配置

module.exports = {
  DB: "mongodb://localhost:8900/test"
}

server.js文件添加如下内容

const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const PORT = 4000;
const cors = require("cors");


const mongoose = require("mongoose");
const config = require("./DB.js");


mongoose.Promise = global.Promise;
mongoose.connect(config.DB, { useNewUrlParser: true }).then(
  () => {
    console.log("Database is connected");
  },
  err => {
    console.log("Can not connect to the database" + err);
  }
);


app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());


app.listen(PORT, function() {
  console.log("Server is running on Port:", PORT);
});

创建Mongoose的schema

在文件post.model.js file中新建如下内容,用于定义数据库中的数据结构。这部分定义的Post会在下面的路由文件中用到

// post.model.js


const mongoose = require("mongoose");
const Schema = mongoose.Schema;


// Define collection and schema for Post
let Post = new Schema(
  {
    title: {
      type: String
    },
    body: {
      type: String
    }
  },
  {
    collection: "posts"
  }

 );


module.exports = mongoose.model("Post", Post);

定义node server 的路由

创建CRUD的操作代码在路由文件post.route.js中

const express = require("express");
let postRoutes = express.Router();


// Require Post model in our routes module
let Post = require("./post.model");


// Defined store route
postRoutes.post("/add", function(req, res) {
  let post = new Post(req.body);
  console.log(post);
  post
    .save()
    .then(() => {
      res.status(200).json({ business: "business in added successfully" });
    })
    .catch(() => {
      res.status(400).send("unable to save to database");
    });
});


// Defined get data(index or listing) route
postRoutes.route("/").get(function(req, res) {
  console.log("erro");
  Post.find(function(err, posts) {
    if (err) {
      res.json(err);
    } else {
      res.json(posts);
    }
  });
});


// Defined edit route
postRoutes.route("/edit/:id").get(function(req, res) {
  let id = req.params.id;
  Post.findById(id, function(err, post) {
    if (err) {
      res.json(err);
    }
    res.json(post);
  });
});


//  Defined update route
postRoutes.route("/update/:id").post(function(req, res) {
  Post.findById(req.params.id, function(err, post) {
    if (!post) res.status(404).send("data is not found");
    else {
      post.title = req.body.title;
      post.body = req.body.body;
      post
        .save()
        .then(() => {
          res.json("Update complete");
        })
        .catch(() => {
          res.status(400).send("unable to update the database");
        });
    }
  });
});


// Defined delete | remove | destroy route
postRoutes.route("/delete/:id").delete(function(req, res) {
  Post.findByIdAndRemove({ _id: req.params.id }, function(err) {
    if (err) res.json(err);
    else res.json("Successfully removed");
  });
});


module.exports = postRoutes;
在文件server.js中添加
app.use("/posts", postRoute);

至此,server端内容搭建完毕,下面通过引入axios插件,实现前端向后端发起请求的功能

引入axios发送网络请求

import VueAxios from 'vue-axios';
import axios from 'axios';

Vue.use(VueAxios, axios);

现在一共有3个server正在运行

  1. Vue development server
  2. Node.js server
  3. MongoDB server

现在route.js文件的内容入下

import Vue from "vue";
import Router from "vue-router";


Vue.use(Router);


import VueAxios from "vue-axios";
import axios from "axios";


Vue.use(VueAxios, axios);


import HomeComponent from "./components/HomeComponent.vue";
import CreateComponent from "./components/CreateComponent.vue";
import IndexComponent from "./components/IndexComponent.vue";
import EditComponent from "./components/EditComponent.vue";


const routes = [
  {
    name: "home",
    path: "/",
    component: HomeComponent
  },
  {
    name: "create",
    path: "/create",
    component: CreateComponent
  },
  {
    name: "posts",
    path: "/posts",
    component: IndexComponent
  },
  {
    name: "edit",
    path: "/edit/:id",
    component: EditComponent
  }
];


export default new Router({
  mode: "history",
  routes: routes
});

实现后台数据在前端展示/删除的功能

在IndexComponent.vue文件中的代码如下

<template>
  <div>
    <h1>Posts</h1>
    <div class="row">
      <div class="col-md-10"></div>
      <div class="col-md-2">
        <router-link :to="{ name: 'create' }" class="btn btn-primary"
          >Create Post</router-link
        >
      </div>
    </div>
    <br />


    <table class="table table-hover">
      <thead>
        <tr>
          <th>Title</th>
          <th>Body</th>
          <th>Actions</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="post in posts" :key="post._id">
          <td>{{ post.title }}</td>
          <td>{{ post.body }}</td>
          <td class="btn-w">
            <router-link
              :to="{ name: 'edit', params: { id: post._id } }"
              class="btn btn-primary"
              >Edit</router-link
            >
          </td>
          <td class="btn-w">
            <button
              class="btn btn-danger"
              @click.prevent="deletePost(post._id)"
            >
              Delete
            </button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>


<script>
export default {
  data() {
    return {
      posts: []
    };
  },
  created() {
    let uri = "http://localhost:4000/posts";
    this.axios.get(uri).then(response => {
      this.posts = response.data;
    });
  },
  methods: {
    deletePost(id) {
      let uri = `http://localhost:4000/posts/delete/${id}`;
      /* eslint-disable */
      this.axios.delete(uri).then(response => {
        this.posts.splice(this.posts.indexOf(id), 1);
      });
    }
  }
};
</script>


<style scoped>
td {
  width: 50%;
}
</style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容