todos) {
log.info(todos.toString());
return ResponseEntity.ok(stockService.saveAll(todos));
}
}
```
[TodoController.java](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/java/com/hellokoding/springboot/fullstack/todo/TodoController.java)
```java
package com.hellokoding.springboot.fullstack.todo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class TodoController {
@GetMapping("/")
public String list(){
return "todo";
}
}
```
### FreeMarker/HTML View Template
[todo.html](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/resources/templates/todo.html)
```html
Todo MVC using VueJS, Spring Boot, MySQL
```
### Static Files
[todo.js](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/resources/static/todo.js)
```js
// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~120 effective lines of JavaScript.
// localStorage persistence
var STORAGE_KEY = 'todos-vuejs-2.0'
var todoStorage = {
fetch: function () {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
},
sync: function (todos) {
axios
.post('/api/v1/todos', todos)
.then(response => (todoStorage.save(response.data)))
.catch(error => console.log(error))
},
save: function(todos){
console.log(todos)
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
}
}
// visibility filters
var filters = {
all: function (todos) {
return todos
},
active: function (todos) {
return todos.filter(function (todo) {
return !todo.completed
})
},
completed: function (todos) {
return todos.filter(function (todo) {
return todo.completed
})
}
}
// app Vue instance
var app = new Vue({
// app initial state
data () {
return {
todos: todoStorage.fetch(),
newTodo: '',
editedTodo: null,
visibility: 'all'
}
},
// watch todos change for localStorage persistence
watch: {
todos: {
handler: function (todos) {
todoStorage.sync(todos)
},
deep: true
}
},
// computed properties
// http://vuejs.org/guide/computed.html
computed: {
filteredTodos: function () {
return filters[this.visibility](this.todos)
},
remaining: function () {
return filters.active(this.todos).length
},
allDone: {
get: function () {
return this.remaining === 0
},
set: function (value) {
this.todos.forEach(function (todo) {
todo.completed = value
})
}
}
},
filters: {
pluralize: function (n) {
return n === 1 ? 'item' : 'items'
}
},
mounted() {
axios.get('/api/v1/todos')
.then(response => (todoStorage.save(response.data)))
.catch(error => console.log(error))
},
// methods that implement data logic.
// note there's no DOM manipulation here at all.
methods: {
addTodo: function () {
var value = this.newTodo && this.newTodo.trim()
if (!value) {
return
}
this.todos.push({
title: value,
completed: false
})
this.newTodo = ''
},
removeTodo: function (todo) {
this.todos.splice(this.todos.indexOf(todo), 1)
},
editTodo: function (todo) {
this.beforeEditCache = todo.title
this.editedTodo = todo
},
doneEdit: function (todo) {
if (!this.editedTodo) {
return
}
this.editedTodo = null
todo.title = todo.title.trim()
if (!todo.title) {
this.removeTodo(todo)
}
},
cancelEdit: function (todo) {
this.editedTodo = null
todo.title = this.beforeEditCache
},
removeCompleted: function () {
this.todos = filters.active(this.todos)
}
},
// a custom directive to wait for the DOM to be updated
// before focusing on the input field.
// http://vuejs.org/guide/custom-directive.html
directives: {
'todo-focus': function (el, binding) {
if (binding.value) {
el.focus()
}
}
}
})
// handle routing
function onHashChange () {
var visibility = window.location.hash.replace(/#\/?/, '')
if (filters[visibility]) {
app.visibility = visibility
} else {
window.location.hash = ''
app.visibility = 'all'
}
}
window.addEventListener('hashchange', onHashChange)
onHashChange()
// mount
app.$mount('.todoapp')
```
[todo.css](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/resources/static/todo.css)
```css
[v-cloak] { display: none; }
```
## 配置并运行
### Application 配置
[Application.java](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/java/com/hellokoding/springboot/fullstack/Application.java)
```java
package com.hellokoding.springboot.fullstack;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
```
[application.properties](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/src/main/resources/application.properties)
```properties
spring.datasource.url=jdbc:mysql://hk-mysql:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=hellokoding
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.freemarker.suffix=.html
```
`hk-mysql` 是指 `docker-compose.yml` 文件中定义的Docker Compose服务
`spring.jpa.hibernate.ddl-auto=create` 允许JPA / Hibernate为您自动创建数据库和表数据
```
在生产环境中,您可能希望使用spring.jpa.hibernate.ddl-auto = validate或spring.jpa.hibernate.ddl-auto = none(默认值)禁用DDL Auto功能。查看此示例作为方法之一
Flyway Example of Database Migration/Evolution with Spring Boot, JPA and Hibernate
```
[Flyway Example of Database Migration/Evolution with Spring Boot, JPA and Hibernate](https://hellokoding.com/database-migration-evolution-with-flyway-and-jpa-hibernate/)
### 使用Docker运行
准备用于Java / Spring Boot应用程序的 `Dockerfile` 和用于MySQL Server的 `docker-compose.yml`
[Dockerfile](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/Dockerfile)
```java
FROM maven:3.5-jdk-8
```
[docker-compose.yml](https://github.com/hellokoding/hellokoding-courses/blob/master/springboot-examples/springboot-todomvc-mysql-vuejs/docker-compose.yml)
```java
version: '3'
services:
hk-mysql:
container_name: hk-mysql
image: mysql/mysql-server:5.7
environment:
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: hellokoding
MYSQL_ROOT_HOST: '%'
ports:
- "3306:3306"
restart: always
todomvc-mysql-vuejs:
build: .
volumes:
- .:/app
- ~/.m2:/root/.m2
working_dir: /app
ports:
- 8080:8080
command: mvn clean spring-boot:run
depends_on:
- hk-mysql
```
在项目根目录下键入以下命令,需要确保本地Docker正在运行
```shell
docker-compose up
```
### 使用 Maven 和 本地MySQL 启动
将 [application.properties](https://hellokoding.com/build-a-full-stack-todomvc-web-app-and-rest-api-service-with-spring-boot-jpa-hibernate-mysql-freemarker-vuejs-and-docker/index.html#applicationconfigurations) 上的 `hk-mysql` 更新为 `localhost` ,并在项目根目录下键入以下命令
```
mvn clean spring-boot:run
```
### 测试
访问 `localhost:8080` 查看app
### Source code
https://github.com/hellokoding/hellokoding-courses/tree/master/springboot-examples/springboot-todomvc-mysql-vuejs
### References
- https://vuejs.org/v2/examples/todomvc.html
- https://vuejs.org/v2/cookbook/using-axios-to-consume-apis.html