Connecting Vue.js to Backend Server
01 引入bootstrap-vue
npm install vue bootstrap bootstrap-vue
- 修改src/main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "bootstrap/dist/css/bootstrap.css";
import "./assets/css/style.css";
import "bootstrap";
import BootstrapVue from "bootstrap-vue";
import moment from "moment";
Vue.use(BootstrapVue);
// Vue.config.productionTip = false;
Vue.config.productionTip = process.env.NODE_ENV === "production";
Vue.filter("date", (value) => {
if (!value) {
return "";
}
return moment(value).format("YYYY-MM-DD");
});
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
02 创建中间层Service
- 增加src/services/TaskService.js
import http from "./HttpService";
export function getAllTasks() {
return http().get("/task");
}
export function getTaskById(id) {
return http().get(`/task/${id}`);
}
export function createTask(task) {
return http().post("/task", task);
}
export function deleteTask(id) {
return http().delete(`/task/${id}`);
}
export function updateTask(task) {
return http().put("/task", task);
}
03 创建View层页面
- 增加/src/views/tasks/TasksCreate.vue
<template>
<div>
<h1>Create Task</h1>
<form class="custom-form" v-on:submit.prevent="onSubmit">
<div class="form-group">
<label for="title">Title</label>
<input
v-model="task.title"
type="text"
class="form-control"
id="title"
name="title"
placeholder="Title"
/>
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea
placeholder="Body"
class="form-control"
v-model="task.body"
name="body"
id="body"
cols="30"
rows="10"
></textarea>
</div>
<div class="form-group">
<label for="due-date">Due Date</label>
<input
name="due-date"
v-model="task.dueDate"
type="date"
class="form-control"
id="due-date"
placeholder="Due Date"
/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-secondary">Create</button>
</div>
</form>
</div>
</template>
<script>
import * as taskService from "../../services/TaskService";
export default {
name: "tasks-create",
data: function () {
return {
task: {
title: "",
body: "",
dueDate: "",
},
};
},
methods: {
onSubmit: async function () {
const request = {
task: this.task,
};
await taskService.createTask(request);
this.$router.push({ name: "tasks-all" });
},
},
};
</script>
- 增加/src/views/tasks/TasksAll.vue
<template>
<div class="d-flex flex-column">
<h1>Tasks</h1>
<div class="mb-4">
<router-link to="/tasks/new" class="btn btn-success ml-2" exact
>Create Task</router-link
>
</div>
<div
v-if="tasks && tasks.length > 0"
class="d-flex flex-wrap justify-content-start"
>
<div
v-for="task in tasks"
v-bind:key="task._id"
class="card mb-2 ml-2 text-white bg-dark"
style="width: 18rem"
>
<div class="card-body">
<div class="d-flex justify-content-between">
<h5 class="card-title">{{ task.title }}</h5>
<span
v-bind:class="{
late: taskIsLate(task.dueDate) && !task.completed,
}"
class="small"
>{{ task.dueDate | date }}</span
>
</div>
<h6 class="card-subtitle mb-2 text-muted">
Created by {{ task.author.username }}
</h6>
<p class="card-text">{{ task.body }}</p>
<div
v-if="task.author._id === $store.state.userId"
class="form-group form-check"
>
<input
type="checkbox"
class="form-check-input"
:disabled="task.completed"
v-model="task.completed"
v-on:click="markAsCompleted(task)"
/>
<label for="form-check-label">Completed</label>
</div>
<div
v-if="task.author._id === $store.state.userId"
class="d-flex justify-content-between"
>
<router-link
type="button"
tag="button"
class="card-link btn btn-primary"
:to="{ name: 'tasks-edit', params: { id: task._id } }"
exact
>Edit</router-link
>
<a
v-on:click.prevent="currentTaskId = task._id"
class="card-link btn btn-danger"
href="#"
v-b-modal.modal1
>Delete</a
>
</div>
</div>
</div>
<div>
<b-modal id="modal1" ref="modal" centered title="Delete Confirmation">
<p class="my-4">Are you sure you would like to delete this task?</p>
<div slot="modal-footer" class="w-100 text-right">
<b-btn size="md" class="mr-1" variant="danger" @click="deleteTask"
>Delete</b-btn
>
<b-btn size="md" variant="secondary" @click="cancelModal"
>Cancel</b-btn
>
</div>
<!-- <template #modal-footer>
<div class="w-100 text-right">
<p class="float-left">Modal Footer Content</p>
<b-button
variant="danger"
size="md"
class="mr-1"
@click="deleteTask"
>
Delete
</b-button>
<b-button
variant="secondary"
size="md"
class="mr-1"
@click="cancelModal"
>
Cancel
</b-button>
</div>
</template> -->
</b-modal>
</div>
</div>
<div v-if="tasks && tasks.length === 0" class="ml-2">
<div class="alert alert-info">No tasks found.</div>
</div>
</div>
</template>
<script>
import * as taskService from "../../services/TaskService";
import moment from "moment";
export default {
name: "tasks-all",
data: function () {
return {
tasks: null,
currentTaskId: null,
};
},
beforeRouteEnter(to, from, next) {
taskService.getAllTasks().then((res) => {
next((vm) => {
vm.tasks = res.data.tasks;
});
});
},
methods: {
taskIsLate: function (date) {
return moment(date).isBefore();
},
cancelModal: function () {
this.$refs.modal.hide();
this.currentTaskId = null;
},
deleteTask: async function () {
this.$refs.modal.hide();
await taskService.deleteTask(this.currentTaskId);
const index = this.tasks.findIndex((task) => {
task._id === this.currentTaskId;
});
this.tasks.splice(index, 1);
this.currentTaskId = null;
},
markAsCompleted: function (task) {
task.completed = true;
const request = {
task: task,
};
taskService.updateTask(request);
},
},
};
</script>
参考:bootstrap 的 Flex, spacing, card, Modal, Botton
- 修改style.css,增加
.modal-title,
.modal-body p,
.modal-footer p{
color:#000 !important;
}
.late{
color:#dc3545;
}
- 修改TasksEdit.vue
<template>
<div>
<h1>Edit Task</h1>
<form class="custom-form" v-on:submit.prevent="onSubmit">
<div class="form-group">
<label for="title">Title</label>
<input
v-model="task.title"
type="text"
class="form-control"
id="title"
name="title"
placeholder="Title"
/>
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea
placeholder="Body"
class="form-control"
v-model="task.body"
name="body"
id="body"
cols="30"
rows="10"
></textarea>
</div>
<div class="form-group">
<label for="due-date">Due Date</label>
<input
name="due-date"
v-model="task.dueDate"
type="date"
class="form-control"
id="due-date"
placeholder="Due Date"
/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-secondary">Save Changes</button>
</div>
</form>
</div>
</template>
<script>
import * as taskService from "../../services/TaskService";
import moment from "moment";
export default {
name: "tasks-edit",
data: function () {
return {
task: {
title: "",
body: "",
dueDate: "",
},
};
},
beforeRouteEnter(to, from, next) {
taskService.getTaskById(to.params.id).then((response) => {
if (!response) {
next("/tasks");
} else {
next((vm) => {
const task = response.data.task;
task.dueDate = moment(task.dueDate).format("YYYY-MM-DD");
vm.task = task;
});
}
});
},
methods: {
onSubmit: async function () {
const request = {
task: this.task,
};
await taskService.updateTask(request);
this.$router.push({ name: "tasks-all" });
},
},
};
</script>
04 package.json
{
"name": "mevn-stack",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"vuebuild": "vue-cli-service build",
"dev": "babel dev-server --out-dir prod-server --watch",
"build": "set NODE_ENV=production && babel dev-server --out-dir prod-server && vue-cli-service build",
"concurrently": "concurrently \"set NODE_ENV=development\" \"babel dev-server --out-dir prod-server --watch\" \"nodemon prod-server/index.js\" \"npm run serve\" ",
"lint": "vue-cli-service lint",
"lintfix": "eslint ./dev-server --fix && vue-cli-service lint"
},
"dependencies": {
"@babel/polyfill": "^7.12.1",
"axios": "^0.21.1",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.19.0",
"bootstrap": "^4.6.0",
"bootstrap-vue": "^2.21.2",
"core-js": "^3.6.5",
"express": "^4.17.1",
"jquery": "^3.6.0",
"jsonwebtoken": "^8.5.1",
"moment": "^2.29.1",
"mongoose": "^5.12.5",
"popper.js": "^1.16.1",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.13.16",
"@babel/preset-env": "^7.13.15",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"concurrently": "^6.0.2",
"cors": "^2.8.5",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"morgan": "^1.10.0",
"prettier": "^2.2.1",
"vue-template-compiler": "^2.6.11"
}
}
05 项目结构