MongoDB
Express.js
Vue.js
Node.js
系统:Win10, 编辑器:VS code
准备环境: 安装vue-cli, mongodb
mongodb下载
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 }],
},
};
项目中引入bootstrap, jquery, popper
npm install bootstrap --save
npm install jquery popper.js --save
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap";
<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>
<template>
<footer id="custom-footer">
<span class="mr-4">© 2021 Morpheus</span>
<span class="mr-4">Private</span>
<span>Terms of Service</span>
</footer>
</template>
<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>
<template>
<div id="custom-home">
<Main />
</div>
</template>
<script>
import Main from "@/components/Main.vue";
export default {
name: "home",
components: {
Main,
},
};
</script>
<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>
* {
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;
}
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");
<template>
<h1>Login Route</h1>
</template>
<template>
<h1>Register Route</h1>
</template>
<template>
<h1>Tasks All Route</h1>
</template>
<template>
<h1>Task Create Route</h1>
</template>
<template>
<h1>Task Edit Route</h1>
</template>
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", //对应高亮的样式类名
});
<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>
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;
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;
}
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");
},
},
});
<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>
form.custom-form{
max-width:40rem;
display: flex;
flex-direction: column;
margin-left:auto;
margin-right:auto;
}
beforeCreate: function(){
this.$store.dispatch('authenticate')
}