在线用户,验证码,bug修复,代码优化

This commit is contained in:
吕金泽 2022-03-16 10:38:26 +08:00
parent 6401c76e65
commit bdea28e66e
38 changed files with 1042 additions and 132 deletions

View File

@ -0,0 +1,10 @@
{
"properties" : { },
"id" : "d95a58e77d314370862ffc4cdfdb8283",
"name" : "在线用户",
"type" : "api",
"parentId" : "02df51e4d7184780a98b632f43dc5848",
"path" : "/online",
"paths" : [ ],
"options" : [ ]
}

View File

@ -0,0 +1,126 @@
{
"properties" : { },
"id" : "d5ccf62c6d2c482e995ce7fe237e9ed3",
"script" : null,
"groupId" : "d95a58e77d314370862ffc4cdfdb8283",
"name" : "列表",
"createTime" : null,
"updateTime" : 1647396892675,
"lock" : null,
"createBy" : null,
"updateBy" : null,
"path" : "/list",
"method" : "POST",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : "{\n \"code\": 200,\n \"message\": \"success\",\n \"data\": [\"cb63a01c-63d7-4722-a2c4-48fffa4b3502\", \"876dd178-0065-42d5-989a-a6ef460113b1\"],\n \"timestamp\": 1647396771372,\n \"executeTime\": 11\n}",
"description" : null,
"requestBodyDefinition" : null,
"responseBodyDefinition" : {
"name" : "",
"value" : "",
"description" : "",
"required" : false,
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ {
"name" : "code",
"value" : "200",
"description" : "",
"required" : false,
"dataType" : "Integer",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "message",
"value" : "success",
"description" : "",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "data",
"value" : "",
"description" : "",
"required" : false,
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ {
"name" : "",
"value" : "cb63a01c-63d7-4722-a2c4-48fffa4b3502",
"description" : "",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
} ]
}, {
"name" : "timestamp",
"value" : "1647396771372",
"description" : "",
"required" : false,
"dataType" : "Long",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "executeTime",
"value" : "11",
"description" : "",
"required" : false,
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
} ]
}
}
================================
import cn.dev33.satoken.stp.StpUtil
var tokens = StpUtil.searchTokenValue("", -1, 0).map(it => it.replace('token:login:token:', ''))
return db.page("""
select
su.username,
so.name office_name,
sll.token,
sll.ip,
sll.browser,
sll.os,
sll.create_date
from sys_login_log sll left join sys_user su on sll.username = su.username left join sys_office so on su.office_id = so.id
where sll.token in (#{tokens})
?{username, and su.username like concat('%', #{username}, '%')}
?{ip, and sll.ip like concat('%', #{ip}, '%')}
order by sll.create_date desc
""")

View File

@ -0,0 +1,26 @@
{
"properties" : { },
"id" : "01fb971510764df3be20209905e86fab",
"script" : null,
"groupId" : "d95a58e77d314370862ffc4cdfdb8283",
"name" : "踢人",
"createTime" : null,
"updateTime" : 1647397091507,
"lock" : null,
"createBy" : null,
"updateBy" : null,
"path" : "/logout",
"method" : "GET",
"parameters" : [ ],
"options" : [ ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : null,
"description" : null,
"requestBodyDefinition" : null,
"responseBodyDefinition" : null
}
================================
import cn.dev33.satoken.stp.StpUtil
return StpUtil.logoutByTokenValue(token);

View File

@ -5,13 +5,49 @@
"groupId" : "1952f25c81084e24b55b11385767dc38",
"name" : "登录",
"createTime" : null,
"updateTime" : 1646544815203,
"updateTime" : 1647396539380,
"lock" : "0",
"createBy" : null,
"updateBy" : null,
"path" : "/login",
"method" : "POST",
"parameters" : [ ],
"parameters" : [ {
"name" : "username",
"value" : null,
"description" : null,
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
}, {
"name" : "password",
"value" : null,
"description" : null,
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
}, {
"name" : "code",
"value" : null,
"description" : null,
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"options" : [ {
"name" : "require_login",
"value" : "false",
@ -28,7 +64,7 @@
"requestBody" : "{\r\n \"username\": \"admin\",\r\n \"password\": \"123456\"\r\n}",
"headers" : [ ],
"paths" : [ ],
"responseBody" : "{\"code\":200,\"message\":\"success\",\"data\":\"60f0ddf8-c42c-4b3e-802f-7482f6bd1075\",\"timestamp\":1646493023758,\"executeTime\":316}",
"responseBody" : "{\n \"code\": 200,\n \"message\": \"success\",\n \"data\": \"cb63a01c-63d7-4722-a2c4-48fffa4b3502\",\n \"timestamp\": 1647395770056,\n \"executeTime\": 20\n}",
"description" : null,
"requestBodyDefinition" : {
"name" : "",
@ -104,10 +140,10 @@
"children" : [ ]
}, {
"name" : "data",
"value" : "60f0ddf8-c42c-4b3e-802f-7482f6bd1075",
"value" : "cb63a01c-63d7-4722-a2c4-48fffa4b3502",
"description" : "",
"required" : false,
"dataType" : "String",
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
@ -116,7 +152,7 @@
"children" : [ ]
}, {
"name" : "timestamp",
"value" : "1646493023758",
"value" : "1647395770056",
"description" : "",
"required" : false,
"dataType" : "Long",
@ -128,7 +164,7 @@
"children" : [ ]
}, {
"name" : "executeTime",
"value" : "316",
"value" : "20",
"description" : "",
"required" : false,
"dataType" : "Integer",
@ -147,6 +183,15 @@ import 'cn.dev33.satoken.secure.SaSecureUtil';
import 'cn.dev33.satoken.stp.StpUtil';
import env;
import request;
import org.ssssssss.magicboot.model.CodeCacheMap
import cn.hutool.http.useragent.UserAgentUtil
import cn.hutool.http.useragent.UserAgent
UserAgent ua = UserAgentUtil.parse(request.getHeaders("User-Agent")[0])
if(body.code != CodeCacheMap.get(body.uuid)){
exit 0, '验证码错误'
}
var user
if(env.get('super-password') == body.password){
@ -158,15 +203,21 @@ if(env.get('super-password') == body.password){
var loginLog = {
username: body.username,
type: '成功',
ip: request.getClientIP()
ip: request.getClientIP(),
browser: ua.getBrowser().toString(),
os: ua.getOs().toString()
}
if(!user){
loginLog.failPassword = body.password
loginLog.type = '失败'
db.table("sys_login_log").primary("id").save(loginLog);
exit 0,'用户名或密码错误'
}
db.table("sys_login_log").primary("id").save(loginLog);
StpUtil.login(user.id)
var token = StpUtil.getTokenValueByLoginId(user.id)
loginLog.token = token
db.table("sys_login_log").primary("id").save(loginLog);
CodeCacheMap.remove(body.uuid)
return StpUtil.getTokenValueByLoginId(user.id)

View File

@ -0,0 +1,149 @@
{
"properties" : { },
"id" : "be89865140ab409085db6d0cc6d82452",
"script" : null,
"groupId" : "1952f25c81084e24b55b11385767dc38",
"name" : "验证码",
"createTime" : null,
"updateTime" : 1647333578623,
"lock" : null,
"createBy" : null,
"updateBy" : null,
"path" : "/verification/code",
"method" : "GET",
"parameters" : [ ],
"options" : [ {
"name" : "require_login",
"value" : "false",
"description" : "该接口需要登录才允许访问",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : null,
"error" : null,
"expression" : null,
"children" : null
} ],
"requestBody" : "",
"headers" : [ ],
"paths" : [ ],
"responseBody" : "{\n \"code\": 200,\n \"message\": \"success\",\n \"data\": {\n \"img\": \"data:image/png;base64,[B@65617f6d\",\n \"uuid\": \"9a080e570e0346bf8b80c035c977c888\"\n },\n \"timestamp\": 1647333558240,\n \"executeTime\": 15\n}",
"description" : null,
"requestBodyDefinition" : null,
"responseBodyDefinition" : {
"name" : "",
"value" : "",
"description" : "",
"required" : false,
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ {
"name" : "code",
"value" : "200",
"description" : "",
"required" : false,
"dataType" : "Integer",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "message",
"value" : "success",
"description" : "",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "data",
"value" : "",
"description" : "",
"required" : false,
"dataType" : "Object",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ {
"name" : "img",
"value" : "data:image/png;base64,[B@65617f6d",
"description" : "",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "uuid",
"value" : "9a080e570e0346bf8b80c035c977c888",
"description" : "",
"required" : false,
"dataType" : "String",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
} ]
}, {
"name" : "timestamp",
"value" : "1647333558240",
"description" : "",
"required" : false,
"dataType" : "Long",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
}, {
"name" : "executeTime",
"value" : "15",
"description" : "",
"required" : false,
"dataType" : "Integer",
"type" : null,
"defaultValue" : null,
"validateType" : "",
"error" : "",
"expression" : "",
"children" : [ ]
} ]
}
}
================================
import cn.hutool.captcha.LineCaptcha
import cn.hutool.captcha.CaptchaUtil
import log
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import org.ssssssss.magicboot.model.CodeCacheMap
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 48);
var uuid = UUID.randomUUID().toString().replace('-', '')
CodeCacheMap.put(uuid, lineCaptcha.getCode())
OutputStream bOut = new ByteArrayOutputStream();
lineCaptcha.write(bOut)
var bytes = bOut.toByteArray()
return {
img: bytes,
uuid: uuid
}

View File

@ -23,10 +23,6 @@
border: 1px solid #efefef
}
body{
--el-dialog__wrapper-bottom: 15vh;
--el-dialog__wrapper-top: 15vh;
--el-dialog__body-max-height: 60vh;
--mb-main-color: #409EFF;
--mb-sidebar-width: 240px;
--mb-main-icon-color: #909399;

View File

@ -24,7 +24,7 @@
import {ref, reactive, getCurrentInstance, defineExpose } from 'vue'
const { proxy } = getCurrentInstance()
const rules = reactive(getRules())
const formData = ref(getFormData())
const formData = ref(initFormData())
const dataForm = ref()
const props = defineProps({
form: {
@ -42,14 +42,14 @@
proxy.$common.setDefaultValue(props.form.props, 'labelPosition', 'right')
proxy.$common.setDefaultValue(props.form.props, 'labelWidth', '120px')
if(props.detail.formData){
if(props.detail && props.detail.formData){
if(props.detail.handlerFormData){
props.detail.handlerFormData(props.detail.formData)
}
formData.value = props.detail.formData
}
if(props.detail.request){
if(props.detail && props.detail.request){
}
@ -65,16 +65,21 @@
return _rules
}
function getFormData() {
function initFormData() {
var data = {}
props.form.rows.forEach(row => {
row.cols.forEach(col => {
data[col.name] = col.defaultValue === null ? col.defaultValue : col.defaultValue || ''
// data[col.name] = col.defaultValue === null ? col.defaultValue : col.defaultValue || ''
data[col.name] = undefined
})
})
return data
}
function getFormData(){
return formData.value
}
function save(d) {
dataForm.value.validate((valid) => {
if (valid) {
@ -87,7 +92,7 @@
type: 'success',
duration: 2000
})
if(props.detail.formData){
if(props.detail && props.detail.formData){
props.detail.formData = {}
}
d.hide()
@ -109,6 +114,6 @@
})
}
defineExpose({ save, getDetail })
defineExpose({ save, getDetail, getFormData })
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="app-container">
<mb-search v-if="table.where" :where="table.where" :no-reset="search.noReset" @search="reload" />
<mb-search v-if="table.where" :where="table.where" :no-reset="search && search.noReset" @search="reload" />
<el-row class="toolbar-container">
<div v-for="(it, i) in tools" :key="i">

View File

@ -1,10 +1,22 @@
<template>
<el-dialog :fullscreen="fullscreen" :width="width" :title="title" v-model="dialogVisible" :close-on-click-modal="false" :key="dialogKey" :append-to-body="true" draggable @opened="opened">
<el-dialog
v-model="dialogVisible"
:custom-class="customClass"
:fullscreen="fullscreen"
:width="width"
:title="title"
:close-on-click-modal="false"
:destroy-on-close="destroyOnClose"
:append-to-body="true"
draggable
@open="$emit('open')"
@close="$emit('close')"
>
<slot name="content" />
<template #footer>
<div slot="footer" class="dialog-footer">
<slot name="btns">
<el-button @click="dialogVisible = false">
<el-button @click="hide">
关闭
</el-button>
<el-button type="primary" :loading="confirmLoading" @click="confirmClick">
@ -17,9 +29,8 @@
</template>
<script>
export default {
emits: ['confirm-click'],
emits: ['confirm-click', 'open', 'close'],
props: {
title: {
type: String,
@ -33,11 +44,7 @@ export default {
type: Boolean,
default: false
},
opened: {
type: Function,
default: () => {}
},
autoKey: {
destroyOnClose: {
type: Boolean,
default: true
}
@ -46,19 +53,20 @@ export default {
return {
dialogVisible: false,
confirmLoading: false,
dialogKey: 'mbDialog',
customClass: 'mbDialog' + this.$common.uuid()
}
},
watch: {
dialogVisible(value) {
if(value){
this.addStyle()
}else{
this.removeStyle()
}
}
},
created() {
if (this.fullscreen) {
document.body.style.setProperty('--el-dialog__wrapper-bottom', '0vh')
document.body.style.setProperty('--el-dialog__wrapper-top', '0vh')
document.body.style.setProperty('--el-dialog__body-max-height', '100vh')
} else {
document.body.style.setProperty('--el-dialog__wrapper-bottom', '15vh')
document.body.style.setProperty('--el-dialog__wrapper-top', '15vh')
document.body.style.setProperty('--el-dialog__body-max-height', '60vh')
}
this.addStyle()
},
methods: {
confirmClick() {
@ -71,33 +79,43 @@ export default {
this.confirmLoading = false
},
show() {
if(this.autoKey){
this.dialogKey = Math.random()
}
this.dialogVisible = true
},
hide() {
this.dialogVisible = false
},
addStyle(){
var componentStyle = document.createElement("style");
var cc = this.customClass
if (this.fullscreen) {
componentStyle.innerHTML = `
.${cc}{
margin-top: 0vh;
margin-bottom: 0vh;
}
.${cc} .el-dialog__body{
max-height: 100vh;
}
`
} else {
componentStyle.innerHTML = `
.${cc}{
margin-top: 10vh;
margin-bottom: 10vh;
}
.${cc} .el-dialog__body{
max-height: 60vh;
overflow: auto;
}
`
}
componentStyle.id = cc
document.head.appendChild(componentStyle);
},
removeStyle(){
document.getElementById(this.customClass) && document.getElementById(this.customClass).remove()
}
}
}
</script>
<style scoped>
.el-dialog__wrapper{
padding-bottom: var(--el-dialog__wrapper-bottom);
padding-top: var(--el-dialog__wrapper-top);
overflow: hidden;
}
.el-dialog__wrapper >>> .el-dialog{
margin-top: 0vh!important;
}
.el-dialog__wrapper >>> .el-dialog__body{
max-height: var(--el-dialog__body-max-height);
overflow: auto;
padding: 25px!important;
}
</style>

View File

@ -1,17 +0,0 @@
<template>
<el-radio-group v-model="modelValue" v-bind="props.props">
<el-radio-button v-for="it in options" :label="it.value" :disabled="it.disabled" :name="it.disabled">{{ it.label }}</el-radio-button>
</el-radio-group>
</template>
<script setup>
import { watch } from 'vue'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: String,
options: Array
})
watch(() => props.modelValue, (value) => {
emit('update:modelValue', value)
})
</script>

View File

@ -1,3 +0,0 @@
<template>
</template>

View File

@ -37,7 +37,7 @@
<script setup>
import { nextTick, watch } from 'vue'
import { nextTick, watch, onMounted } from 'vue'
const props = defineProps({
where: {
@ -60,7 +60,7 @@ watch(() => props.where,() => {
console.log(props.where)
})
const emit = defineEmits(['search'])
const emit = defineEmits(['search', 'mounted'])
function input(input){
if(input){
@ -101,6 +101,10 @@ function reset() {
nextTick(() => emit('search'))
}
onMounted(() => {
emit('mounted')
})
</script>
<style scoped>

View File

@ -1,5 +1,6 @@
<template>
<el-table-column
v-bind="col.props"
:key="col.field"
:label="col.label"
:prop="col.field"

View File

@ -0,0 +1,94 @@
<template>
<el-checkbox-group
v-model="modelValue"
:size="size"
:disabled="disabled"
:min="min"
:max="max"
:text-color="textColor"
:fill="fill"
@change="change"
>
<el-checkbox-button v-if="button" v-for="it in options" v-bind="it" :label="it[valueField]">
{{ it[labelField] }}
</el-checkbox-button>
<el-checkbox v-if="!button" v-for="it in options" v-bind="it" :label="it[valueField]">
{{ it[labelField] }}
</el-checkbox>
</el-checkbox-group>
</template>
<script setup>
import {watch, ref, getCurrentInstance} from "vue";
import request from '@/scripts/request'
const emit = defineEmits(['update:modelValue', 'change'])
const { proxy } = getCurrentInstance()
const props = defineProps({
modelValue: {
type: Array,
default: () => []
},
type: String,
button: {
type: Boolean,
default: false
},
options: Array,
url: String,
params: Object,
method: {
type: String,
default: 'get'
},
labelField: {
type: String,
default: 'label'
},
valueField: {
type: String,
default: 'value'
},
size: String,
disabled: {
type: Boolean,
default: false
},
min: Number,
max: Number,
textColor: {
type: String,
default: '#fff'
},
fill: {
type: String,
default: '#409EFF'
}
})
const options = ref([])
if(props.type){
options.value = proxy.$common.getDictType(props.type)
}else if(props.url){
request({
url: props.url,
method: props.method,
params: props.params,
data: props.params
}).then(res => {
options.value = res.data.list || res.data
})
}else if(props.options){
options.value = props.options
}
function change(value){
emit('change', value)
}
watch(() => props.modelValue, (value) => {
console.log(value)
emit('update:modelValue', value)
})
</script>

View File

@ -12,7 +12,7 @@
</template>
<script setup>
import { watch } from 'vue'
import { watch, ref } from 'vue'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: String,
@ -23,11 +23,7 @@
},
format: {
type: String,
default: 'yyyy-MM-dd'
},
valueFormat: {
type: String,
default: 'yyyy-MM-dd'
default: 'YYYY-MM-DD'
},
startPlaceholder: {
type: String,
@ -39,6 +35,7 @@
},
props: Object
})
const valueFormat = ref(props.format)
watch(() => props.modelValue, (value) => {
emit('update:modelValue', value)
})

View File

@ -0,0 +1,86 @@
<template>
<el-radio-group
v-model="modelValue"
:size="size"
:disabled="disabled"
:text-color="textColor"
:fill="fill"
@change="change"
>
<el-radio-button v-if="button" v-for="it in options" v-bind="it" :label="it[valueField]">
{{ it[labelField] }}
</el-radio-button>
<el-radio v-if="!button" v-for="it in options" v-bind="it" :label="it[valueField]">
{{ it[labelField] }}
</el-radio>
</el-radio-group>
</template>
<script setup>
import {watch, ref, getCurrentInstance} from "vue";
import request from '@/scripts/request'
const emit = defineEmits(['update:modelValue', 'change'])
const { proxy } = getCurrentInstance()
const props = defineProps({
modelValue: String | Number | Boolean,
type: String,
button: {
type: Boolean,
default: false
},
options: Array,
url: String,
params: Object,
method: {
type: String,
default: 'get'
},
labelField: {
type: String,
default: 'label'
},
valueField: {
type: String,
default: 'value'
},
size: String,
disabled: {
type: Boolean,
default: false
},
textColor: {
type: String,
default: '#fff'
},
fill: {
type: String,
default: '#409EFF'
}
})
const options = ref([])
if(props.type){
options.value = proxy.$common.getDictType(props.type)
}else if(props.url){
request({
url: props.url,
method: props.method,
params: props.params,
data: props.params
}).then(res => {
options.value = res.data.list || res.data
})
}else if(props.options){
options.value = props.options
}
function change(value){
emit('change', value)
}
watch(() => props.modelValue, (value) => {
emit('update:modelValue', value)
})
</script>

View File

@ -11,9 +11,12 @@
import { watch } from 'vue'
const emit = defineEmits(['update:modelValue'])
const props = defineProps({
modelValue: String,
activeValue: String,
inactiveValue: String,
modelValue: {
type: Boolean,
default: false
},
activeValue: Boolean | String | Number,
inactiveValue: Boolean | String | Number,
props: Object
})
watch(() => props.modelValue, (value) => {

View File

@ -26,11 +26,11 @@
<script setup>
import { watch, ref, reactive, defineExpose, nextTick, getCurrentInstance, onBeforeMount } from 'vue'
import { watch, ref, reactive, defineExpose, nextTick, getCurrentInstance, onBeforeMount, onMounted } from 'vue'
const { proxy } = getCurrentInstance()
const emit = defineEmits(['update:select-values', 'check-change', 'node-click'])
const emit = defineEmits(['update:select-values', 'check-change', 'node-click', 'mounted'])
const props = defineProps({
url: {
@ -75,6 +75,10 @@ const props = defineProps({
}
})
watch(() => props.checkedIds, (value) => {
console.log(value)
})
const tree = ref()
const treeData = ref([])
const searchData = ref([])
@ -91,6 +95,10 @@ onBeforeMount(async () => {
await loadTreeData()
})
onMounted(() => {
emit('mounted')
})
watch(() => props.selectValues, async () => {
await loadTreeData()
checkedAll(searchData.value, false)

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1647393180960" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3075" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M522.854 33.884L902.47 164.732c14.1 4.85 23.54 18.113 23.54 33.011v402.218c-10.714 238.49-386.18 383.703-402.118 389.764A34.91 34.91 0 0 1 511.505 992c-4.217 0-8.439-0.767-12.387-2.275C483.147 983.664 107.648 838.452 97 601.502V197.743c0-14.898 9.442-28.16 23.503-33.011L500.121 33.884a35.24 35.24 0 0 1 22.733 0z m243.984 299.804c-29.29-29.29-76.777-29.29-106.066 0L459.246 535.213l-95.46-95.46c-29.289-29.289-76.776-29.289-106.065 0-29.29 29.29-29.29 76.777 0 106.067l148.492 148.492c14.645 14.645 33.839 21.967 53.033 21.967l1.152-0.009c18.808-0.287 37.53-7.606 51.881-21.958l254.559-254.558c29.289-29.29 29.289-76.777 0-106.066z" fill="#909399" p-id="3076"></path></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -56,7 +56,6 @@ import Sidebar from './sidebar/sidebar.vue'
import { logout } from '@/scripts/auth'
import { getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
console.log(proxy.$global.user.info.headPortrait)
</script>
<style scoped>
@ -117,4 +116,4 @@ console.log(proxy.$global.user.info.headPortrait)
height: 100%;
overflow: auto;
}
</style>
</style>

View File

@ -47,6 +47,8 @@ export function login(data){
var token = res.data
setToken(token)
resolve(token)
}).catch((e) => {
reject(e)
})
})
}

View File

@ -0,0 +1,132 @@
<template>
<mb-form ref="magicForm" v-bind="formOptions" />
<el-button @click="getFormData">获取表单数据</el-button>
</template>
<script setup>
import { ref, reactive, getCurrentInstance } from 'vue'
const { proxy } = getCurrentInstance()
const magicForm = ref()
const formOptions = reactive({
form: {
request: {
url: "user/save",
method: "post"
},
rows: [{
gutter: 24,
cols: [{
span: 12,
name: 'input',
label: 'input'
},{
span: 12,
name: 'switch',
component: 'switch',
label: 'switch',
props: {
activeValue: '1',
inactiveValue: '0'
}
},{
span: 12,
name: 'checkbox',
component: 'checkbox',
label: 'checkbox',
props: {
type: 'office_type'
}
},{
span: 12,
name: 'checkboxButton',
component: 'checkbox',
label: 'checkboxButton',
props: {
type: 'office_type',
button: true
}
},{
span: 12,
name: 'radio',
component: 'radio',
label: 'radio',
props: {
type: 'is_login'
}
},{
span: 12,
name: 'radioButton',
component: 'radio',
label: 'radioButton',
props: {
type: 'is_login',
button: true
}
},{
span: 12,
name: 'date',
component: 'date',
label: 'date',
props: {
type: 'date'
}
},{
span: 12,
name: 'datetime',
component: 'date',
label: 'datetime',
props: {
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss'
}
},{
span: 12,
name: 'daterange',
component: 'date',
label: 'daterange',
props: {
type: 'daterange'
}
},{
span: 12,
name: 'datetimerange',
component: 'date',
label: 'datetimerange',
props: {
type: 'datetimerange',
format: 'YYYY-MM-DD HH:mm:ss'
}
},{
component: 'treeselect',
span: 12,
name: 'treeselect',
label: 'treeselect',
rules: [{ required: true, message: '请选择组织机构', trigger: 'change' }],
props: {
url: 'user/offices'
}
}, {
component: 'select',
span: 12,
name: 'select',
label: 'select',
rules: [{ required: true, message: '请选择角色', trigger: 'change' }],
props: {
url: 'role/all',
placeholder: '请选择角色',
el: { multiple: true }
}
},{
span: 12,
name: 'head',
component: 'upload-image',
label: '头像',
rules: [{ required: true, message: '请选择头像', trigger: 'change' }]
}]
}]
}
})
function getFormData(){
console.log(magicForm.value.getFormData())
}
</script>

View File

@ -51,25 +51,25 @@ const listOptions = reactive({
cols: [
{
field: 'username',
title: '登录名称',
label: '登录名称',
sortable: 'custom'
}, {
field: 'name',
title: '姓名/昵称',
label: '姓名/昵称',
sortable: 'custom'
}, {
field: 'officeName',
title: '所属机构'
label: '所属机构'
}, {
field: 'roles',
title: '角色'
label: '角色'
}, {
field: 'phone',
title: '手机号',
label: '手机号',
sortable: 'custom'
}, {
field: 'isLogin',
title: '禁止登录',
label: '禁止登录',
type: 'switch',
width: 100,
change: (row) => {
@ -80,17 +80,17 @@ const listOptions = reactive({
}
}, {
field: 'createDate',
title: '创建时间',
label: '创建时间',
width: 180
}, {
title: '操作',
label: '操作',
type: 'btns',
width: 140,
fixed: 'right',
btns: [
{
permission: 'user:save',
title: '修改',
label: '修改',
type: 'text',
icon: 'ElEdit',
click: (row) => {
@ -101,7 +101,7 @@ const listOptions = reactive({
}
}, {
permission: 'user:delete',
title: '删除',
label: '删除',
type: 'text',
icon: 'ElDelete',
click: (row) => {
@ -136,6 +136,12 @@ const formOptions = reactive({
rows: [{
gutter: 24,
cols: [{
span: 12,
name: 'head',
component: 'upload-image',
label: '头像',
rules: [{ required: true, message: '请选择头像', trigger: 'change' }]
},{
span: 12,
name: 'username',
label: '登录名称',
@ -170,7 +176,6 @@ const formOptions = reactive({
span: 12,
name: 'officeId',
label: '组织机构',
defaultValue: null,
rules: [{ required: true, message: '请选择组织机构', trigger: 'change' }],
props: {
url: 'user/offices'
@ -180,7 +185,6 @@ const formOptions = reactive({
span: 12,
name: 'roles',
label: '选择角色',
defaultValue: null,
rules: [{ required: true, message: '请选择角色', trigger: 'change' }],
props: {
url: 'role/all',

View File

@ -12,6 +12,11 @@
<mb-icon icon="password"/>
<input class="magic-input" ref="password" v-model="loginForm.password" type="password" placeholder="密码" name="password" tabindex="2" auto-complete="on" @keyup.enter.native="handleLogin" />
</div>
<div class="magic-login-row">
<mb-icon icon="verification-code"/>
<input class="magic-input code" ref="code" v-model="loginForm.code" placeholder="验证码" name="code" tabindex="3" @keyup.enter.native="handleLogin" />
<img class="code-img" :src="codeImg" @click="refreshCode">
</div>
<div class="magic-login-row">
<button :loading="loading" class="magic-button" type="primary" size="medium" @click.native.prevent="handleLogin">登录</button>
</div>
@ -24,12 +29,20 @@
import { reactive, ref, getCurrentInstance } from 'vue'
import { login } from '@/scripts/auth'
const { proxy } = getCurrentInstance()
const codeImg = ref(import.meta.env.VITE_APP_BASE_API + '/security/verification/code')
const loginForm = reactive({
username: '',
password: ''
password: '',
code: ''
})
const loading = ref(false)
function refreshCode(){
proxy.$get('/security/verification/code').then(res => {
codeImg.value = 'data:image/png;base64,' + res.data.img
loginForm.uuid = res.data.uuid
})
}
refreshCode()
function handleLogin() {
if(!loginForm.username){
proxy.$message({
@ -47,12 +60,21 @@
showClose: true
})
return
}else if(!loginForm.code){
proxy.$message({
message: '请输入验证码',
type: 'error',
duration: 2000,
showClose: true
})
return
}else{
loading.value = true
login(loginForm).then((res) => {
proxy.$router.push({ path: '/home' })
loading.value = false
}).catch(() => {
refreshCode()
loading.value = false
})
}
@ -63,6 +85,15 @@
*{
box-sizing: border-box;
}
.code{
width: 283px;
float: left;
margin-right: 15px;
}
.code-img{
border: 1px solid #D9D9D9;
border-radius: 4px;
}
input{
width: 100%;
}

View File

@ -28,12 +28,26 @@
},
{
field: 'type',
label: '登录状态'
label: '登录状态',
templet: (row) => {
return row.type == '成功' ? '<b style="color: #409EFF">成功</b>' : '<b style="color: red">失败</b>'
}
},
{
field: 'ip',
label: 'ip'
},
{
field: 'browser',
label: '浏览器'
},
{
field: 'os',
label: '系统',
props: {
"show-overflow-tooltip": true
}
},
{
field: 'createDate',
label: '操作时间'

View File

@ -0,0 +1,113 @@
<template>
<mb-list ref="magicList" v-bind="listOptions" />
<!-- <mb-dialog ref="magicDialog" title="提示" width="600px" @confirm-click="disable">-->
<!-- <template #content>-->
<!-- <el-row :gutter="24">-->
<!-- <el-col :span="24">-->
<!-- 确定要踢{{currRow.username}}下线并临时封禁吗-->
<!-- </el-col>-->
<!-- <el-col :span="24">-->
<!-- <mb-radio v-model="time" :options="options"></mb-radio>-->
<!-- </el-col>-->
<!-- </el-row>-->
<!-- </template>-->
<!-- </mb-dialog>-->
</template>
<script setup>
import {reactive, ref, getCurrentInstance} from "vue";
const { proxy } = getCurrentInstance()
const magicList = ref()
// const magicDialog = ref()
// const currRow = ref()
// const time = ref(0)
// const options = reactive([{
// label: '',
// value: 0
// },{
// label: '1',
// value: 60
// },{
// label: '10',
// value: 10 * 60
// },{
// label: '1',
// value: 1 * 60 *60
// },{
// label: '5',
// value: 5 * 60 *60
// },{
// label: '',
// value: -1
// }])
const listOptions = reactive({
table: {
url: 'online/list',
where: {
username: {
label: '登录名称',
},
ip: {
label: 'IP',
}
},
cols: [
{
field: 'username',
label: '登录名称'
}, {
field: 'officeName',
label: '所属机构'
}, {
field: 'ip',
label: 'IP'
}, {
field: 'browser',
label: '浏览器'
}, {
field: 'os',
label: '操作系统',
props: {
"show-overflow-tooltip": true
}
}, {
field: 'createDate',
label: '登录时间'
}, {
label: '操作',
type: 'btns',
width: 140,
fixed: 'right',
btns: [
{
permission: 'online:logout',
label: '踢人',
type: 'text',
icon: 'ElBicycle',
click: (row) => {
// currRow.value = row
// magicDialog.value.show()
proxy.$alert(`确定要踢“${row.username}”下线吗?`, '提示', {
confirmButtonText: '确定',
callback: (action) => {
if (action === 'confirm') {
proxy.$get('online/logout',{ token: row.token }).then(() => {
magicList.value.reload()
})
}
}
})
}
}
]
}
]
}
})
// function disable(){
// proxy.$get('online/logout',{ id: currRow.value.id, time: time.value }).then(() => {
// magicDialog.value.hide()
// magicList.value.reload()
// })
// }
</script>

View File

@ -40,7 +40,10 @@
},
{
field: 'userAgent',
label: '用户代理'
label: '用户代理',
props: {
"show-overflow-tooltip": true
}
},
{
field: 'username',

View File

@ -196,7 +196,10 @@ const tableOptions = reactive({
type: 'text',
icon: 'ElUserFilled',
click: (row) => {
proxy.$router.push({ path: `/system/user/user-list?officeId=${row.id}` })
proxy.$router.push({
path: '/system/user/user-list',
query: { officeId: row.id }
})
}
}
]

View File

@ -13,11 +13,21 @@
<template>
<div class="app-container">
<div class="left">
<mb-tree url="office/tree" :el="{ 'expand-on-click-node': false,'show-checkbox': true }" :checked-ids="[tableOptions.where.officeId]" :expand="false" :search="true" search-width="100%" :checked="false" @check-change="checkChange" />
<mb-tree
url="office/tree"
:el="{ 'expand-on-click-node': false,'show-checkbox': true }"
:checked-ids="[tableOptions.where.officeId]"
:expand="false"
:search="true"
search-width="100%"
:checked="false"
@check-change="checkChange"
@mounted="treeMounted"
/>
</div>
<div class="right">
<mb-search :where="tableOptions.where" @search="reloadTable">
<mb-search :where="tableOptions.where" @search="reloadTable" @mounted="searchMounted">
<template #btns>
<el-button :loading="downloadLoading" class="filter-item" icon="ElDownload" @click="handleDownload">
导出
@ -71,13 +81,13 @@ const tableOptions = reactive({
roleId: {
type: 'select',
label: '角色',
value: proxy.$route.query.roleId,
value: '',
properties: {
url: 'role/all',
el: { multiple: true }
}
},
officeId: proxy.$route.query.officeId
officeId: ''
},
cols: [
{
@ -159,20 +169,17 @@ const userFormDialog = ref()
const table = ref()
const userForm = ref()
// onMounted(() => {
// setTimeout(function(){
// nextTick(() => {
// console.log(proxy.$route.query.roleId)
// if(proxy.$route.query.roleId){
// tableOptions.where.roleId.value = proxy.$route.query.roleId
// }
// if(proxy.$route.query.officeId){
// tableOptions.where.officeId = proxy.$route.query.officeId
// }
// })
function searchMounted(){
if(proxy.$route.query.roleId){
tableOptions.where.roleId.value = proxy.$route.query.roleId
}
}
// },1000)
// })
function treeMounted(){
if(proxy.$route.query.officeId){
tableOptions.where.officeId = proxy.$route.query.officeId
}
}
function checkChange(values) {
tableOptions.where.officeId = values

View File

@ -14,7 +14,7 @@
<description>magic-boot</description>
<properties>
<java.version>1.8</java.version>
<magic-api.version>2.0.0-beta.3</magic-api.version>
<magic-api.version>2.0.0</magic-api.version>
<druid.version>1.1.10</druid.version>
<hutool-all.version>5.7.13</hutool-all.version>
<sa-token.version>1.26.0</sa-token.version>

View File

@ -1,4 +1,4 @@
package org.ssssssss.magicboot.config;
package org.ssssssss.magicboot.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;

View File

@ -0,0 +1,22 @@
package org.ssssssss.magicboot.model;
import java.util.HashMap;
import java.util.Map;
public class CodeCacheMap {
private static Map<String, String> map = new HashMap<String, String>();
public static void put(String key, String value){
map.put(key, value);
}
public static void remove(String key){
map.remove(key);
}
public static String get(String key){
return map.get(key);
}
}

View File

@ -0,0 +1,25 @@
package org.ssssssss.magicboot.provider;
import cn.dev33.satoken.exception.DisableLoginException;
import org.springframework.stereotype.Component;
import org.ssssssss.magicapi.core.context.RequestEntity;
import org.ssssssss.magicapi.core.interceptor.ResultProvider;
import org.ssssssss.magicapi.core.model.JsonBean;
@Component
public class ExceptionResultProvider implements ResultProvider {
@Override
public Object buildResult(RequestEntity requestEntity, int code, String message, Object data) {
long timestamp = System.currentTimeMillis();
return new JsonBean<>(code, message, data, (int) (timestamp - requestEntity.getRequestTime()));
}
@Override
public Object buildException(RequestEntity requestEntity, Throwable throwable) {
if(throwable.getCause() instanceof DisableLoginException){
return buildResult(requestEntity, 500, "此账号已被临时封禁,请联系管理员");
}
return buildResult(requestEntity, 500, "系统内部出现错误");
}
}