目录
前言
本项目为简易的全栈项目,其中前端项目使用到了vue3、ts、element plus、axios等技术栈;后端项目使用到了springboot、jdbc、mysql、maven等技术
附上开源代码gitee地址:
前端代码:https://gitee.com/smileagain-lg/vue3-element-demo.git
后端代码:https://gitee.com/smileagain-lg/spring-boot-demo.git
一、前端项目
1、使用vue脚手架创建项目
1.1检查vue版本
输入命令vue -v(v要大写),版本需要在4.5.1之后
1.2 使用vue脚手架创建项目
1、使用命令vue create vue3-element-demo 创建vue项目。
2、通过上下键选择,选择 manually select features,按回车,手动进行配置。
3、通过上下键选择,通过空格键选中(使用babel,typescript,router、vuex、css pre-processors、linter / formatter),按回车
4、选择vue3按回车
5、是否使用 class 风格装饰器,此处输入【y】,然后回车确认;
6、babel 和 ts 是否一起使用,用于现代模式、自动检测的 polyfills 和转译 jsx,此处输入【y】,然后回车确认;
7、是否使用history路由模式,此处输入【n】,即使用默认的 hash 模式,然后回车确认;
8、选择 css 预处理器,按上下方向键来选择,笔者习惯了 less,然后回车确认;
9、选择第一个eslint with error prevention only(仅具有错误预防功能的eslint)
10、babel、eslint 等插件的配置是单独的文件进行配置,还是都在 package.json 文件里面?此处输选择 package.json 咯,并不想太多零散的配置文件,然后回车确认;
11、是否保存当前创建 vue3 项目的特性配置,下次再创建 vue 项目的时候,可以选择该特性,然后回车确认即可创建完成。这个可以不用保存,输入【n】,然后回车确认;
12、项目创建中;
项目创建成功:
运行项目
2、删除项目多余文件,修改配置项目
2.1、删除以下文件
2.1、在views下创建index文件
<template>
<div>我的首页</div>
</template>
<script lang="ts">
export default {
name: 'index'
}
</script>
<style scoped></style>
2.2、修改router/index.ts路由文件:
编译时会有报错:
vue eslint报错:component name “index“ should always be multi-word.eslintvue/multi-word-component-names
原因:除了根组件(app.vue)外,自定义组件名称应该由多单词组成,防止和html标签冲突。
解决方法,关闭eslint校验,重新编译就正常了
lintonsave: false
2.3、修改app.vue文件:
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="less">
html,
body,
#app {
width: 100%;
height: 100%;
}
</style>
运行效果:
2.4、初始化页面样式以及清除浮动
新建css/resset.css文件,并在index.html文件中引入,初始化样式
/**
* eric meyer's reset css v2.0 (http://meyerweb.com/eric/tools/css/reset/)
* http://cssreset.com
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video{
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
font-weight: normal;
vertical-align: baseline;
}
/* html5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section{
display: block;
}
ol, ul, li{
list-style: none;
}
blockquote, q{
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after{
content: '';
content: none;
}
table{
border-collapse: collapse;
border-spacing: 0;
}
/* custom */
a{
color: #7e8c8d;
text-decoration: none;
-webkit-backface-visibility: hidden;
}
::-webkit-scrollbar{
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track-piece{
background-color: rgba(0, 0, 0, 0.2);
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:vertical{
height: 5px;
background-color: rgba(125, 125, 125, 0.7);
-webkit-border-radius: 6px;
}
::-webkit-scrollbar-thumb:horizontal{
width: 5px;
background-color: rgba(125, 125, 125, 0.7);
-webkit-border-radius: 6px;
}
html, body{
width: 100%;
font-family: "arial", "microsoft yahei", "黑体", "宋体", "微软雅黑", sans-serif;
}
body{
line-height: 1;
-webkit-text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html{
overflow-y: scroll;
}
/*清除浮动*/
.clearfix:before,
.clearfix:after{
content: " ";
display: inline-block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix{
*zoom: 1;
}
/*隐藏*/
.dn{
display: none;
}
index.html中引用:
3、引入elementplus 组件库
3.1、导入依赖包
npm i element-plus -d
第一种:全局挂载elementplus,在main.js中添加
第一种:按需导入组件
vue.config.js中引入(按需导入方式),添加如下配置
const { defineconfig } = require('@vue/cli-service');
const autoimport = require('unplugin-auto-import/webpack');
const components = require('unplugin-vue-components/webpack');
const { elementplusresolver } = require('unplugin-vue-components/resolvers');
module.exports = defineconfig({
transpiledependencies: true,
//关闭eslint校验
lintonsave: false,
configurewebpack: {
plugins: [
autoimport({
resolvers: [elementplusresolver()],
}),
components({
resolvers: [elementplusresolver()],
}),
],
}
})
在页面中使用elementplus组件
效果:
3、创建登录页面
在view下新建login.vue文件
<template>
<div class="container" :class="{ 'sign-up-mode': signupmode }">
<!-- form表单容器 -->
<div class="form-container">
<div class="signin-signup">
<!-- 登录 -->
<el-form
ref="ruleformref"
:model="loginuser"
:rules="rules"
label-width="100px"
class="login-form sign-in-form"
>
<el-form-item label="邮箱" prop="email">
<el-input v-model="loginuser.email" placeholder="enter email..." />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input
v-model="loginuser.password"
type="password"
placeholder="enter password..."
/>
</el-form-item>
<el-form-item>
<el-button type="primary" class="submit-btn" @click="submitform(ruleformref)">提交</el-button>
</el-form-item>
<!-- 找回密码 -->
<el-form-item>
<p class="tiparea">忘记密码<a>立即找回</a></p>
</el-form-item>
</el-form>
</div>
</div>
<!-- 左右切换动画 -->
<div class="panels-container">
<div class="panel left-panel">
<div class="content">
<h3>row,row,row your boat</h3>
<p>gentlely down the stream</p>
<button @click="signupmode = !signupmode" class="btn transparent">
注册
</button>
</div>
</div>
<div class="panel right-panel">
<div class="content">
<h3>merrily,merrily,merrily,merrily,</h3>
<p>life is but a dream</p>
<button @click="signupmode = !signupmode" class="btn transparent">
登录
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, torefs, getcurrentinstance } from 'vue'
import type { forminstance, formrules } from 'element-plus'
// vue3语法糖
// 通过解构getcurrentinstance,获取this,这里的this就是ctx
// const { ctx } = getcurrentinstance()
const { proxy } = getcurrentinstance()
// 登录/注册模式
const signupmode = ref(false)
const ruleformref = ref<forminstance>()
// 登录表单
const loginuser = reactive({
email: '',
password: ''
})
// 校验规则
const rules = reactive({
email: [
{
required: true,
type: 'email',
message: 'email格式错误',
trigger: 'blur'
}
],
password: [
{ required: true, message: '密码不得为空', trigger: 'blur' },
{ min: 6, max: 30, message: '密码长度必须在6到30之间', trigger: 'blur' }
]
})
// 触发登录方法
const submitform = (formel: forminstance | undefined) => {
if(!formel!) return
formel.validate((valid) => {
if (valid) {
console.log('submit!')
const data = proxy.$http.getuserpassword(loginuser)
console.log('data', data)
} else {
console.log('error submit!')
return false
}
})
}
</script>
<style scoped>
.container {
position: relative;
width: 100%;
min-height: 100vh;
background-color: #fff;
overflow: hidden;
}
.form-container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.signin-signup {
position: relative;
top: 50%;
left: 75%;
transform: translate(-50%, -50%);
width: 44%;
transition: 1s 0.7s ease-in-out;
display: grid;
grid-template-columns: 1fr;
z-index: 5;
}
/* 左右切换动画 */
.social-text {
padding: 0.7rem 0;
font-size: 1rem;
}
.social-media {
display: flex;
justify-content: center;
}
.social-icon {
height: 46px;
width: 46px;
display: flex;
justify-content: center;
align-items: center;
margin: 0 0.45rem;
color: #333;
border-radius: 50%;
border: 1px solid #333;
text-decoration: none;
font-size: 1.1rem;
transition: 0.3s;
}
.social-icon:hover {
color: #4481eb;
border-color: #4481eb;
}
.btn {
width: 150px;
background-color: #5995fd;
border: none;
outline: none;
height: 49px;
border-radius: 49px;
color: #fff;
text-transform: uppercase;
font-weight: 600;
margin: 10px 0;
cursor: pointer;
transition: 0.5s;
}
.btn:hover {
background-color: #4d84e2;
}
.panels-container {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(2, 1fr);
}
.container:before {
content: '';
position: absolute;
height: 2000px;
width: 2000px;
top: -10%;
right: 48%;
transform: translatey(-50%);
background-image: linear-gradient(-45deg, #4481eb 0%, #04befe 100%);
transition: 1.8s ease-in-out;
border-radius: 50%;
z-index: 6;
}
.image {
width: 100%;
transition: transform 1.1s ease-in-out;
transition-delay: 0.4s;
}
.panel {
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: space-around;
text-align: center;
z-index: 6;
}
.left-panel {
pointer-events: all;
padding: 3rem 17% 2rem 12%;
}
.right-panel {
pointer-events: none;
padding: 3rem 12% 2rem 17%;
}
.panel .content {
color: #fff;
transition: transform 0.9s ease-in-out;
transition-delay: 0.6s;
}
.panel h3 {
font-weight: 600;
line-height: 1;
font-size: 1.5rem;
}
.panel p {
font-size: 0.95rem;
padding: 0.7rem 0;
}
.btn.transparent {
margin: 0;
background: none;
border: 2px solid #fff;
width: 130px;
height: 41px;
font-weight: 600;
font-size: 0.8rem;
}
.right-panel .image,
.right-panel .content {
transform: translatex(800px);
}
/* animation */
.container.sign-up-mode:before {
transform: translate(100%, -50%);
right: 52%;
}
.container.sign-up-mode .left-panel .image,
.container.sign-up-mode .left-panel .content {
transform: translatex(-800px);
}
.container.sign-up-mode .signin-signup {
left: 25%;
}
.container.sign-up-mode form.sign-up-form {
opacity: 1;
z-index: 2;
}
.container.sign-up-mode form.sign-in-form {
opacity: 0;
z-index: 1;
}
.container.sign-up-mode .right-panel .image,
.container.sign-up-mode .right-panel .content {
transform: translatex(0%);
}
.container.sign-up-mode .left-panel {
pointer-events: none;
}
.container.sign-up-mode .right-panel {
pointer-events: all;
}
@media (max-width: 870px) {
.container {
min-height: 800px;
height: 100vh;
}
.signin-signup {
width: 100%;
top: 95%;
transform: translate(-50%, -100%);
transition: 1s 0.8s ease-in-out;
}
.signin-signup,
.container.sign-up-mode .signin-signup {
left: 50%;
}
.panels-container {
grid-template-columns: 1fr;
grid-template-rows: 1fr 2fr 1fr;
}
.panel {
flex-direction: row;
justify-content: space-around;
align-items: center;
padding: 2.5rem 8%;
grid-column: 1 / 2;
}
.right-panel {
grid-row: 3 / 4;
}
.left-panel {
grid-row: 1 / 2;
}
.image {
width: 200px;
transition: transform 0.9s ease-in-out;
transition-delay: 0.6s;
}
.panel .content {
padding-right: 15%;
transition: transform 0.9s ease-in-out;
transition-delay: 0.8s;
}
.panel h3 {
font-size: 1.2rem;
}
.panel p {
font-size: 0.7rem;
padding: 0.5rem 0;
}
.btn.transparent {
width: 110px;
height: 35px;
font-size: 0.7rem;
}
.container:before {
width: 1500px;
height: 1500px;
transform: translatex(-50%);
left: 30%;
bottom: 68%;
right: initial;
top: initial;
transition: 2s ease-in-out;
}
.container.sign-up-mode:before {
transform: translate(-50%, 100%);
bottom: 32%;
right: initial;
}
.container.sign-up-mode .left-panel .image,
.container.sign-up-mode .left-panel .content {
transform: translatey(-300px);
}
.container.sign-up-mode .right-panel .image,
.container.sign-up-mode .right-panel .content {
transform: translatey(0px);
}
.right-panel .image,
.right-panel .content {
transform: translatey(300px);
}
.container.sign-up-mode .signin-signup {
top: 5%;
transform: translate(-50%, 0);
}
}
@media (max-width: 570px) {
form {
padding: 0 1.5rem;
}
.image {
display: none;
}
.panel .content {
padding: 0.5rem 1rem;
}
.container {
padding: 1.5rem;
}
.container:before {
bottom: 72%;
left: 50%;
}
.container.sign-up-mode:before {
bottom: 28%;
left: 50%;
}
}
/* 控制login & register显示 */
form {
padding: 0rem 5rem;
transition: all 0.2s 0.7s;
overflow: hidden;
}
form.sign-in-form {
z-index: 2;
}
form.sign-up-form {
opacity: 0;
z-index: 1;
}
/* register */
.loginform,
.registerform {
margin-top: 20px;
background-color: #fff;
padding: 20px 40px 20px 20px;
border-radius: 5px;
box-shadow: 0px 5px 10px #cccc;
}
.submit-btn {
width: 100%;
}
.tiparea {
text-align: right;
font-size: 12px;
color: #333;
width: 100%;
}
.tiparea a {
color: #409eff;
}
</style>
4、封装并使用 axios
4.1、安装axios
npm i axios
4.2、安装nprogress顶部进度条
npm i --save-dev @types/nprogress
4.3、封装请求拦截
在 src 目录新建 utils 文件夹,再新建 requestutil.ts 文件,写上以下代码
import axios from 'axios'
import nprogress from 'nprogress'
import 'nprogress/nprogress.css'
import { elmessage } from 'element-plus'
const http = axios.create({
baseurl: 'http://localhost:9000',
timeout: 300 * 1000, // 请求超时时间设置为300秒
})
const network_error = '网络错误,请联系开发人员'
/**
* 请求拦截器
*/
http.interceptors.request.use((req) => {
console.log('请求拦截器 =>', req)
nprogress.start()
return req;
}, (error) => {
nprogress.done()
return promise.reject(error);
});
/**
* 响应拦截器
*/
http.interceptors.response.use(function (res) {
console.log('响应拦截器 =>', res)
nprogress.done()
if (res.status == 200) {
return res.data
} else {
elmessage.error((network_error))
return promise.reject(network_error)
}
});
export default http
4.4、前端设置跨域
在vue.config.js中配置如下代码:
const { defineconfig } = require('@vue/cli-service');
// const autoimport = require('unplugin-auto-import/webpack');
// const components = require('unplugin-vue-components/webpack');
// const { elementplusresolver } = require('unplugin-vue-components/resolvers');
module.exports = defineconfig({
transpiledependencies: true,
//关闭eslint校验
lintonsave: false,
// elementplus按需导入方式
// configurewebpack: {
// plugins: [
// autoimport({
// resolvers: [elementplusresolver()],
// }),
// components({
// resolvers: [elementplusresolver()],
// }),
// ],
// }
devserver: {
open: true,
host: 'localhost',
port: 8080,
https: false,
// 设置跨域
proxy: {
'/api': {
target: 'http://localhost:9000',
ws: true,
changeorigin: true,
pathrewrite: {
'^api': ''
}
}
}
}
})
4.5、配置接口api
在src目录下创建api文件夹,里面创建index.ts
import http from '@/utils/requestutils'
export default {
/**
* 根据用户邮箱、密码查询用户信息
*/
getuserpassword(data: any) {
return http.post(
'/api/getuserpassword',
data,
{
headers: {
'content-type': 'application/json'
},
}
)
},
/**
* 保存用户信息
*/
saveuser(data: any) {
return http.post(
'/api/saveuser',
data,
{
headers: {
'content-type': 'application/json'
},
}
)
},
}
4.6、将http请求全局封装
在 main.ts 文件引入http请求工具并配置为全局方法
import { createapp } from 'vue'
import app from './app.vue'
import router from './router'
import store from './store'
import elementplus from 'element-plus'
import 'element-plus/dist/index.css'
import axios from 'axios'
import apiserve from '@/api'
const app = createapp(app)
app.use(store)
app.use(router)
app.use(elementplus)
app.mount('#app')
app.config.globalproperties.$http = apiserve
app.config.globalproperties.$axios = axios
二、后端项目
1、检查jdk和maven的安装版本
在cmd输入 java -version
和mvn -v检查对应的安装情况
2、创建springboot项目
通过idea的spring initializr创建工程,不选择maven而是选择spring initializr快捷创建。然后去勾选相关依赖。
3、创建springboot项目成功
项目创建成功
4、配置maven和maven库
配置本地maven库
5、加载maven库
6、创建application.yml
在resources下面新建application.yml,并配置数据库名,密码,以及端口,端口尽量不要使用8080,避免和前端端口相同了
注释掉另外一个配置
# mysql
spring:
datasource:
#mysql配置
driverclassname: com.mysql.cj.jdbc.driver
url: jdbc:mysql://localhost:3306/easyproject?useunicode=true&characterencoding=utf-8&usessl=false&servertimezone=utc
#数据库名和密码
username: root
password: 920724
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.demo.model
server:
port: 9000
7、运行项目
项目运行成功,端口9000
8、新建webconfig文件处理跨域
创建utils文件夹,在utils文件夹下创建webconfig,并添加以下配置
package com.springboot.userlogin.springbootdemo.utils;
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.config.annotation.corsregistry;
import org.springframework.web.servlet.config.annotation.webmvcconfigureradapter;
// 使用注解说明是全局配置类
@configuration
public class webconfig extends webmvcconfigureradapter { // 继承跨域请求的类
@override
public void addcorsmappings(corsregistry registry) { // 跨域处理的方法
registry.addmapping("/**") // 任意访问都允许跨域
.allowedorigins("http://localhost:8080", "null") // 跨域来源
.allowedmethods("post", "get", "put", "options", "delete") // 跨域请求类型
.maxage(3600) // 超时时间
.allowcredentials(true); // 允许携带信息
}
}
alt+insert快捷键可以弹出
9、使用idea连接mysql
mysql安装配置方法可以参照我写的另外一个文档:
输入mysql用户名和密码:
10、在pom文件添加lombok依赖
目的:为了使用@data注解
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>1.18.24</version>
</dependency>
11、创建bean文件夹,用于放置实体对象
在项目中创建bean文件夹,新建user实体类,使用data注解,创建构造方法和get\set方法
package com.springboot.userlogin.springbootdemo.bean;
import lombok.data;
@data
public class user {
private int id;
private string username;
private string password;
private string email;
private string role;
private boolean state;
// public user() {
// }
// public user(string username, string password, string email, string role, boolean state) {
// this.username = username;
// this.password = password;
// this.email = email;
// this.role = role;
// this.state = state;
// }
//
// public int getid() {
// return id;
// }
//
// public string getusername() {
// return username;
// }
//
// public string getpassword() {
// return password;
// }
//
// public string getemail() {
// return email;
// }
//
// public string getrole() {
// return role;
// }
//
// public boolean getstate() {
// return state;
// }
//
// public void setid(int id) {
// this.id = id;
// }
//
// public void setusername(string username) {
// this.username = username;
// }
//
// public void setpassword(string password) {
// this.password = password;
// }
//
// public void setemail(string email) {
// this.email = email;
// }
//
// public void setrole(string role) {
// this.role = role;
// }
//
// public void setstate(boolean state) {
// this.state = state;
// }
}
12、查看构造成功成功的实体对象
快捷键 alt + 7
13、创建controller接口
import org.springframework.web.bind.annotation.postmapping;
import org.springframework.web.bind.annotation.requestbody;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
public class logincontroller {
@autowired
userdao userdao;
@postmapping("/api/getuserpassword") // @requestmapping注解创建接口
public string userlogin(@requestbody user user) { // @requestbody注解方便找到user实体
system.out.println("user : " + user);
string str = "error";
int count = userdao.getuserbymassage(user.getemail(), user.getpassword());
if (count > 0) {
str = "ok";
}
return str;
}
}
14、创建dao接口
package com.springboot.userlogin.springbootdemo.dao;
import org.apache.ibatis.annotations.mapper;
import org.apache.ibatis.annotations.param;
import org.springframework.stereotype.repository;
@repository
@mapper
public interface userdao {
int getuserbymassage(@param("email") string email, @param("password") string password);
}
15、创建mapper映射文件
在resources下面创建mapper文件夹,用于存放数据库的映射文件
在mapper文件夹下新建usermapper.xml
<?xml version="1.0" encoding="utf-8" ?>
<!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--对应dao层接口文件的目录-->
<mapper namespace="com.springboot.userlogin.springbootdemo.dao.userdao">
<!-- id值为userdao接口方法名; -->
<select id="getuserbymassage" resulttype="java.lang.integer">
select count(id) from easyuser
where email=#{email} and password=#{password}
</select>
</mapper>
发表评论