当前位置: 首页 > 工具软件 > mevn-stack > 使用案例 >

Isomorphic JavaScript with MEVN stack,第一部分------前端Vue项目创建

夏俊杰
2023-12-01

本文详细记述了使用MEVN技术栈,系统开发CRUD的过程

MEVN:

  • MongoDB

  • Express.js

  • Vue.js

  • Node.js

系统:Win10, 编辑器:VS code

准备环境: 安装vue-cli, mongodb

mongodb下载

01.创建Vue.js应用, 增加vuex, router两选项,其余默认。

F:\Study_js\Packt_Isomorphic JavaScript with MEVN Stack>vue create mevn-stack


Vue CLI v4.5.11
┌───────────────────────────────────────────┐
│                                           │
│   New version available 4.5.11 → 4.5.12   │
│     Run npm i -g @vue/cli to update!      │
│                                           │
└───────────────────────────────────────────┘

? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, Router, Vuex, Linter
? Choose a version of Vue.js that you want to start the project with 2.x
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No


Vue CLI v4.5.11
✨  Creating project in F:\Study_js\Packt_Isomorphic JavaScript with MEVN Stack\mevn-stack.
�  Initializing git repository...
⚙️  Installing CLI plugins. This might take a while...

yarn install v1.22.4
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "win32" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.13: The platform "win32" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...

success Saved lockfile.
Done in 37.37s.
info There appears to be trouble with your network connection. Retrying...
info There appears to be trouble with your network connection. Retrying...
info There appears to be trouble with your network connection. Retrying...
�  Invoking generators...
�  Installing additional dependencies...

yarn install v1.22.4
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.3.2: The platform "win32" is incompatible with this module.
info "fsevents@2.3.2" is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.13: The platform "win32" is incompatible with this module.
info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
Done in 11.55s.
⚓  Running completion hooks...

�  Generating README.md...

�  Successfully created project mevn-stack.
�  Get started with the following commands:

 $ cd mevn-stack
 $ yarn serve

注意:这里删除yarn,使用默认的npm包管理。 删除yarn.lock文件,运行npm install

.eslintrc.js配置如下:

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
  parserOptions: {
    parser: "babel-eslint",
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "multiline-comment-style": ["error", "bare-block"],
    "spaced-comment": ["error", "always"],
    semi: ["error", "always"],
    "semi-spacing": "error",
    "no-extra-semi": "error",
    "no-unexpected-multiline": "error",
    "max-len": ["error", { code: 120 }],
    "comma-style": ["error", "last"],
    "comma-dangle": ["error", "always-multiline"],
    indent: ["error", 2],
    "space-infix-ops": "error",
    "space-before-blocks": "error",
    "brace-style": "error",
    "keyword-spacing": "error",
    "arrow-spacing": "error",
    "space-before-function-paren": [
      "error",
      {
        anonymous: "always",
        named: "never",
        asyncArrow: "always",
      },
    ],
    "newline-per-chained-call": "error",
    "space-in-parens": ["error", "never"],
    "array-bracket-spacing": ["error", "never"],
    "object-curly-spacing": ["error", "always"],
    "comma-spacing": ["error", { before: false, after: true }],
    "no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }],
  },
};

02.创建Vue.js Components

项目中引入bootstrap, jquery, popper

npm install bootstrap --save

npm install jquery popper.js --save

  • 修改main.js ,  增加
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap";
  • 新建 src/components/Navbar.vue, 参考https://getbootstrap.com/docs/4.6/examples/carousel/ 中的<header>, copy到Navbar.vue中修改后如下:
    <template>
      <header>
        <nav class="navbar navbar-expand-md navbar-dark fixed-top custom-bg-dark">
          <a class="navbar-brand" href="#">
            <img style="max-height: 25px" />
            Task Manager
          </a>
          <button
            class="navbar-toggler"
            type="button"
            data-toggle="collapse"
            data-target="#navbarCollapse"
            aria-controls="navbarCollapse"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarCollapse">
            <!-- <ul class="navbar-nav mr-auto">   左对齐 -->
            <ul class="navbar-nav ml-auto">   <!-- 右对齐 -->
              <li class="nav-item active">
                <a class="nav-link" href="/">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="/tasks">Tasks</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="/register">Register</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="/login">Login</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Logout</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Morpheus</a>
              </li>
            </ul>
          </div>
        </nav>
      </header>
    </template>
    
  • 新建src/components/Footer.vue
<template>
  <footer id="custom-footer">
    <span class="mr-4">&copy; 2021 Morpheus</span>
    <span class="mr-4">Private</span>
    <span>Terms of Service</span>
  </footer>
</template>
  • 增加src/components/Main.vue
<template>
  <div class="jumbotron custom-bg-dark">
    <h1 class="display-4">MEVN Stack Task Manager</h1>
    <p class="lead">This is a sample task manager application,
      built with MongoDB, ExpressJS, VueJS, and NodeJS
      technologies.
    </p>
    <hr class="my-4">
    <p>Click below to begin managing tasks for users.</p>
    <a class="btn btn-success btn-lg" href="#" role="button">View Tasks</a>  
  </div>
</template>

  • 修改Home.vue
<template>
  <div id="custom-home">
    <Main />
  </div>
</template>

<script>
import Main from "@/components/Main.vue";

export default {
  name: "home",
  components: {
    Main,
  },
};
</script>
  • 修改App.vue
<template>
  <div id="app">
    <Navbar />
    <div id="app-container">
      <router-view />
    </div>
    <Footer />
  </div>
</template>

<script>
import Navbar from "@/components/Navbar.vue";
import Footer from "@/components/Footer.vue";

export default {
  name: "app",
  components: {
    Navbar,
    Footer,
  },
};
</script>
  • 增加src/assets/css/style.css
* {
  color: white;
}
.custom-bg-dark {
  background-color: #373f46;
}

nav.navbar {
  height: 6rem;
}

a.navbar-brand {
  text-transform: uppercase;
  letter-spacing: 3px;
}

li.nav-item a{
  text-transform: uppercase;
}

@media screen and (max-width: 767px){
  div#navbarCollapse {
    width: 100%;
    position: fixed;
    top: 75px;
    left: 0;
    padding-left: 20px;
    padding-bottom: 5px;
    background-color: #373f46;
  }
}

div#app-container{
  position: fixed;
  top: 96px;
  left: 0;
  bottom: 96px;
  width: 100%;
  padding: 2rem;
  background-color:#1d2733;
  overflow: auto;
}

footer#custom-footer {
  height: 6rem;
  background-color: #373f46;
  font-size: 1rem;
  position: fixed;
  bottom: 0;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

div#custom-home {
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.btn-primary {
  background-color: #32ACED;
}

.btn-success {
  background-color: #4fc08d;
}

.btn-success:hover {
  background-color: #3da978;
}
  • 修改main.js , add style.css
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap";
import "./assets/css/style.css";

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");

03.修改路由Route

  • 增加src/views/authentication/Login.vue
<template>
    <h1>Login Route</h1>
</template>
  • 增加src/views/authentication/Register.vue
<template>
    <h1>Register Route</h1>
</template>
  • 增加src/views/tasks/TasksAll.vue
<template>
    <h1>Tasks All Route</h1>
</template>
  • 增加src/views/tasks/TasksCreate.vue
<template>
    <h1>Task Create Route</h1>
</template>
  • 增加src/views/tasks/TasksEdit.vue
<template>
    <h1>Task Edit Route</h1>
</template>
  • 修改src/router/index.js, 增加相关路由
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Login from "../views/authentication/Login.vue";
import Register from "../views/authentication/Register.vue";
import TasksAll from "../views/tasks/TasksAll.vue";
import TasksCreate from "../views/tasks/TasksCreate.vue";
import TasksEdit from "../views/tasks/TasksEdit.vue";

Vue.use(VueRouter);

export default new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home,
    },
    {
      path: "/tasks",
      name: "tasks-all",
      component: TasksAll,
    },
    {
      path: "/tasks/new",
      name: "tasks-create",
      component: TasksCreate,
    },
    {
      path: "/tasks/:id",
      name: "tasks-edit",
      component: TasksEdit,
    },
    {
      path: "/login",
      name: "login",
      component: Login,
    },
    {
      path: "/register",
      name: "register",
      component: Register,
    },
    {
      path: "*",
      redirect: "/",
    },
  ],
  linkActiveClass: "active", //对应高亮的样式类名
});
  • 修改Navbar.vue,替换<a> 成 <router-link>
<template>
  <header>
    <nav class="navbar navbar-expand-md navbar-dark fixed-top custom-bg-dark">
      <!-- <a class="navbar-brand" href="#">
        <img style="max-height: 25px" />
        Task Manager
      </a> -->
      <router-link to="/" class="navbar-brand">
        <img style="max-height:25px" src="../assets/logo.png"/>
        Task Manager
      </router-link>
      <button
        class="navbar-toggler"
        type="button"
        data-toggle="collapse"
        data-target="#navbarCollapse"
        aria-controls="navbarCollapse"
        aria-expanded="false"
        aria-label="Toggle navigation"
      >
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarCollapse">
        <!-- <ul class="navbar-nav mr-auto"> -->
        <ul class="navbar-nav ml-auto">
          <li class="nav-item">  <!-- exact准确匹配路径,否则Home一直高亮 -->
            <router-link to="/" class="nav-link" exact>Home</router-link>
          </li>
          <li class="nav-item">
            <router-link to="/tasks" class="nav-link" exact>Tasks</router-link>
          </li>
          <li class="nav-item">
            <router-link to="/register" class="nav-link" exact>Register</router-link>
          </li>
          <li class="nav-item">
             <router-link to="/login" class="nav-link" exact>Login</router-link>
          </li>

          <li class="nav-item">
            <a class="nav-link" href="#">Morpheuse</a>
          </li>
        </ul>
      </div>
    </nav>
  </header>
</template>

04.增加导航保护 Navigation Guards

  • 修改router.js, 增加 const isLoggedIn = true;  测试验证登录效果。
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Login from "../views/authentication/Login.vue";
import Register from "../views/authentication/Register.vue";
import TasksAll from "../views/tasks/TasksAll.vue";
import TasksCreate from "../views/tasks/TasksCreate.vue";
import TasksEdit from "../views/tasks/TasksEdit.vue";

Vue.use(VueRouter);

const isLoggedIn = true;

const routes = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes: [
    {
      path: "/",
      name: "home",
      component: Home,
    },
    {
      path: "/tasks",
      name: "tasks-all",
      component: TasksAll,
      beforeEnter: (to, from, next) => {
        if (isLoggedIn) {
          next();
        } else {
          next("/login");
        }
      },
    },
    {
      path: "/tasks/new",
      name: "tasks-create",
      component: TasksCreate,
      beforeEnter: (to, from, next) => {
        if (isLoggedIn) {
          next();
        } else {
          next("/login");
        }
      },
    },
    {
      path: "/tasks/:id",
      name: "tasks-edit",
      component: TasksEdit,
      beforeEnter: (to, from, next) => {
        if (isLoggedIn) {     
          next();
        } else {          //没有登录跳转到登录页面
          next("/login");
        }
      },
    },
    {
      path: "/register",
      name: "register",
      component: Register,
      beforeEnter: (to, from, next) => {
        if (!isLoggedIn) {    //没有登录跳转到注册页面
          next();
        } else {
          next("/");
        }
      },
    },
    {
      path: "/login",
      name: "login",
      component: Login,
      beforeEnter: (to, from, next) => {
        if (isLoggedIn) {
          next();
        } else {
          next("/");
        }
      },
    },
    {
      path: "*",
      redirect: "/",
    },
  ],
  linkActiveClass: "active",
});

/*
// router guards
routes.beforeEach((to, from, next) => {
  // Evaluate condition
  //    next('/home');
  //    next(false); //阻止跳转
  if (isLoggedIn) {
    next();
  } else {
    next("/login");
  }
});
*/
export default routes;

05.应用Vuex实现状态管理

  • 新增src/services/AuthService.js
import store from "../store";

export function isLoggedIn() {
  const token = localStorage.getItem("token");
  return token != null;
}

export function login() {
  const token = {
    username: "morpheus",
  };
  setToken(token);
}

function setToken(token) {
  localStorage.setItem("token", JSON.stringify(token));
  store.dispatch("authenticate");
}

export function getUsername() {
  return "morpheus";
}

export function getUserId() {
  return 1;
}
  • 修改src/store/index.js
import Vue from "vue";
import Vuex from "vuex";
import * as auth from "./services/AuthService";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    isLoggedIn: false,
    apiUrl: "http://localhost:3000/api",
    username: null,
    userId: null,
  },
  mutations: {
    authenticate(state) {
      state.isLoggedIn = auth.isLoggedIn();
      if (state.isLoggedIn) {
        state.username = auth.getUsername();
        state.userId = auth.getUserId();
      } else {
        state.userId = null;
        state.username = null;
      }
    },
  },
  actions: {
    authenticate(context) {
      context.commit("authenticate");
    },
  },
});
  • 增加views/authentication/Login.vue
<template>
<div>
  <h1>Login Route</h1>
  <form class="custom-form" v-on:submit="onSubmit">
    <div class="form-group">
      <label for="username">Username</label>
      <input type="text" class="form-control" id="username" placeholder="Username"/>
    </div>
    <div class="form-group">
      <label for="password">Password</label>
      <input type="password" class="form-control" id="password" placeholder="Password"/>
    </div>
    <div class="form-group">
      <button type="submit" class="btn btn-secondary">Submit</button>
    </div>
  </form>
</div>
</template>

<script>
  import * as auth from "../../services/AuthService";
  export default {
    name:"login",
    methods:{
      onSubmit:function(event){
        event.preventDefault();
        auth.login();
        this.$router.push({name:"home"});
      }
    }
  }
</script>
  • 修改src/asset/css/style.css, 增加
form.custom-form{
  max-width:40rem;
  display: flex;
  flex-direction: column;
  margin-left:auto;
  margin-right:auto;
}
  • 修改App.vue ,增加
beforeCreate: function(){
    this.$store.dispatch('authenticate')
}

 

 类似资料: