springboot+thymeleaf工程中集成Vue3模板
概要
在springboot+thymeleaf的工程中,引入vue3并支持thymeleaf模板中嵌套vue模板,并且支持前端的组件式和模块式开发,适合前后端不分离的小型系统或网站,适合独立的开发者快速上手开发属于自己的项目。
技术栈
- java 17
- Springboot 3.0+
- thymeleaf
- vue3
- element-plus
搭建介绍
1、 创建一个Springboot3工程
main
├── java --核心java代码
├── controller -- thymeleaf 模板试图控制层
├── resources -- 资源目录
├── config -- 配置文件
├── static -- 静态文件包含css,js 和 vue模板
├── components -- 组件依赖包含vue.js、element-plus、less以及vue3-sfc-loader
├── css -- 样式文件,可以引入less
├── js -- 自定义JS,支持模块化语法
└── view -- vue 模板
└── templates -- thymeleaf 模板
2、创建前端组件
各组件的下载地址:
- unocss:https://unocss.jiangruyi.com
- vue3: https://cn.vuejs.org
- vue3-sfc-loader: https://gitcode.com/gh_mirrors/vu/vue3-sfc-loader
- element-plus: https://element-plus.org/zh-CN
- less: https://less.bootcss.com
按下图所示将前端组件加入到工程目录下:
3、编写一个vue模板作为系统主页面
这里使用了vue3,编写前可以先去了解一下Vue3的语法糖。vu3官网地址。取名为index.vue,放在工程目录resources/static/view下。虽然static下的文件可以被直接访问,但如果直接访问vue模板将缺少有关的加载组件,浏览器是无法成功加载,所以不用担心页面被直接访问从而泄露的问题。
<script>
import {defineAsyncComponent, onMounted, ref} from "vue"
import {ElMessage} from 'element-plus'
import {routes} from "../js/router.mjs";
import Btn1 from './btn1.vue'
export default {
name: 'index',
components: {
Btn1
},
setup() {
const message = ref('Hello Vue3')
const currentComponent = ref(defineAsyncComponent(() => {
return import(routes["test01"])
}))
onMounted(() => {
console.log('index mounted')
})
const changeView = (name) => {
currentComponent.value = defineAsyncComponent(() => {
return import(routes[name])
})
}
return {message, changeView, currentComponent}
}
}
</script>
<template>
<div class="main-page">
<el-card>
<template #header>
element-plus 展示 {{ message }}
</template>
<div class="mb-4">
<Btn1></Btn1>
</div>
<div class="p3">
<el-button type="primary" @click="changeView('test01')">
<el-icon class="mr1">
<Promotion/>
</el-icon>
test01
</el-button>
<el-button type="primary" @click="changeView('test02')">
<el-icon class="mr1">
<Promotion/>
</el-icon>
test02
</el-button>
</div>
<div class="p3">
<component :is="currentComponent"></component>
</div>
</el-card>
</div>
</template>
<style scoped lang="less">
.main-page {
padding: 20px;
height: 100%;
}
</style>
4、编写app.html
app.html 为前端项目的主入口,需要引入上述加入到工程目录的组件(注意:这里css和js的引入需要用到thymeleaf 的相关语法)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
<meta content="width=device-width, initial-scale=1.0, user-scalable=no" name="viewport"/>
<title>vue3-sfc-loader-demo</title>
<link th:href="@{/components/element-plus/css/index.css}" rel="stylesheet">
<link th:href="@{/css/loading.css}" rel="stylesheet">
</head>
<body>
<div id="app" un-cloak></div>
</body>
<script th:src="@{/components/unocss/runtime.js}"></script>
<script th:src="@{/components/vue3/index.js}"></script>
<script th:src="@{/components/vue3-sfc-loader/index.js}"></script>
<script th:src="@{/components/less/index-v4.js}"></script>
<script th:src="@{/components/element-plus/index.js}"></script>
<script th:src="@{/components/element-plus/icons.js}"></script>
<script th:src="@{/components/element-plus/zh_cn.js}"></script>
<script type="module">
const {createApp, defineAsyncComponent} = Vue;
const {loadModule} = window['vue3-sfc-loader'];
const options = {
moduleCache: {
vue: Vue,
"element-plus": ElementPlus,
less: less
},
async getFile(url) {
return await fetch(url).then(res => {
if (!res.ok) throw Object.assign(new Error(res.statusText + " " + url), {res});
return {
getContentData: asBinary => asBinary ? res.arrayBuffer() : res.text()
};
});
},
addStyle(textContent) {
const style = Object.assign(document.createElement("style"), {textContent});
const ref = document.head.getElementsByTagName("style")[0] || null;
document.head.insertBefore(style, ref);
}
};
const tmpPath = "view/index.vue"
const app = createApp({
components: {
'app-component': defineAsyncComponent(() => loadModule(tmpPath, options))
},
template: '<app-component></app-component>'
});
app.use(ElementPlus, {
locale: ElementPlusLocaleZhCn
});
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.mount("#app")
</script>
</html>
const tmpPath = “view/index.vue” 这块为上面创建的vue模板路径,需要按照实际工程编写的目录而定。
5.映射app.html访问地址
package top.ycdev.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ViewController {
@RequestMapping("/index")
public String index() {
return "app";
}
}
路径可以根据实际情况而定,这边使用了index,也可定义为“/”,这样就可以通过IP+端口直接访问。
6、编写application.yml
server:
port: 8900
spring:
application:
name: demo
thymeleaf:
cache: false ##关闭缓存
注意:开发中可以关闭模板缓存,这样修改了页面可以实时更新,不用重启项目。
7、访问页面
浏览器中访问 http://localhost:8900/index 即可。