459 lines
12 KiB
Vue
459 lines
12 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
deleteClient,
|
|
downloadClient, generateClientIP,
|
|
getClients,
|
|
saveClient
|
|
} from "@/api/clients";
|
|
import { h, reactive, ref } from "vue";
|
|
import { addDialog } from "@/components/ReDialog/index";
|
|
import qrCodeForms from "./component/qrCode.vue";
|
|
import editClientForms from "./component/detail.vue";
|
|
import { type PlusColumn, PlusSearch } from "plus-pro-components";
|
|
import "plus-pro-components/es/components/search/style/css";
|
|
import { storageLocal } from "@pureadmin/utils";
|
|
import { ArrowDown } from "@element-plus/icons-vue";
|
|
import { ElMessageBox } from "element-plus";
|
|
|
|
defineOptions({
|
|
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
|
name: "Clients"
|
|
});
|
|
|
|
const editClientFormRef = ref();
|
|
|
|
const clientSearchForm = ref({
|
|
name: "",
|
|
email: "",
|
|
ip: "",
|
|
createUser: "",
|
|
enabled: undefined,
|
|
current: 1,
|
|
size: 9
|
|
});
|
|
|
|
const clientSearchProps = {
|
|
gutter: 0
|
|
};
|
|
|
|
const clientSearchFormColumns: PlusColumn[] = [
|
|
{
|
|
label: "名称",
|
|
prop: "name",
|
|
valueType: "copy",
|
|
fieldProps: {
|
|
placeholder: "请输入"
|
|
}
|
|
},
|
|
{
|
|
label: "邮箱",
|
|
prop: "email",
|
|
valueType: "copy",
|
|
fieldProps: {
|
|
placeholder: "请输入"
|
|
}
|
|
},
|
|
{
|
|
label: "IP",
|
|
prop: "ip",
|
|
valueType: "copy",
|
|
fieldProps: {
|
|
placeholder: "请输入"
|
|
}
|
|
},
|
|
{
|
|
label: "创建人",
|
|
prop: "createUser",
|
|
valueType: "copy",
|
|
fieldProps: {
|
|
placeholder: "请输入"
|
|
}
|
|
},
|
|
{
|
|
label: "状态",
|
|
prop: "enabled",
|
|
valueType: "select",
|
|
options: [
|
|
{
|
|
label: "启用",
|
|
value: 1
|
|
},
|
|
{
|
|
label: "禁用",
|
|
value: 0
|
|
}
|
|
],
|
|
fieldProps: {
|
|
placeholder: "请选择"
|
|
}
|
|
}
|
|
];
|
|
|
|
const clientsList = reactive({
|
|
data: [],
|
|
total: 0
|
|
});
|
|
|
|
const getClientsApi = (data?: object) => {
|
|
getClients(data).then(clients => {
|
|
if (clients.code === 200) {
|
|
clientsList.data = clients.data.records;
|
|
clientsList.total = clients.data.total;
|
|
}
|
|
});
|
|
};
|
|
|
|
// 下载客户端配置文件
|
|
const downloadClientConfig = (id: string, clientName: string) => {
|
|
downloadClient(id).then(response => {
|
|
if (response) {
|
|
const blob = new Blob([response], {
|
|
type: "text/plain"
|
|
});
|
|
const link = document.createElement("a"); // 创建a标签
|
|
link.download = clientName + ".conf"; // a标签添加属性
|
|
link.style.display = "none";
|
|
link.href = URL.createObjectURL(blob);
|
|
document.body.appendChild(link);
|
|
link.click(); // 执行下载
|
|
URL.revokeObjectURL(link.href); // 释放url
|
|
document.body.removeChild(link); // 释放标签
|
|
}
|
|
});
|
|
};
|
|
|
|
// 打开二维码模态框
|
|
const openQrCodeDialog = (clientName: string, id: string) => {
|
|
addDialog({
|
|
width: "20%",
|
|
title: clientName,
|
|
contentRenderer: () => h(qrCodeForms),
|
|
props: {
|
|
formInline: {
|
|
id: id
|
|
}
|
|
},
|
|
hideFooter: true
|
|
});
|
|
};
|
|
|
|
// 打开新增客户端弹窗
|
|
const openAddClientDialog = () => {
|
|
let clientIP = ref([]);
|
|
let serverIP = ref([]);
|
|
// 先 生成客户端IP
|
|
generateClientIP({
|
|
rule: "AUTO"
|
|
}).then(res => {
|
|
if (res.code === 200) {
|
|
clientIP.value = res.data.clientIP;
|
|
serverIP.value = res.data.serverIP;
|
|
}
|
|
});
|
|
const serverInfo = storageLocal().getItem("server-info");
|
|
const restartRule = storageLocal().getItem("restart-rule") ? 1 : 0;
|
|
addDialog({
|
|
width: "40%",
|
|
title: "新增",
|
|
contentRenderer: () => h(editClientForms, { ref: editClientFormRef }),
|
|
props: {
|
|
formInline: {
|
|
id: "",
|
|
serverId: serverInfo.id,
|
|
name: "",
|
|
email: "",
|
|
subnetRange: "",
|
|
ipAllocation: clientIP,
|
|
allowedIPS: serverIP,
|
|
extraAllowedIPS: "",
|
|
endpoint: "",
|
|
useServerDNS: 0,
|
|
enableAfterCreation: restartRule,
|
|
keys: {
|
|
privateKey: "",
|
|
publicKey: "",
|
|
presharedKey: ""
|
|
},
|
|
enabled: 1
|
|
}
|
|
},
|
|
beforeSure: (done, { options }) => {
|
|
const FormRef = editClientFormRef.value.getDetailFormRef();
|
|
FormRef.validate(valid => {
|
|
if (!valid) return;
|
|
saveClient(options.props.formInline).then(res => {
|
|
if (res.code === 200) {
|
|
done();
|
|
getClientsApi(clientSearchForm.value);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
// 打开编辑客户端信息模态框
|
|
const openEditClientDialog = (client?: any) => {
|
|
const serverInfo = storageLocal().getItem("server-info");
|
|
addDialog({
|
|
width: "40%",
|
|
title: client.name,
|
|
contentRenderer: () => h(editClientForms, { ref: editClientFormRef }),
|
|
props: {
|
|
formInline: {
|
|
id: client.id,
|
|
serverId: serverInfo.id,
|
|
name: client.name,
|
|
email: client.email,
|
|
subnetRange: client.subnetRange,
|
|
ipAllocation: client.ipAllocation,
|
|
allowedIPS: client.allowedIPS,
|
|
extraAllowedIPS: client.extraAllowedIPS,
|
|
endpoint: client.endpoint,
|
|
useServerDNS: client.useServerDNS,
|
|
enableAfterCreation: client.enableAfterCreation,
|
|
keys: client.keys,
|
|
enabled: Number(client.enabled)
|
|
}
|
|
},
|
|
beforeSure: (done, { options }) => {
|
|
const FormRef = editClientFormRef.value.getDetailFormRef();
|
|
FormRef.validate(valid => {
|
|
if (!valid) return;
|
|
saveClient(options.props.formInline).then(res => {
|
|
if (res.code === 200) {
|
|
done();
|
|
getClientsApi(clientSearchForm.value);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
// 打开删除弹窗
|
|
const openDeleteMessageBox = (clientName: string, clientId: string) => {
|
|
ElMessageBox.confirm("是否删除:" + clientName, "删除", {
|
|
distinguishCancelAndClose: true,
|
|
confirmButtonText: "确认",
|
|
cancelButtonText: "取消",
|
|
type: "warning",
|
|
center: true
|
|
}).then(() => {
|
|
deleteClient(clientId).then(res => {
|
|
if (res.code === 200) {
|
|
getClientsApi(clientSearchForm.value);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
// 搜索
|
|
const searchHandler = (value: object) => {
|
|
getClientsApi(value);
|
|
};
|
|
|
|
// 重置搜索表单
|
|
const resetSearchHandler = (value: object) => {
|
|
getClientsApi({
|
|
current: 1,
|
|
size: 9
|
|
});
|
|
clientSearchForm.value.current = 1;
|
|
clientSearchForm.value.size = 9;
|
|
};
|
|
|
|
// 定义分页相关事件
|
|
const pageChange = (page: number, size: number) => {
|
|
clientSearchForm.value.size = size;
|
|
clientSearchForm.value.current = page;
|
|
getClientsApi(clientSearchForm.value);
|
|
};
|
|
|
|
// 超长省略号显示
|
|
const ellipsis = (str: string) => {
|
|
if (!str) return "";
|
|
if (str.length >= 10) {
|
|
return str.slice(0, 10) + "...";
|
|
}
|
|
return str;
|
|
};
|
|
|
|
getClientsApi(clientSearchForm.value);
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div class="search-header" style="padding-bottom: 5px">
|
|
<el-card>
|
|
<PlusSearch
|
|
v-model="clientSearchForm"
|
|
resetText="重置"
|
|
searchText="搜索"
|
|
:hasUnfold="false"
|
|
:columns="clientSearchFormColumns"
|
|
:rowProps="clientSearchProps"
|
|
label-width="80"
|
|
label-position="right"
|
|
@search="searchHandler"
|
|
@reset="resetSearchHandler"
|
|
/>
|
|
</el-card>
|
|
<div style="margin-top: 5px">
|
|
<el-card>
|
|
<el-button type="primary" @click="openAddClientDialog"
|
|
>新增客户端</el-button
|
|
>
|
|
</el-card>
|
|
</div>
|
|
</div>
|
|
<div class="content">
|
|
<el-card body-style="padding: inherit" shadow="hover">
|
|
<div class="flex flex-wrap gap-4" style="display: flex;justify-content: center;">
|
|
<el-card
|
|
v-for="val in clientsList.data"
|
|
style="float: left; width: 500px"
|
|
shadow="hover"
|
|
>
|
|
<template #header>
|
|
<div class="card-header">
|
|
<el-tooltip :content="val.name" placement="top">
|
|
<el-tag size="large">
|
|
{{ ellipsis(val.name) }}
|
|
</el-tag>
|
|
</el-tooltip>
|
|
<el-dropdown
|
|
trigger="click"
|
|
type="primary"
|
|
split-button
|
|
style="float: right"
|
|
>
|
|
更多
|
|
<template #dropdown>
|
|
<el-dropdown-menu>
|
|
<el-dropdown-item>
|
|
<el-button
|
|
type="warning"
|
|
@click="downloadClientConfig(val.id, val.name)"
|
|
>下载</el-button
|
|
>
|
|
</el-dropdown-item>
|
|
<el-dropdown-item>
|
|
<el-button
|
|
type="danger"
|
|
@click="openDeleteMessageBox(val.name, val.id)"
|
|
>删除</el-button
|
|
>
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</template>
|
|
</el-dropdown>
|
|
<el-button
|
|
type="success"
|
|
style="float: right; margin-right: 5px"
|
|
@click="openQrCodeDialog(val.name, val.id)"
|
|
>二维码</el-button
|
|
>
|
|
<el-button
|
|
type="primary"
|
|
style="float: right; margin-right: 5px"
|
|
@click="openEditClientDialog(val)"
|
|
>
|
|
详情
|
|
</el-button>
|
|
</div>
|
|
</template>
|
|
<el-form label-position="top">
|
|
<el-form-item prop="name" label="名称">
|
|
<el-input v-model="val.name" />
|
|
</el-form-item>
|
|
<el-form-item prop="email" label="邮箱">
|
|
<el-input v-model="val.email" />
|
|
</el-form-item>
|
|
<el-form-item prop="ipAllocation" label="客户端IP">
|
|
<el-select
|
|
v-model="val.ipAllocation"
|
|
:clearable="true"
|
|
:reserve-keyword="false"
|
|
suffix-icon=""
|
|
tag-type="primary"
|
|
popper-class="options-class"
|
|
placeholder=""
|
|
multiple
|
|
filterable
|
|
allow-create
|
|
default-first-option
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item prop="allowedIPS" label="允许的IP范围">
|
|
<el-select
|
|
v-model="val.allowedIPS"
|
|
:clearable="true"
|
|
:reserve-keyword="false"
|
|
suffix-icon=""
|
|
tag-type="danger"
|
|
popper-class="options-class"
|
|
placeholder=""
|
|
multiple
|
|
filterable
|
|
allow-create
|
|
default-first-option
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="创建人">
|
|
<el-tag effect="dark" type="primary">{{
|
|
val.createUser
|
|
}}</el-tag>
|
|
</el-form-item>
|
|
<el-form-item label="客户端状态">
|
|
<el-tag v-if="val.enabled" effect="dark" type="success"
|
|
>启用</el-tag
|
|
>
|
|
<el-tag v-else effect="dark" type="warning">禁用</el-tag>
|
|
</el-form-item>
|
|
<el-form-item class="timeItem">
|
|
<p>创建时间: {{ val.createdAt }}</p>
|
|
<p>更新时间: {{ val.updatedAt }}</p>
|
|
</el-form-item>
|
|
</el-form>
|
|
</el-card>
|
|
</div>
|
|
<div class="paginate" style="background-color: #ffffff; margin-top: 5px">
|
|
<el-card>
|
|
<el-pagination
|
|
small
|
|
background
|
|
layout="total,prev,pager,next"
|
|
:page-size="clientSearchForm.size"
|
|
:total="clientsList.total"
|
|
@change="pageChange"
|
|
/>
|
|
</el-card>
|
|
</div>
|
|
</el-card>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
.timeItem {
|
|
.el-form-item__content {
|
|
display: block;
|
|
}
|
|
}
|
|
.options-class {
|
|
display: none;
|
|
}
|
|
</style>
|
|
<style scoped>
|
|
.example-showcase .el-dropdown + .el-dropdown {
|
|
margin-left: 15px;
|
|
}
|
|
.example-showcase .el-dropdown-link {
|
|
cursor: pointer;
|
|
color: var(--el-color-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
</style>
|