完成子账户前端操作

This commit is contained in:
柏码の讲师 2023-12-13 17:48:28 +08:00
parent fa630a1d89
commit 497b8b8b04
6 changed files with 251 additions and 4 deletions

View File

@ -6,6 +6,7 @@ import com.example.entity.vo.request.RenameNodeVO;
import com.example.entity.vo.request.RuntimeDetailVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO;
import com.example.entity.vo.response.ClientSimpleVO;
import com.example.entity.vo.response.RuntimeHistoryVO;
import com.example.service.ClientService;
import jakarta.annotation.Resource;
@ -26,6 +27,11 @@ public class MonitorController {
return RestBean.success(service.listClients());
}
@GetMapping("/simple-list")
public RestBean<List<ClientSimpleVO>> simpleClientList() {
return RestBean.success(service.listSimpleList());
}
@PostMapping("/rename")
public RestBean<Void> renameClient(@RequestBody @Valid RenameClientVO vo) {
service.renameClient(vo);

View File

@ -0,0 +1,13 @@
package com.example.entity.vo.response;
import lombok.Data;
@Data
public class ClientSimpleVO {
int id;
String name;
String location;
String osName;
String osVersion;
String ip;
}

View File

@ -8,6 +8,7 @@ import com.example.entity.vo.request.RenameNodeVO;
import com.example.entity.vo.request.RuntimeDetailVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO;
import com.example.entity.vo.response.ClientSimpleVO;
import com.example.entity.vo.response.RuntimeHistoryVO;
import java.util.List;
@ -20,6 +21,7 @@ public interface ClientService extends IService<Client> {
void updateClientDetail(ClientDetailVO vo, Client client);
void updateRuntimeDetail(RuntimeDetailVO vo, Client client);
List<ClientPreviewVO> listClients();
List<ClientSimpleVO> listSimpleList();
void renameClient(RenameClientVO vo);
void renameNode(RenameNodeVO vo);
ClientDetailsVO clientDetails(int clientId);

View File

@ -10,6 +10,7 @@ import com.example.entity.vo.request.RenameNodeVO;
import com.example.entity.vo.request.RuntimeDetailVO;
import com.example.entity.vo.response.ClientDetailsVO;
import com.example.entity.vo.response.ClientPreviewVO;
import com.example.entity.vo.response.ClientSimpleVO;
import com.example.entity.vo.response.RuntimeHistoryVO;
import com.example.mapper.ClientDetailMapper;
import com.example.mapper.ClientMapper;
@ -108,6 +109,15 @@ public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> impleme
}).toList();
}
@Override
public List<ClientSimpleVO> listSimpleList() {
return clientIdCache.values().stream().map(client -> {
ClientSimpleVO vo = client.asViewObject(ClientSimpleVO.class);
BeanUtils.copyProperties(detailMapper.selectById(vo.getId()), vo);
return vo;
}).toList();
}
@Override
public void renameClient(RenameClientVO vo) {
this.update(Wrappers.<Client>update().eq("id", vo.getId()).set("name", vo.getName()));

View File

@ -0,0 +1,154 @@
<script setup>
import {reactive, ref} from "vue";
import {Lock, Message, User} from "@element-plus/icons-vue";
import {osNameToIcon} from "@/tools";
import {ElMessage} from "element-plus";
import {post} from "@/net";
defineProps({
clients: Array
})
const emits = defineEmits(['create'])
const form = reactive({
username: '',
email: '',
password: '',
})
const formRef = ref()
const valid = ref(false)
const onValidate = (prop, isValid) => valid.value = isValid
const validateUsername = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入用户名'))
} else if(!/^[a-zA-Z0-9\u4e00-\u9fa5]+$/.test(value)){
callback(new Error('用户名不能包含特殊字符,只能是中文/英文'))
} else {
callback()
}
}
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: ['blur', 'change'] },
{ validator: validateUsername, trigger: ['blur', 'change'] },
{ min: 2, max: 8, message: '用户名的长度必须在2-8个字符之间', trigger: ['blur', 'change'] },
],
password: [
{ required: true, message: '请输入密码', trigger: ['blur', 'change'] },
{ min: 6, max: 16, message: '密码的长度必须在6-16个字符之间', trigger: ['blur', 'change'] }
], email: [
{ required: true, message: '请输入邮件地址', trigger: ['blur', 'change'] },
{type: 'email', message: '请输入合法的电子邮件地址', trigger: ['blur', 'change']}
]
}
const checkedClients = []
const onCheck = (state, id) => {
if(state) {
checkedClients.push(id)
} else {
const index = checkedClients.indexOf(id);
checkedClients.splice(index, 1)
}
}
function createSubAccount() {
formRef.value.validate(isValid => {
if(checkedClients.length === 0) {
ElMessage.warning('请至少选择一个服务器用于子账户进行管理')
return
}
if(isValid) {
post('/api/user/sub/create', {
...form,
clients: checkedClients
}, () => {
ElMessage.success('子账户创建成功!')
emits('create')
})
}
})
}
</script>
<template>
<div style="padding: 15px 20px;height: 100%">
<div style="display: flex;flex-direction: column;height: 100%">
<div>
<div class="title">
<i class="fa-solid fa-user-plus"></i> 添加新的子账户
</div>
<div class="desc">子账户同样用于管理服务器但是可以自由分配指定的服务器子账户只能访问被分配到的服务器</div>
<el-divider style="margin: 10px 0"/>
</div>
<div>
<el-form label-position="top" :rules="rules" :model="form"
@validate="onValidate" ref="formRef">
<el-form-item label="用户名" prop="username">
<el-input type="text" v-model="form.username"
:prefix-icon="User" placeholder="子账户用户名" maxlength="16"/>
</el-form-item>
<el-form-item label="电子邮件" prop="email">
<el-input type="email" v-model="form.email"
:prefix-icon="Message" placeholder="子账户电子邮件" maxlength="16"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="form.password"
:prefix-icon="Lock" placeholder="子账户密码" maxlength="16"/>
</el-form-item>
</el-form>
<el-divider style="margin: 10px 0"/>
<div class="desc">请在下方选择允许子账户访问的服务器列表</div>
</div>
<el-scrollbar style="flex: 1">
<div class="client-card" v-for="item in clients">
<el-checkbox @change="state => onCheck(state, item.id)"/>
<div style="margin-left: 20px">
<div style="font-size: 14px;font-weight: bold">
<span :class="`flag-icon flag-icon-${item.location}`"></span>
<span style="margin: 0 10px">{{ item.name }}</span>
</div>
<div style="font-size: 12px;color: grey">
操作系统:
<i :style="{color: osNameToIcon(item.osName).color}"
:class="`fa-brands ${osNameToIcon(item.osName).icon}`"></i>
{{`${item.osName} ${item.osVersion}`}}
</div>
<div style="font-size: 12px;color: grey">
<span style="margin-right: 10px">公网IP: {{item.ip}}</span>
</div>
</div>
</div>
</el-scrollbar>
<div style="text-align: center;margin-top: 10px">
<el-button @click="createSubAccount" type="success"
:disabled="!valid" plain>确认创建</el-button>
</div>
</div>
</div>
</template>
<style scoped>
.title {
font-size: 18px;
font-weight: bold;
color: dodgerblue;
}
.desc {
font-size: 13px;
color: grey;
line-height: 16px;
}
.client-card {
border-radius: 5px;
background-color: var(--el-bg-color-page);
padding: 10px;
display: flex;
align-items: center;
margin: 10px;
}
</style>

View File

@ -1,9 +1,10 @@
<script setup>
import {reactive, ref} from "vue";
import {Lock, Plus, Switch} from "@element-plus/icons-vue";
import {logout, post} from "@/net";
import {get, logout, post} from "@/net";
import {ElMessage} from "element-plus";
import router from "@/router";
import CreateSubAccount from "@/component/CreateSubAccount.vue";
const formRef = ref()
const valid = ref(false)
@ -15,7 +16,6 @@ const form = reactive({
new_password_repeat: '',
})
const validatePassword = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'))
@ -50,6 +50,25 @@ function resetPassword() {
}
})
}
const simpleList = ref([])
get('/api/monitor/simple-list', list => {
simpleList.value = list
initSubAccounts()
})
const accounts = ref([])
const initSubAccounts = () =>
get('/api/user/sub/list', list => accounts.value = list)
const createAccount = ref(false)
function deleteAccount(id) {
get(`/api/user/sub/delete?uid=${id}`, () => {
ElMessage.success('子账户删除成功')
initSubAccounts()
})
}
</script>
<template>
@ -85,10 +104,33 @@ function resetPassword() {
<div class="info-card" style="flex: 50%">
<div class="title"><i class="fa-solid fa-users"></i> 子用户管理</div>
<el-divider style="margin: 10px 0"/>
<el-empty :image-size="100" description="还没有任何子用户哦">
<el-button :icon="Plus" type="primary" plain>添加子用户</el-button>
<div v-if="accounts.length" style="text-align: center">
<div v-for="item in accounts" class="account-card">
<el-avatar class="avatar" :size="30"
src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
<div style="margin-left: 15px;line-height: 18px;flex: 1">
<div>
<span>{{item.username}}</span>
<span style="font-size: 13px;color: grey;margin-left: 5px">
管理 {{item.clientList.length}} 个服务器
</span>
</div>
<div style="font-size: 13px;color: grey">{{item.email}}</div>
</div>
<el-button type="danger" :icon="Delete"
@click="deleteAccount(item.id)" text>删除子账户</el-button>
</div>
<el-button :icon="Plus" type="primary"
@click="createAccount = true" plain>添加更多子用户</el-button>
</div>
<el-empty :image-size="100" description="还没有任何子用户哦" v-else>
<el-button :icon="Plus" type="primary" plain
@click="createAccount = true">添加子用户</el-button>
</el-empty>
</div>
<el-drawer v-model="createAccount" size="350" :with-header="false">
<create-sub-account :clients="simpleList" @create="createAccount = false;initSubAccounts()"/>
</el-drawer>
</div>
</template>
@ -105,4 +147,24 @@ function resetPassword() {
color: dodgerblue;
}
}
.account-card {
border-radius: 5px;
background-color: var(--el-bg-color-page);
padding: 10px;
display: flex;
align-items: center;
text-align: left;
margin: 10px 0;
}
:deep(.el-drawer) {
margin: 10px;
height: calc(100% - 20px);
border-radius: 10px;
}
:deep(.el-drawer__body) {
padding: 0;
}
</style>