🎉 2.7.2.RELEASE 集成JustAuth支持第三方登录

This commit is contained in:
smallchill 2020-08-20 00:22:48 +08:00
parent e4d39096f6
commit 9c8a04763e
25 changed files with 677 additions and 279 deletions

View File

@ -59,7 +59,8 @@ SpringBlade
* 交流一群:`477853168`(满)
* 交流二群:`751253339`(满)
* 交流三群:`784729540`(满)
* 交流四群:`1034621754`
* 交流四群:`1034621754`(满)
* 交流五群:`946350912`
## 在线演示
* Saber-基于Vue[https://saber.bladex.vip](https://saber.bladex.vip)

View File

@ -82,7 +82,7 @@
<div id="app">
<div class="avue-home">
<div class="avue-home__main">
<img class="avue-home__loading" src="./svg/loading-spin.svg" alt="loading">
<img class="avue-home__loading" src="<%= BASE_URL %>svg/loading-spin.svg" alt="loading">
<div class="avue-home__title">
正在加载资源
</div>

View File

@ -22,7 +22,7 @@ export const grant = (roleIds, menuIds) => {
return request({
url: '/api/blade-system/role/grant',
method: 'post',
params: {
data: {
roleIds,
menuIds
}

View File

@ -18,6 +18,22 @@ export const loginByUsername = (tenantId, account, password, type, key, code) =>
}
});
export const loginBySocial = (tenantId, source, code, state) => request({
url: '/api/blade-auth/token',
method: 'post',
headers: {
'Tenant-Id': tenantId
},
params: {
tenantId,
source,
code,
state,
grantType: "social",
scope: "all",
}
});
export const getButtons = () => request({
url: '/api/blade-system/menu/buttons',
method: 'get'
@ -28,11 +44,23 @@ export const getUserInfo = () => request({
method: 'get'
});
export const refeshToken = () => request({
export const refreshToken = () => request({
url: baseUrl + '/user/refesh',
method: 'post'
})
export const registerGuest = (form, oauthId) => request({
url: '/api/blade-user/register-guest',
method: 'post',
params: {
tenantId: form.tenantId,
name: form.name,
account: form.account,
password: form.password,
oauthId
}
});
export const getMenu = () => request({
url: '/api/blade-system/menu/routes',
method: 'get'

View File

@ -0,0 +1,106 @@
<template>
<el-dialog title="账号注册"
append-to-body
:visible.sync="accountBox"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
width="20%">
<el-form :model="form" ref="form" label-width="80px">
<el-form-item label="租户编号">
<el-input v-model="form.tenantId" placeholder="请输入租户编号"></el-input>
</el-form-item>
<el-form-item label="用户姓名">
<el-input v-model="form.name" placeholder="请输入用户姓名"></el-input>
</el-form-item>
<el-form-item label="账号名称">
<el-input v-model="form.account" placeholder="请输入账号名称"></el-input>
</el-form-item>
<el-form-item label="账号密码">
<el-input v-model="form.password" placeholder="请输入账号密码"></el-input>
</el-form-item>
<el-form-item label="确认密码">
<el-input v-model="form.password2" placeholder="请输入确认密码"></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button type="primary" :loading="loading" @click="handleRegister"> </el-button>
</span>
</el-dialog>
</template>
<script>
import {mapGetters} from "vuex";
import {validatenull} from "@/util/validate";
import {registerGuest} from "@/api/user";
export default {
name: "thirdRegister",
data() {
return {
form: {
tenantId: '',
name: '',
account: '',
password: '',
password2: '',
},
loading: false,
accountBox: false,
};
},
computed: {
...mapGetters(["userInfo"]),
},
created() {
},
mounted() {
console.log(this.userInfo)
//
if (validatenull(this.userInfo.userId) || this.userInfo.userId < 0) {
this.form.name = this.userInfo.account;
this.form.account = this.userInfo.account;
this.accountBox = true;
}
},
methods: {
handleRegister() {
if (this.form.tenantId === '') {
this.$message.warning("请先输入租户编号");
return;
}
if (this.form.account === '') {
this.$message.warning("请先输入账号名称");
return;
}
if (this.form.password === '' || this.form.password2 === '') {
this.$message.warning("请先输入密码");
return;
}
if (this.form.password !== this.form.password2) {
this.$message.warning("两次密码输入不一致");
return;
}
this.loading = true;
registerGuest(this.form, this.userInfo.oauthId).then(res => {
this.loading = false;
const data = res.data;
if (data.success) {
this.accountBox = false;
this.$alert("注册申请已提交,请耐心等待管理员通过!", '注册提示').then(() => {
this.$store.dispatch("LogOut").then(() => {
this.$router.push({path: "/login"});
});
})
} else {
this.$message.error(data.msg || '提交失败');
}
}, error => {
window.console.log(error);
this.loading = false;
});
},
},
};
</script>

View File

@ -1,7 +1,7 @@
// 配置编译环境和线上环境之间的切换
let baseUrl = '';
let iconfontVersion = ['567566_pwc3oottzol', '1066523_v8rsbcusj5q'];
let iconfontVersion = ['567566_pwc3oottzol', '1066523_6bvkeuqao36'];
let iconfontUrl = `//at.alicdn.com/t/font_$key.css`;
let codeUrl = `${baseUrl}/code`
const env = process.env

View File

@ -64,6 +64,8 @@ export default [
"iconfont iconicon_addmessage",
"iconfont iconicon_addresslist",
"iconfont iconicon_add",
"iconfont icongithub",
"iconfont icongitee2",
]
},
{

View File

@ -36,5 +36,7 @@ export default {
icon: 'source',
children: 'children'
}
}
},
// 授权地址
authUrl: 'http://localhost/blade-auth/oauth/render'
}

View File

@ -71,6 +71,8 @@ export default {
password: 'Please input a password',
wechat: 'Wechat',
qq: 'QQ',
github: 'github',
gitee: 'gitee',
phone: 'Please input a phone',
code: 'Please input a code',
submit: 'Login',

View File

@ -71,6 +71,8 @@ export default {
password: '请输入密码',
wechat: '微信',
qq: 'QQ',
github: 'github',
gitee: '码云',
phone: '请输入手机号',
code: '请输入验证码',
submit: '登录',

View File

@ -17,6 +17,7 @@ import i18n from './lang' // Internationalization
import './styles/common.scss';
import basicContainer from './components/basic-container/main'
import thirdRegister from './components/third-register/main'
Vue.use(router)
Vue.use(VueAxios, axios)
@ -28,6 +29,7 @@ Vue.use(window.AVUE, {
})
//注册全局容器
Vue.component('basicContainer', basicContainer)
Vue.component('thirdRegister', thirdRegister);
// 加载相关url地址
Object.keys(urls).forEach(key => {
Vue.prototype[key] = urls[key];

View File

@ -95,7 +95,7 @@ export default {
if (!(date.seconds >= this.website.tokenTime) && !this.refreshLock) {
this.refreshLock = true;
this.$store
.dispatch("RefeshToken")
.dispatch("RefreshToken")
.then(() => {
this.refreshLock = false;
})

View File

@ -22,12 +22,9 @@
<codeLogin v-else-if="activeName==='code'"></codeLogin>
<thirdLogin v-else-if="activeName==='third'"></thirdLogin>
<div class="login-menu">
<a href="#"
@click.stop="activeName='user'">{{ $t('login.userLogin') }}</a>
<a href="#"
@click.stop="activeName='code'">{{ $t('login.phoneLogin') }}</a>
<a href="#"
@click.stop="activeName='third'">{{ $t('login.thirdLogin') }}</a>
<a href="#" @click.stop="activeName='user'">{{ $t('login.userLogin') }}</a>
<!--<a href="#" @click.stop="activeName='code'">{{ $t('login.phoneLogin') }}</a>-->
<a href="#" @click.stop="activeName='third'">{{ $t('login.thirdLogin') }}</a>
</div>
</div>
@ -44,6 +41,7 @@ import { dateFormat } from "@/util/date";
import { validatenull } from "@/util/validate";
import topLang from "@/page/index/top/top-lang";
import topColor from "@/page/index/top/top-color";
import {getQueryString, getTopUrl} from "@/util/util";
export default {
name: "login",
components: {
@ -56,42 +54,61 @@ export default {
data() {
return {
time: "",
activeName: "user"
activeName: "user",
socialForm: {
tenantId: "000000",
source: "",
code: "",
state: "",
}
};
},
watch: {
$route() {
const params = this.$route.query;
this.socialForm.state = params.state;
this.socialForm.code = params.code;
if (!validatenull(this.socialForm.state)) {
const loading = this.$loading({
lock: true,
text: `${
this.socialForm.state === "WX" ? "微信" : "QQ"
}登录中,请稍后`,
spinner: "el-icon-loading"
});
setTimeout(() => {
loading.close();
}, 2000);
}
this.handleLogin();
}
},
created() {
this.handleLogin();
this.getTime();
setInterval(() => {
this.getTime();
}, 1000);
},
mounted() {},
mounted() {
},
computed: {
...mapGetters(["website"])
...mapGetters(["website", "tagWel"])
},
props: [],
methods: {
getTime() {
setInterval(() => {
this.time = dateFormat(new Date());
}, 1000);
},
handleLogin() {
const topUrl = getTopUrl();
const redirectUrl = "/oauth/redirect/";
this.socialForm.source = getQueryString("source");
this.socialForm.code = getQueryString("code");
this.socialForm.state = getQueryString("state");
if (validatenull(this.socialForm.source) && topUrl.includes(redirectUrl)) {
let source = topUrl.split("?")[0];
source = source.split(redirectUrl)[1];
this.socialForm.source = source;
}
if (!validatenull(this.socialForm.source) && !validatenull(this.socialForm.code) && !validatenull(this.socialForm.state)) {
const loading = this.$loading({
lock: true,
text: '第三方系统登录中,请稍后。。。',
spinner: "el-icon-loading"
});
this.$store.dispatch("LoginBySocial", this.socialForm).then(() => {
window.location.href = topUrl.split(redirectUrl)[0];
this.$router.push({path: this.tagWel.value});
loading.close();
}).catch(() => {
loading.close();
});
}
}
}
};

View File

@ -1,20 +1,26 @@
<template>
<div class="social-container">
<div class="box"
@click="handleClick('wechat')">
<span class="container"
:style="{backgroundColor:'#6ba2d6'}">
<i icon-class="wechat"
class="iconfont icon-weixin"></i>
<div @click="handleClick('github')">
<span class="container" :style="{backgroundColor:'#61676D'}">
<i icon-class="github" class="iconfont icongithub"></i>
</span>
<p class="title">{{$t('login.github')}}</p>
</div>
<div @click="handleClick('gitee')">
<span class="container" :style="{backgroundColor:'#c35152'}">
<i icon-class="gitee" class="iconfont icongitee2"></i>
</span>
<p class="title">{{$t('login.gitee')}}</p>
</div>
<div @click="handleClick('wechat_open')">
<span class="container" :style="{backgroundColor:'#8dc349'}">
<i icon-class="wechat" class="iconfont icon-weixin"/>
</span>
<p class="title">{{$t('login.wechat')}}</p>
</div>
<div class="box"
@click="handleClick('tencent')">
<span class="container"
:style="{backgroundColor:'#8dc349'}">
<i icon-class="qq"
class="iconfont icon-qq"></i>
<div @click="handleClick('qq')">
<span class="container" :style="{backgroundColor:'#6ba2d6'}">
<i icon-class="qq" class="iconfont icon-qq"/>
</span>
<p class="title">{{$t('login.qq')}}</p>
</div>
@ -22,33 +28,13 @@
</template>
<script>
import { openWindow } from "@/util/util";
import website from '@/config/website';
export default {
name: "thirdLogin",
methods: {
handleClick(thirdpart) {
let appid, client_id, redirect_uri, url;
redirect_uri = encodeURIComponent(
window.location.origin + "/#/authredirect"
);
if (thirdpart === "wechat") {
appid = "xxxx";
url =
"https://open.weixin.qq.com/connect/qrconnect?appid=" +
appid +
"&redirect_uri=" +
redirect_uri +
"&state=WX&response_type=code&scope=snsapi_login#wechat_redirect";
} else if (thirdpart === "tencent") {
client_id = "xxxx";
url =
"https://graph.qq.com/oauth2.0/authorize?response_type=code&state=QQ&client_id=" +
client_id +
"&redirect_uri=" +
redirect_uri;
}
openWindow(url, thirdpart, 540, 540);
handleClick(source) {
window.location.href = `${website.authUrl}/${source}`;
}
}
};
@ -60,15 +46,14 @@ export default {
display: flex;
align-items: center;
justify-content: space-around;
.box {
cursor: pointer;
}
.iconfont {
color: #fff;
font-size: 30px;
}
.container {
$height: 50px;
cursor: pointer;
display: inline-block;
width: $height;
height: $height;

View File

@ -11,26 +11,9 @@ import 'nprogress/nprogress.css' // progress bar style
NProgress.configure({showSpinner: false});
const lockPage = store.getters.website.lockPage; //锁屏页
router.beforeEach((to, from, next) => {
if (to.matched.length === 0 && to.fullPath.indexOf("?sec") === -1) {
next(to.path + "?sec");
window.location.reload();
} else {
next();
}
//缓冲设置
if (to.meta.keepAlive === true && store.state.tags.tagList.some(ele => {
return ele.value === to.fullPath;
})) {
to.meta.$keepAlive = true;
} else {
NProgress.start()
if (to.meta.keepAlive === true && validatenull(to.meta.$keepAlive)) {
to.meta.$keepAlive = true;
} else {
to.meta.$keepAlive = false;
}
}
const meta = to.meta || {};
const isMenu = meta.menu === undefined ? to.query.menu : meta.menu;
store.commit('SET_IS_MENU', isMenu === undefined);
if (getToken()) {
if (store.getters.isLock && to.path !== lockPage) { //如果系统激活锁屏,全部跳转到锁屏页
next({path: lockPage})

View File

@ -1,4 +1,5 @@
import Layout from '@/page/index/'
export default [{
path: '/wel',
component: Layout,
@ -10,7 +11,16 @@ export default [{
i18n: 'dashboard'
},
component: () =>
import( /* webpackChunkName: "views" */ '@/views/wel')
import( /* webpackChunkName: "views" */ '@/views/wel/index')
}, {
path: 'dashboard',
name: '控制台',
meta: {
i18n: 'dashboard',
menu: false,
},
component: () =>
import( /* webpackChunkName: "views" */ '@/views/wel/dashboard')
}]
}, {
path: '/test',

View File

@ -11,6 +11,7 @@ const getters = {
screen: state => state.common.screen,
isLock: state => state.common.isLock,
isFullScren: state => state.common.isFullScren,
isMenu: state => state.common.isMenu,
lockPasswd: state => state.common.lockPasswd,
tagList: state => state.tags.tagList,
tagWel: state => state.tags.tagWel,

View File

@ -4,12 +4,14 @@ import {
removeStore
} from '@/util/store'
import website from '@/config/website'
const common = {
state: {
language: getStore({name: 'language'}) || 'zh',
isCollapse: false,
isFullScren: false,
isMenu: true,
isShade: false,
screen: -1,
isLock: getStore({name: 'isLock'}) || false,
@ -44,6 +46,9 @@ const common = {
SET_FULLSCREN: (state) => {
state.isFullScren = !state.isFullScren;
},
SET_IS_MENU: (state, menu) => {
state.isMenu = menu;
},
SET_LOCK: (state) => {
state.isLock = true;
setStore({

View File

@ -3,7 +3,8 @@ import {setStore, getStore} from '@/util/store'
import {isURL, validatenull} from '@/util/validate'
import {deepClone} from '@/util/util'
import webiste from '@/config/website'
import {loginByUsername, getUserInfo, getMenu, getTopMenu, logout, refeshToken, getButtons} from '@/api/user'
import {Message} from 'element-ui'
import {loginByUsername, loginBySocial, getUserInfo, getMenu, getTopMenu, logout, refreshToken, getButtons} from '@/api/user'
function addPath(ele, first) {
@ -45,7 +46,7 @@ const user = {
loginByUsername(userInfo.tenantId, userInfo.username, userInfo.password, userInfo.type, userInfo.key, userInfo.code).then(res => {
const data = res.data.data;
commit('SET_TOKEN', data.accessToken);
commit('SET_USERIFNO', data);
commit('SET_USER_INFO', data);
commit('DEL_ALL_TAG');
commit('CLEAR_LOCK');
resolve();
@ -54,11 +55,23 @@ const user = {
})
})
},
GetButtons({commit}) {
//根据第三方信息登录
LoginBySocial({ commit }, userInfo) {
return new Promise((resolve) => {
getButtons().then(res => {
const data = res.data.data;
commit('SET_PERMISSION', data);
loginBySocial(userInfo.tenantId, userInfo.source, userInfo.code, userInfo.state).then(res => {
const data = res.data;
if (data.success) {
commit('SET_TOKEN', data.data.accessToken);
commit('SET_REFRESH_TOKEN', data.data.refreshToken);
commit('SET_USER_INFO', data.data);
commit('DEL_ALL_TAG');
commit('CLEAR_LOCK');
} else {
Message({
message: data.msg,
type: 'error'
})
}
resolve();
})
})
@ -87,9 +100,9 @@ const user = {
})
},
//刷新token
RefeshToken({state, commit}) {
RefreshToken({state, commit}) {
return new Promise((resolve, reject) => {
refeshToken(state.refeshToken).then(res => {
refreshToken(state.refreshToken).then(res => {
const data = res.data.data;
commit('SET_TOKEN', data);
resolve(data);
@ -102,9 +115,10 @@ const user = {
LogOut({commit}) {
return new Promise((resolve, reject) => {
logout().then(() => {
commit('SET_TOKEN', '')
commit('SET_TOKEN', '');
commit('SET_MENU', [])
commit('SET_ROLES', [])
commit('SET_MENU_ALL', []);
commit('SET_ROLES', []);
commit('DEL_ALL_TAG');
commit('CLEAR_LOCK');
removeToken()
@ -117,9 +131,10 @@ const user = {
//注销session
FedLogOut({commit}) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_MENU', [])
commit('SET_ROLES', [])
commit('SET_TOKEN', '');
commit('SET_MENU', []);
commit('SET_MENU_ALL', []);
commit('SET_ROLES', []);
commit('DEL_ALL_TAG');
commit('CLEAR_LOCK');
removeToken()
@ -143,12 +158,22 @@ const user = {
menu.forEach(ele => {
addPath(ele, true);
})
commit('SET_MENU', menu)
commit('SET_MENU', menu);
commit('SET_MENU_ALL', menu);
dispatch('GetButtons');
resolve(menu)
})
})
},
GetButtons({commit}) {
return new Promise((resolve) => {
getButtons().then(res => {
const data = res.data.data;
commit('SET_PERMISSION', data);
resolve();
})
})
},
},
mutations: {
SET_TOKEN: (state, token) => {
@ -156,16 +181,33 @@ const user = {
state.token = token;
setStore({name: 'token', content: state.token, type: 'session'})
},
SET_USERIFNO: (state, userInfo) => {
SET_USER_INFO: (state, userInfo) => {
state.userInfo = userInfo;
setStore({name: 'userInfo', content: state.userInfo})
},
SET_MENU_ALL: (state, menuAll) => {
state.menuAll = menuAll
setStore({name: 'menuAll', content: state.menuAll, type: 'session'})
},
SET_MENU: (state, menu) => {
state.menu = menu
setStore({name: 'menu', content: state.menu, type: 'session'})
},
SET_MENU_ALL: (state, menuAll) => {
state.menuAll = menuAll;
if (validatenull(menu)) return;
//合并动态路由去重
let menuAll = state.menuAll;
menuAll = menuAll.concat(menu).reverse();
let newMenu = [];
for (let item1 of menuAll) {
let flag = true;
for (let item2 of newMenu) {
if (item1.name === item2.name || item1.path === item2.path) {
flag = false;
}
}
if (flag) newMenu.push(item1);
}
state.menuAll = newMenu;
setStore({name: 'menuAll', content: state.menuAll, type: 'session'})
},
SET_ROLES: (state, roles) => {
state.roles = roles;

View File

@ -26,6 +26,7 @@ export const getObjType = obj => {
}
return map[toString.call(obj)];
};
/**
* 对象深拷贝
*/
@ -286,3 +287,21 @@ export const openWindow = (url, title, w, h) => {
newWindow.focus()
}
}
/**
* 获取顶部地址栏地址
*/
export const getTopUrl = () => {
return window.location.href.split("/#/")[0];
}
/**
* 获取url参数
* @param name 参数名
*/
export const getQueryString = (name) => {
let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
let r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(decodeURI(r[2]));
return null;
}

View File

@ -180,15 +180,15 @@
dicData: [
{
label: "工具栏",
value: 0
},
{
label: "操作栏",
value: 1
},
{
label: "工具操作栏",
label: "操作栏",
value: 2
},
{
label: "工具操作栏",
value: 3
}
],
hide: true,

View File

@ -183,12 +183,19 @@
ids.push(ele.id);
});
return ids.join(",");
},
idsArray() {
let ids = [];
this.selectionList.forEach(ele => {
ids.push(ele.id);
});
return ids;
}
},
methods: {
submit() {
const menuLIst = this.$refs.tree.getCheckedKeys().join(",");
grant(this.ids, menuLIst).then(() => {
const menuLIst = this.$refs.tree.getCheckedKeys();
grant(this.idsArray, menuLIst).then(() => {
this.box = false;
this.$message({
type: "success",

View File

@ -102,6 +102,7 @@
<script>
import {
getList,
getUser,
remove,
update,
add,
@ -555,7 +556,18 @@
},
beforeOpen(done, type) {
if (["edit", "view"].includes(type)) {
//
getUser(this.form.id).then(res => {
this.form = res.data.data;
if(this.form.hasOwnProperty("deptId")){
this.form.deptId = this.form.deptId.split(",");
}
if(this.form.hasOwnProperty("roleId")){
this.form.roleId = this.form.roleId.split(",");
}
if(this.form.hasOwnProperty("postId")){
this.form.postId = this.form.postId.split(",");
}
});
}
done();
},

156
src/views/wel/dashboard.vue Normal file
View File

@ -0,0 +1,156 @@
<template>
<basic-container>
<div class="wel">
<basic-block :width="width"
:height="height"
icon="el-icon-platform-eleme"
text="开始菜单1"
time="1"
background="/img/bg/bg3.jpg"
color="#d56259"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-eleme"
text="开始菜单2"
time="2"
background="/img/bg/bg2.jpg"
color="#419ce7"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-delete-solid"
text="开始菜单3"
time="3"
color="#56b69b"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-delete"
text="开始菜单4"
time="4"
color="#d44858"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-tools"
text="开始菜单5"
time="5"
color="#3a1f7e"></basic-block>
<basic-block :width="410"
:height="height"
icon="el-icon-setting"
text="开始菜单6"
time="6"
background="/img/bg/bg1.jpg"
dept="这是一段很长的很长很长很长的描述这是一段很长的很长很长很长的描述"
color="#422829"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-user-solid"
text="开始菜单7"
time="7"
color="#613cbd"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-star-off"
text="开始菜单8"
time="8"
color="#da542e"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-goods"
text="开始菜单9"
time="9"
color="#2e8aef"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-circle-check"
text="开始菜单10"
time="10"
color="#3d17b8"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-platform"
text="开始菜单11"
time="11"
color="#e31462"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-fold"
text="开始菜单12"
time="12"
color="#d9532d"></basic-block>
<basic-block :width="410"
:height="height"
icon="el-icon-s-open"
text="开始菜单13"
time="13"
dept="这是一段很长的很长很长很长的描述这是一段很长的很长很长很长的描述"
color="#b72147"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-flag"
text="开始菜单14"
time="14"
color="#01a100"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-data"
text="开始菜单15"
time="15"
color="#0c56bf"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-grid"
text="开始菜单16"
time="16"
color="#0098a9"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-release"
text="开始菜单17"
time="17"
background="/img/bg/bg2.jpg"
color="#209bdf"></basic-block>
<basic-block :width="width"
:height="height"
icon="el-icon-s-home"
text="开始菜单18"
time="18"
background="/img/bg/bg3.jpg"
color="#603bbc"></basic-block>
<basic-block :width="515"
:height="height"
icon="el-icon-s-promotion"
text="开始菜单19"
time="19"
dept="这是一段很长的很长很长很长的描述这是一段很长的很长很长很长的描述"
color="#009bad"></basic-block>
<basic-block :width="515"
:height="height"
icon="el-icon-s-custom"
text="开始菜单20"
time="20"
background="/img/bg/bg4.jpg"
dept="这是一段很长的很长很长很长的描述这是一段很长的很长很长很长的描述"
color="#d74e2a"></basic-block>
</div>
</basic-container>
</template>
<script>
export default {
data() {
return {
width: 200,
height: 120,
}
}
}
</script>
<style lang="scss">
.wel {
display: flex;
flex-wrap: wrap;
width: 1100px;
margin: 0 auto;
}
</style>

View File

@ -1,11 +1,12 @@
<template>
<div>
<basic-container>
<third-register></third-register>
<p style="text-align: center;">
<img src="https://img.shields.io/badge/Release-V2.7.0-green.svg" alt="Downloads"/>
<img src="https://img.shields.io/badge/Release-V2.7.2-green.svg" alt="Downloads"/>
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status"/>
<img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR3-blue.svg" alt="Coverage Status"/>
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.6.RELEASE-blue.svg" alt="Downloads"/>
<img src="https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR7-blue.svg" alt="Coverage Status"/>
<img src="https://img.shields.io/badge/Spring%20Boot-2.2.9.RELEASE-blue.svg" alt="Downloads"/>
<a target="_blank" href="https://bladex.vip">
<img src="https://img.shields.io/badge/Saber%20Author-Small%20Chill-ff69b4.svg" alt="Downloads"/>
</a>
@ -124,17 +125,32 @@
<el-row>
<basic-container>
<el-collapse v-model="logActiveNames" @change="handleChange">
<el-collapse-item title="2.7.2发布 集成JustAuth支持第三方登录" name="19">
<div>1.升级至 SpringCloud Hoxton.SR7</div>
<div>2.升级至 SpringBoot 2.2.9.RELEASE</div>
<div>4.升级至 SpringBootAdmin 2.3.0</div>
<div>3.升级至 Seata 1.3.0</div>
<div>5.升级至 Kinfe4j 2.0.4</div>
<div>6.升级至 FastJson 1.2.73</div>
<div>8.集成JustAuth支持第三方登录</div>
<div>9.优化请求日志打印工具</div>
<div>10.优化Token返回字段集合</div>
<div>11.修复菜单列表API报空指针的问题</div>
<div>12.修复角色配置数据量较大导致失败的问题</div>
</el-collapse-item>
<el-collapse-item title="2.7.1发布 增加行政区划管理支持seata1.2" name="18">
<div>1.升级至 SpringCloud Hoxton.SR5</div>
<div>2.升级至 SpringBoot 2.2.7.RELEASE</div>
<div>3.升级至 Seata 1.2.0</div>
<div>4.升级至 FastJson 1.2.70</div>
<div>5.升级至 Avue 2.5.3</div>
<div>6.新增行政区划管理模块</div>
<div>7.优化用户导入的密码配置逻辑</div>
<div>8.优化INode结构支持懒加载数据格式</div>
<div>9.优化代码生成模板支持最新版Saber结构</div>
<div>10.修复Log模块在多线程异步场景下报错的问题</div>
<div>4.升级至 MybatisPlus 3.3.2</div>
<div>5.升级至 Kinfe4j 2.0.3</div>
<div>6.升级至 FastJson 1.2.70</div>
<div>7.升级至 Avue 2.5.3</div>
<div>8.新增行政区划管理模块</div>
<div>9.优化用户导入的密码配置逻辑</div>
<div>10.优化INode结构支持懒加载数据格式</div>
<div>11.优化代码生成模板支持最新版Saber结构</div>
<div>12.修复Log模块在多线程异步场景下报错的问题</div>
</el-collapse-item>
<el-collapse-item title="2.7.0发布 内核全面升级,增加岗位管理,用户导入导出" name="17">
<div>1.升级至 SpringCloud Hoxton.SR3</div>