文章目录
VBlog项目代码理解之前端
资源
项目地址
前后端交互理解
后端代码理解
推荐:整个项目几乎是只用到了SpringBoot、Vue、Mybatis、ElementUI,没有用到Redis、RabbitMQ等内容,很适合刚学完SpringBoot和Vue的同学练手,感谢作者!帮作者打个广告吧~
PS:这是本人第一个学习的项目,难免会有错误的地方,哪里有问题烦请指正,感谢!
配置问题
解决前后端交互、跨域、页面跳转等问题
config/index:通过代理解决跨域问题
- 核心内容就是
proxyTable
,这块根据SpringBoot
的配置来配置,具体内容都在注释里了。 - config/index.js参数详解
'use strict'
// Template version: 1.2.7
// see http://vuejs-templates.github.io/webpack for documentation.
// 这个不知道有啥用
const path = require('path')
// 话说这边并没有用env: require()来指定环境啊
module.exports = {
// 管开发的时候,既有前后端交互的跨域配置,也有在前端玩的配置
dev: {
// Paths
// 静态资源子目录和公开地址
assetsSubDirectory: 'static',
assetsPublicPath: '/',
// 这边是设置代理,解决跨域问题,开发的时候才用
proxyTable: {
// 路径啥也不加
'/': {
// 改写,相当于把http://localhost:8080改成下面的内容+/
target: 'http://localhost:8081',
changeOrigin: true, // 指示是否跨域
pathRewrite: {
'^/': '' // 啥都没有,就等价于http://localhost:8081/
}
}
},
// 这边是前端自己玩
// Various Dev Server settings
host: 'localhost', // can be overwritten by process.env.HOST
// dev-server的端口号
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'eval-source-map',
// debug工具出问题的时候可以试试改为false,可能会有用
cacheBusting: true,
// 是否生成css、map文件,说可能存在问题,但是没啥必要用这个,出问题了可以控制台
// 所以这里于默认不同,设为false
cssSourceMap: false,
},
// 配置build、打包问题
// 这一套很固定,根本不用动,创建完了就是这样的
build: {
// 这块好像都是说运行npm run build之后,生成的文件应在的位置
// build之后生成的index位置?
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
// 静态资源的根目录
assetsRoot: path.resolve(__dirname, '../dist'),
// 静态资源子目录
assetsSubDirectory: 'static',
// 静态资源的公开路径,也就是真正的引用路径(引用路径指使用时?)
assetsPublicPath: '/',
/**
* Source Maps
*/
// 是否生成生产环境的sourcemap,sourcemap用来对编译后的文件进行debug,方法是映射回编译前的文件
// 编译后的代码人是看不懂的
productionSourceMap: true,
// 这应当是一种映射工具
devtool: '#source-map',
// 是否在生产环境中压缩代码,如果要压缩必须安装compression-webpack-plugin
productionGzip: false,
// 指定要压缩的文件类型
productionGzipExtensions: ['js', 'css'],
// 开启编译完成后的报告,只有运行了npm run build --report才有吧
bundleAnalyzerReport: process.env.npm_config_report
}
}
router:页面跳转控制
- 所有的跳转都通过路由控制,在
router/index.js
文件中配置,path
可以通过children
设置子路径,如果有多个子模块,就会变成可选模式。 - 同时每个路径都绑定了
component
,子路径的component
会在父路径的组件的<router-view>
位置显示,所以路径的跳转本质上就是组件的不同组合。 - 还可以配置一些信息,如组件名字、是否隐藏、保持激活等内容。
import Vue from 'vue'
import Router from 'vue-router'
import Login from '@/components/Login'
import Home from '@/components/Home'
import ArticleList from '@/components/ArticleList'
import CateMana from '@/components/CateMana'
import DataCharts from '@/components/DataCharts'
import PostArticle from '@/components/PostArticle'
import UserMana from '@/components/UserMana'
import BlogDetail from '@/components/BlogDetail'
import Doex from '@/components/Doex'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: '登录',
hidden: true,
component: Login
}, {
path: '/home',
name: '',
component: Home,
hidden: true
}, {
path: '/home',
component: Home,
name: '文章管理',
iconCls: 'fa fa-file-text-o',
children: [
{
path: '/articleList',
name: '文章列表',
component: ArticleList,
meta: {
keepAlive: true
}
}, {
path: '/postArticle',
name: '发表文章',
component: PostArticle,
meta: {
keepAlive: false
}
}, {
path: '/blogDetail',
name: '博客详情',
component: BlogDetail,
hidden: true,
meta: {
keepAlive: false
}
}, {
path: '/editBlog',
name: '编辑博客',
component: PostArticle,
hidden: true,
meta: {
keepAlive: false
}
}
]
}, {
path: '/home',
component: Home,
name: '用户管理',
children: [
{
path: '/user',
iconCls: 'fa fa-user-o',
name: '用户管理',
component: UserMana
}
]
}, {
path: '/home',
component: Home,
name: '栏目管理',
children: [
{
path: '/cateMana',
iconCls: 'fa fa-reorder',
name: '栏目管理',
component: CateMana
}
]
}, {
path: '/home',
component: Home,
name: '数据统计',
iconCls: 'fa fa-bar-chart',
children: [
{
path: '/charts',
iconCls: 'fa fa-bar-chart',
name: '数据统计',
component: DataCharts
}
]
}, {
path: '/home',
component: Home,
name: '身体记录',
iconCls: 'el-icon-date',
children: [
{
path: '/doex',
iconCls: 'el-icon-date',
name: '身体记录',
component: Doex
}
]
}
]
})
utils/api.js:调用后端方法并接受返回值
关于两种不同的Content-Type
:
application/x-www-form-urlencoded
会对参数进行编码,键值对参数用&
连接,空格转换为+
,有特殊符号就转换为ASCII HEX
值。然后这个类型就是编码格式,也是浏览器默认的编码格式。如果是Get请求,就将参数转化成?key=value&key=value
的格式接在url
后面。multipart/form-data
不会进行编码,使用分割线来相当于&
。常用于文件等二进制,也可以用于键值对参数。application/json
也经常使用。- 两种post接口的解读
四种常用请求含义:
Get(SELECT)
:从服务器查询,可以在服务器通过请求的参数区分查询的方式。POST(CREATE)
:在服务器新建一个资源,调用insert
操作。PUT(UPDATE)
:在服务器更新资源,调用update
操作。DELETE(DELETE)
:从服务器删除资源,调用delete
语句。
import axios from 'axios'
// base意义是啥
let base = '';
// 这些要在component用到的时候import
export const postRequest = (url, params) => {
return axios({
method: 'post',
url: `${
base}${
url}`,
data: params,
transformRequest: [function (data) {
// 这边就是参数传递的标准表达式了
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
}
export const uploadFileRequest = (url, params) => {
return axios({
method: 'post',
url: `${
base}${
url}`,
data: params,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
export const putRequest = (url, params) => {
return axios({
method: 'put',
url: `${
base}${
url}`,
data: params,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
}
export const deleteRequest = (url) => {
return axios({
method: 'delete',
url: `${
base}${
url}`
});
}
export const getRequest = (url,params) => {
return axios({
method: 'get',
data:params,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
url: `${
base}${
url}`
});
}
filter_utils:过滤器
- 在一个单独的文件中定义过滤器,使用方法是
{ { 值 | 过滤器函数名}}
过滤器的定义:
import Vue from 'vue'
// 定义全局过滤器,在main.js中导入
Vue.filter("formatDate", function formatDate(value) {
var date = new Date(value);
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
return year + "-" + month + "-" + day;
});
Vue.filter("formatDateTime", function formatDateTime(value) {
var date = new Date(value);
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var hours = date.getHours();
var minutes = date.getMinutes();
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
return year + "-" + month + "-" + day + " " + hours + ":" + minutes;
});
main:导入依赖
导入使用的外部组件,如ElementtUI
、VCharts
,还有过滤器也是在这里导入的。
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
// import './styles/element-variables.scss'
import 'font-awesome/css/font-awesome.min.css'
// 再次导入了过滤器
import './utils/filter_utils.js'
import VCharts from 'v-charts'
Vue.use(ElementUI)
Vue.use(VCharts)
Vue.config.productionTip = false;
window.bus = new Vue();
new Vue({
el: '#app',
router,
template: '<App/>',
components: {
App}
})
组件
- 最核心的部分,通过各种组件的组合、跳转来实现页面的展示。
Login:登录与权限验证
- 登录页面是Login,两个输入框绑定rules校验规则,绑定方法是
el-form
的:rules
,然后通过prop
关联rules
中的性质。 - 然后登录按钮绑定了方法,传递地址为后端的spring security的
/login/{username, password}
,执行后判断
v:onclick.nativa.prevent:“方法名”:
- 给vue组件绑定事件时候,必须加上native ,否则会认为监听的是来自Item组件自定义的事件
- 但父组件想在子组件上监听自己的click的话,需要加上native修饰符,故写法就像上面这样。
- prevent 是用来阻止默认的 ,相当于原生的event.preventDefault()
auto-complete=“off”:
autocomplete 属性规定输入字段是否应该启用自动完成功能。自动完成允许浏览器预测对字段的输入。当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项。
注意:autocomplete 属性适用于 <form>
,以及下面的 <input>
类型:text, search, url, telephone, email, password, datepickers, range 以及 color。
完整代码:
<template>
<!--v-bind:rules,是绑定一些数据?答:是绑定校验规则,通过prop的方法使用rules的校验规则-->
<!--所有的class都可以在下面设置style-->
<el-form :rules="rules" class="login-container" label-position="left"
label-width="0px" v-loading="loading">
<h3 class="login_title">系统登录</h3>
<el-form-item prop="account">
<!--输入用户名,关闭了自动提示。-->
<el-input type="text" v-model="loginForm.username" auto-complete="off" placeholder="账号"></el-input>
</el-form-item>
<!--注意这个prop,这是用来设置校验规则的,-->
<el-form-item prop="checkPass">
<el-input type="password" v-model="loginForm.password" auto-complete="off" placeholder="密码"></el-input>
</el-form-item>
<!--这个左右对齐好像没差别-->
<el-checkbox class="login_remember" v-model="checked" label-position="left">记住密码</el-checkbox>
<el-form-item style="width: 100%">
<!--v:on绑定自定义方法-->
<el-button type="primary" @click.native.prevent="submitClick" style="width: 100%">登录</el-button>
</el-form-item>
</el-form>
</template>
<!--调用utils/api里面的HTTP传递方法-->
<script>
import {
postRequest} from '../utils/api'
import {
putRequest} from '../utils/api'
export default{
data(){
// 下面这些值会传递给上边
// 这个return是要传给后端码?
return {
// rules:规则,校验规则
rules: {
// 跟上面的prop绑定的,触发器为失去焦点
account: [{
required: true, message: '请输入用户名', trigger: 'blur'}],
checkPass: [{
required: true, message: '请输入密码', trigger: 'blur'}]
},
// 默认被选择
checked: true,
// 默认文本内容,因为由v-model,所以会显示
loginForm: {
username: 'sang',
password: '123'
},
loading: false
}
},
methods: {
submitClick: function () {
var _this = this;
// 改变参数
this.loading = true;
// 方法里用的axios,第一个参数是url,第二个是参数
// 方法通往Spring security的权限管理
postRequest('/login', {
username: this.loginForm.username,
password: this.loginForm.password
}).then(resp=> {
_this.loading = false;
if (resp.status == 200) {
//成功
var json = resp.data;
if (json.status == 'success') {
_this.$router.replace({
path: '/home'});
} else {
// 第一个参数是内容,第二个参数是标题
_this.$alert('登录失败!', '失败!');
}
} else {
//失败
_this.$alert('登录失败!', '失败!');
}
}, resp=> {
_this.loading = false;
_this.$alert('找不到服务器⊙﹏⊙∥!', '失败!');
});
}
}
}
</script>
<!--给绑定的class设置style-->
<style>
.login-container {
border-radius: 15px;
background-clip: padding-box;
margin: 180px auto;
width: 350px;
padding: 35px 35px 15px 35px;
background: #fff;
border: 1px solid #eaeaea;
box-shadow: 0 0 25px #cac6c6;
}
.login_title {
margin: 0px auto 40px auto;
text-align: center;
color: #505458;
}
.login_remember {
margin: 0px 0px 35px 0px;
text-align: left;
}
</style>
Home:主页面框架
登录成功就来到Home。
v-if="!item.hidden":
通过hidden来控制是否显示
slot=“title”:是系统自带的吗,找不到东西
Breadcrumb 面包屑:
el-breadcrumb
作用显示当前页面的路径,快速返回之前的任意页面,之前的页面用el-breadcrumb-item
表示
可以控制最上面的导航,加了点东西就变成下一行了
激活判断:
代码中可以根据keep-alive
参数来决定子组件显示的位置
<!--根据激活状态判断在哪里显示-->
<keep-alive>
<div>
{
{
'激活'}}
</div>
<router-view v-if="this.$route.meta.keepAlive"></router-view>
</keep-alive>
<div>
{
{
'未激活'}}
</div>
<router-view v-if="!this.$route.meta.keepAlive"></router-view>
通过:index
来绑定路径path
<!--但是这个怎么跳转呢?-->
<!--index这里绑定了path, 为啥index绑定了就能跳?是因为el-menu-item的特性?-->
<el-menu-item :index="item.children[0].path">
<i :class="item.children[0].iconCls"></i>
<span slot="title">{
{
item.children[0].name}}</span>
</el-menu-item>
登录声明:
通过一个钩子函数触发,当转到该页面的时候,会调用
// 钩子函数,转到的时候触发
mounted: function () {
this.$alert('为了确保所有的小伙伴都能看到完整的数据演示,数据库只开放了查询权限和部分字段的更新权限,其他权限都不具备,完整权限的演示需要大家在自己本地部署后,换一个正常的数据库用户后即可查看,这点请大家悉知!', '友情提示', {
confirmButtonText: '确定',
callback: action => {
}
});
var _this = this;
getRequest("/currentUserName").then(function