零:最终实现图片展览:
一:前端项目搭建;
1:使用cmd下载vue:
2:npm install -g @vue/cli
(如果安装过就不用啦)
3:vue create springboot-vue-demo(自己的名字):
4:
5:将目标移动到第一个选项然后按回车到达下面这个界面,选择一个vue版本,我们这里选择vue3x
6:下一个界面依次按下: y in package.json y 这三个,第三个是是否保存当前配置可以按N
7:
进入:
退出打开idea去开启项目编写
二:前端框架搭建,首先看一下最终的实现效果:
1:首先我们发现这个后台管理系统有三个部分,内容,顶部栏,左边导航栏,所以我们要将顶部和左侧两个不变的地方写在components中:
Header.vue:
<template>
<div style="height: 50px; line-height: 50px; border-bottom: 1px solid #ccc; display: flex; background-color:#545c64">
<div style="width: 200px; padding-left: 30px; font-weight: bold; color: white">后台管理</div>
<div style="flex: 1"></div>
<div style="width: 100px">
<el-dropdown style="line-height: 3.5 ;color: white">
<span class="el-dropdown-link" >
张三 <el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</el-dropdown-item>
<el-dropdown-item>退出系统</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script >
import { ArrowDown } from '@element-plus/icons-vue';
export default {
components:{ArrowDown},
name: "Header"
}
</script>
插入一下:新版element引入图标使用的script:import { ArrowDown } from '@element-plus/icons-vue';
,项目中加入element是需要手动npm导入的,这里可以通过看官网介绍
Aside.vue:
<template>
<div>
<el-menu
style="width: 200px; min-height: calc(100vh - 50px)"
:uniqueOpened="true"
default-active="2"
class="el-menu-vertical-demo"
background-color="#545c64"
text-color="#fff"
active-text-color="#ffd04b"
>
<el-sub-menu index="1">
<template #title>
<i class="el-icon-location"></i>
<span>导航一</span>
</template>
<el-menu-item index="1-4">
<template #title>选项1-1</template>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="2">
<el-icon><document /></el-icon>
<template #title>导航二</template>
</el-menu-item>
<el-menu-item index="3">
<el-icon><document /></el-icon>
<template #title>导航三</template>
</el-menu-item>
<el-menu-item index="4">
<el-icon><setting /></el-icon>
<template #title>导航四</template>
</el-menu-item>
</el-menu>
</div>
</template>
<script>
import {
Location,
Document,
Menu as IconMenu,
Setting,
} from '@element-plus/icons-vue'
export default {
components:{
Location,
Document,
IconMenu,
Setting
},
name: "Aside"
}
</script>
<style scoped>
</style>
Main.js:
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/es/locale/lang/zh-cn'
import router from './router'
import store from './store'
import '@/assets/css/global.css'
createApp(App).use(store).use(router).use(ElementPlus,{locale}).mount('#app')
App.vue:
<template>
<div >
<!-- 头部-->
<Header/>
<!-- 主体-->
<div style="display: flex">
<!-- 侧边栏-->
<Aside />
<!-- 内容区域-->
<router-view style="flex: 1"/>
</div>
</div>
<!-- <router-view/>-->
</template>
<style>
</style>
<script>
import Header from "@/components/Header";
import Aside from "@/components/Aside";
export default {
name:"Layout",
components:{
Header,
Aside
}
}
</script>
Home.vue:
<template>
<div style="padding: 10px" >
<!-- 功能区域-->
<div style="margin: 10px 0px">
<el-button type="primary" @click="add">新增</el-button>
<el-button type="primary">导入</el-button>
<el-button type="primary">导出</el-button>
</div>
<!-- 搜索区域-->
<div style="margin: 10px 0px">
<el-input v-model="search" placeholder="请输入关键字" style="width: 20%"></el-input>
<el-button type="primary" style="margin-left: 5px">查询</el-button>
</div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column
prop="id"
label="ID" sortable="sortable" >
</el-table-column>
<el-table-column
prop="username"
label="用户名" >
</el-table-column>
<el-table-column
prop="nickName"
label="昵称">
</el-table-column>
<el-table-column
prop="age"
label="年龄">
</el-table-column>
<el-table-column
prop="sex"
label="性别">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text" size="small">编辑</el-button>
<el-popconfirm title="确定删除吗?">
<template #reference>
<el-button type="text">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="margin: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
<el-dialog title="提示" v-model="dialogVisible" width="30%">
<el-form :model="form" label-width="120px">
<el-form-item label="用户名">
<el-input v-model="form.username" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickName" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio v-model="form.sex" label="男">男</el-radio>
<el-radio v-model="form.sex" label="女">女</el-radio>
</el-form-item>
<el-form-item label="地址">
<el-input type="textarea" v-model="form.address" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save" >确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
<script>
import request from "@/util/request";
export default {
name: 'Home',
components: {
},
data(){
return{
form:{},
dialogVisible:false,
search:"",
currentPage:1,
total:10,
tableData:[
]
}
},
methods:{
// 新增用户
add(){
this.dialogVisible = true
// 先清空表单域
this.form = {}
},
// 保存用户传送给后台,使用常见的api:axios
save(){
request.post("/user",this.form).then(res => {
console.log(res)
})
},
handleEdit(){
},
handleSizeChange(){
},
handleCurrentChange(){
}
}
}
</script>
全局css:
index.js:(router)
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
store:index.js
import { createStore } from 'vuex'
export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})
三:后端基本结构:使用springboot+mybatis-plus
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
实体类:user:
package com.example.demo.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@TableName("user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String nickName;
private Integer age;
private String sex;
private String address;
private String avatar;
}
Mapper接口:
package com.example.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
返回结果封装类:Result:
package com.example.demo.common;
/**
* 返回结果的包装类
* @param <T>
*/
public class Result<T> {
private String code;
private String msg;
private T data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Result() {
}
public Result(T data) {
this.data = data;
}
public static Result success() {
Result result = new Result<>();
result.setCode("0");
result.setMsg("成功");
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("0");
result.setMsg("成功");
return result;
}
public static Result error(String code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
Mybatis-plus插件:
package com.example.demo.common;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mybatis-plus 分页插件
*/
@Configuration
@MapperScan("com.example.demo.mapper")
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
控制层:controller:
package com.example.demo.controller;
import com.example.demo.common.Result;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @RestController:返回数据
* @Controller:渲染界面
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserMapper userMapper;
/**
* 新增 @RequestBody:前台传过来的json转换为java对象
* @return
*/
@PostMapping
public Result<?> save(@RequestBody User user){
userMapper.insert(user);
return Result.success();
}
}
三:数据交互:
这里首先说前端我们怎样将home.vue里面的数据传输给后端?答案是使用axios!
配置axios:
import axios from 'axios'
const request = axios.create({
// 注意!! 这里是全局统一加上了 '/api' 前缀,也就是说所有接口都会加上'/api'前缀在,页面里面写接口的时候就不要加 '/api'了,否则会出现2个'/api',类似 '/api/api/user'这样的报错,切记!!!
baseURL: '/api',
timeout: 5000
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
// config.headers['token'] = user.token; // 设置请求头
return config
}, error => {
return Promise.reject(error)
});
// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 如果是返回的文件
if (response.config.responseType === 'blob') {
return res
}
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
return res;
},
error => {
console.log('err' + error) // for debug
return Promise.reject(error)
}
)
export default request
配好这个以后在数据交互的home.vue界面就可以使用了:
浏览器还有一个跨域访问的问题:配置vue.config.js:
// 跨域配置
module.exports = {
devServer: { //记住,别写错了devServer//设置本地默认端口 选填
port: 9876,
proxy: { //设置代理,必须填
'/api': { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定
target: 'http://localhost:7099', //代理的目标地址
changeOrigin: true, //是否设置同源,输入是的
pathRewrite: { //路径重写
'^/api': '' //选择忽略拦截器里面的内容
}
}
}
}
}
现在就可以测试了:
最新的home.vue:
<template>
<div style="padding: 10px" >
<!-- 功能区域-->
<div style="margin: 10px 0px">
<el-button type="primary" @click="add">新增</el-button>
<el-button type="primary">导入</el-button>
<el-button type="primary">导出</el-button>
</div>
<!-- 搜索区域-->
<div style="margin: 10px 0px">
<el-input v-model="search" placeholder="请输入关键字" style="width: 20%"></el-input>
<el-button type="primary" style="margin-left: 5px">查询</el-button>
</div>
<el-table :data="tableData" border style="width: 100%">
<el-table-column
prop="id"
label="ID" sortable="sortable" >
</el-table-column>
<el-table-column
prop="username"
label="用户名" >
</el-table-column>
<el-table-column
prop="nickName"
label="昵称">
</el-table-column>
<el-table-column
prop="age"
label="年龄">
</el-table-column>
<el-table-column
prop="sex"
label="性别">
</el-table-column>
<el-table-column
prop="address"
label="地址">
</el-table-column>
<el-table-column fixed="right" label="操作" width="100">
<template #default="scope">
<el-button @click="handleEdit(scope.row)" type="text" size="small">编辑</el-button>
<el-popconfirm title="确定删除吗?">
<template #reference>
<el-button type="text">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<div style="margin: 10px 0">
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-sizes="[5, 10, 20]"
:page-size="10"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
>
</el-pagination>
<el-dialog title="提示" v-model="dialogVisible" width="30%">
<el-form :model="form" label-width="120px">
<el-form-item label="用户名">
<el-input v-model="form.username" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="昵称">
<el-input v-model="form.nickName" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="年龄">
<el-input v-model="form.age" style="width: 80%"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio v-model="form.sex" label="男">男</el-radio>
<el-radio v-model="form.sex" label="女">女</el-radio>
</el-form-item>
<el-form-item label="地址">
<el-input type="textarea" v-model="form.address" style="width: 80%"></el-input>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="save" >确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</div>
</template>
<script>
import request from "@/util/request";
export default {
name: 'Home',
components: {
},
data(){
return{
form:{},
dialogVisible:false,
search:"",
currentPage:1,
total:10,
tableData:[
]
}
},
methods:{
// 新增用户
add(){
this.dialogVisible = true
// 先清空表单域
this.form = {}
},
// 保存用户传送给后台,使用常见的api:axios
save(){
request.post("/user",this.form).then(res => {
console.log(res)
})
this.dialogVisible=false
},
handleEdit(){
},
handleSizeChange(){
},
handleCurrentChange(){
}
}
}
</script>