🎨基本功能完善
This commit is contained in:
parent
6a37c8d902
commit
66982f3a7b
@ -47,6 +47,7 @@
|
||||
"url": "https://github.com/xiaoxian521"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@pureadmin/descriptions": "^1.2.1",
|
||||
"@pureadmin/table": "^3.1.2",
|
||||
"@pureadmin/utils": "^2.4.7",
|
||||
@ -64,6 +65,7 @@
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.20.4",
|
||||
"plus-pro-components": "^0.1.4",
|
||||
"qs": "^6.12.1",
|
||||
"responsive-storage": "^2.2.0",
|
||||
"sortablejs": "^1.15.2",
|
||||
|
@ -5,6 +5,9 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.1
|
||||
version: 2.3.1(vue@3.4.27)
|
||||
'@pureadmin/descriptions':
|
||||
specifier: ^1.2.1
|
||||
version: 1.2.1(echarts@5.5.0)(element-plus@2.7.1)(typescript@5.4.5)
|
||||
@ -56,6 +59,9 @@ dependencies:
|
||||
pinyin-pro:
|
||||
specifier: ^3.20.4
|
||||
version: 3.20.4
|
||||
plus-pro-components:
|
||||
specifier: ^0.1.4
|
||||
version: 0.1.4(element-plus@2.7.1)(vue@3.4.27)
|
||||
qs:
|
||||
specifier: ^6.12.1
|
||||
version: 6.12.1
|
||||
@ -4623,6 +4629,19 @@ packages:
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/plus-pro-components@0.1.4(element-plus@2.7.1)(vue@3.4.27):
|
||||
resolution: {integrity: sha512-lglcrqYQqYM20GgxN+Hg9te/DfNHeERTBSP3EKzWAw6mSl3TU2LYVJIvPgzxAFtoRcg8f9Ybv9vVgtFHSH3JVg==}
|
||||
peerDependencies:
|
||||
element-plus: ^2.3.4
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@element-plus/icons-vue': 2.3.1(vue@3.4.27)
|
||||
element-plus: 2.7.1(vue@3.4.27)
|
||||
lodash-es: 4.17.21
|
||||
sortablejs: 1.15.2
|
||||
vue: 3.4.27(typescript@5.4.5)
|
||||
dev: false
|
||||
|
||||
/popmotion@11.0.5:
|
||||
resolution: {integrity: sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==}
|
||||
dependencies:
|
||||
|
29
src/api/clients.ts
Normal file
29
src/api/clients.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { baseUri } from "@/api/utils";
|
||||
|
||||
// 获取服务端信息
|
||||
export const getClients = (params?: object) => {
|
||||
return http.request<any>("get", baseUri("/client/list"), { params });
|
||||
};
|
||||
|
||||
// 下载客户端配置文件
|
||||
export const downloadClient = (id: string) => {
|
||||
return http.request<any>("post", baseUri("/client/download/" + id), null, {
|
||||
responseType: "arraybuffer"
|
||||
});
|
||||
};
|
||||
|
||||
// 获取客户端配置二维码
|
||||
export const clientQrCode = (id: string) => {
|
||||
return http.request<any>("post", baseUri("/client/generate-qrcode/" + id));
|
||||
};
|
||||
|
||||
// 新增/更新客户端信息
|
||||
export const saveClient = (data?: object) => {
|
||||
return http.request<any>("post", baseUri("/client/save"), { data });
|
||||
};
|
||||
|
||||
// 删除客户端
|
||||
export const deleteClient = (id: string) => {
|
||||
return http.request<any>("delete", baseUri("/client/" + id));
|
||||
};
|
27
src/api/server.ts
Normal file
27
src/api/server.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { http } from "@/utils/http";
|
||||
import { baseUri } from "@/api/utils";
|
||||
|
||||
// 获取服务端信息
|
||||
export const getServer = () => {
|
||||
return http.request<any>("get", baseUri("/server"));
|
||||
};
|
||||
|
||||
// 更新服务端信息
|
||||
export const updateServer = (data?: object) => {
|
||||
return http.request<any>("post", baseUri("/server"), { data });
|
||||
};
|
||||
|
||||
// 获取全局配置
|
||||
export const getGlobalConfig = () => {
|
||||
return http.request<any>("get", baseUri("/setting/server"));
|
||||
};
|
||||
|
||||
// 更新全局配置
|
||||
export const updateGlobalSetting = (data?: object) => {
|
||||
return http.request<any>("post", baseUri("/setting/server-global"), { data });
|
||||
};
|
||||
|
||||
// 获取当前主机的公网IP
|
||||
export const getPublicIP = () => {
|
||||
return http.request<any>("get", baseUri("/setting/public-ip"));
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
// 这里存放本地图标,在 src/layout/list.vue 文件中加载,避免在首启动加载
|
||||
// 这里存放本地图标,在 src/layout/server.vue 文件中加载,避免在首启动加载
|
||||
import { addIcon } from "@iconify/vue/dist/offline";
|
||||
|
||||
// 本地菜单图标,后端在路由的 icon 中返回对应的图标字符串并且前端在此处使用 addIcon 添加即可渲染菜单图标
|
||||
|
@ -5,20 +5,20 @@ export default {
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "/server/index",
|
||||
name: "Server",
|
||||
component: () => import("@/views/server/list.vue"),
|
||||
path: "/server/config",
|
||||
name: "Clients",
|
||||
component: () => import("@/views/server/clients.vue"),
|
||||
meta: {
|
||||
title: "服务端列表",
|
||||
title: "客户端",
|
||||
showParent: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: "/server/config",
|
||||
name: "ServerConfig",
|
||||
component: () => import("@/views/server/config.vue"),
|
||||
path: "/server/index",
|
||||
name: "Server",
|
||||
component: () => import("@/views/server/server.vue"),
|
||||
meta: {
|
||||
title: "服务端配置",
|
||||
title: "服务端",
|
||||
showParent: true
|
||||
}
|
||||
}
|
||||
|
83
src/store/modules/globalSetting.ts
Normal file
83
src/store/modules/globalSetting.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { globalSettingType } from "@/store/types";
|
||||
import { store } from "@/store";
|
||||
import {getGlobalConfig, updateGlobalSetting, updateServer} from "@/api/server";
|
||||
import {state} from "vue-tsc/out/shared";
|
||||
|
||||
export const useGlobalSettingStore = defineStore({
|
||||
id: "pure-globalSetting",
|
||||
state: (): globalSettingType => ({
|
||||
endpointAddress: "",
|
||||
dnsServer: [],
|
||||
MTU: 0,
|
||||
persistentKeepalive: 0,
|
||||
firewallMark: "",
|
||||
table: "",
|
||||
configFilePath: ""
|
||||
}),
|
||||
getters: {
|
||||
getGlobalSetting(state) {
|
||||
return state;
|
||||
},
|
||||
getEndpointAddress(state) {
|
||||
return state.endpointAddress;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
SET_ENDPOINT_ADDRESS(endpointAddress: string) {
|
||||
this.endpointAddress = endpointAddress;
|
||||
},
|
||||
SET_DNS_SERVER(dnsServer: []) {
|
||||
this.dnsServer = dnsServer;
|
||||
},
|
||||
SET_MTU(MTU: number) {
|
||||
this.MTU = MTU;
|
||||
},
|
||||
SET_PERSISTENT_KEEPALIVE(persistentKeepalive: string) {
|
||||
this.persistentKeepalive = persistentKeepalive;
|
||||
},
|
||||
SET_FIREWALL_MARK(firewallMark: string) {
|
||||
this.firewallMark = firewallMark;
|
||||
},
|
||||
SET_TABLE(table: number) {
|
||||
this.table = table;
|
||||
},
|
||||
SET_CONFIG_FILE_PATH(configFilePath: number) {
|
||||
this.configFilePath = configFilePath;
|
||||
},
|
||||
async getGlobalSettingApi() {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
getGlobalConfig()
|
||||
.then(data => {
|
||||
if (data.code === 200) {
|
||||
this.SET_ENDPOINT_ADDRESS(data.data.endpointAddress);
|
||||
this.SET_DNS_SERVER(data.data.dnsServer);
|
||||
this.SET_MTU(data.data.MTU);
|
||||
this.SET_PERSISTENT_KEEPALIVE(data.data.persistentKeepalive);
|
||||
this.SET_FIREWALL_MARK(data.data.firewallMark);
|
||||
this.SET_TABLE(data.data.table);
|
||||
this.SET_CONFIG_FILE_PATH(data.data.configFilePath);
|
||||
}
|
||||
resolve(data);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
async updateGlobalSettingApi(data?: object) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
updateGlobalSetting(data)
|
||||
.then(res => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export function useGlobalSettingStoreHook() {
|
||||
return useGlobalSettingStore(store);
|
||||
}
|
86
src/store/modules/server.ts
Normal file
86
src/store/modules/server.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type { serverType } from "@/store/types";
|
||||
import { store } from "@/store";
|
||||
import {getServer, updateServer} from "@/api/server";
|
||||
import {storageLocal} from "@pureadmin/utils";
|
||||
|
||||
export const useServerStore = defineStore({
|
||||
id: "pure-server",
|
||||
state: (): serverType => ({
|
||||
id: "",
|
||||
ipScope: "",
|
||||
listenPort: 0,
|
||||
privateKey: "",
|
||||
publicKey: "",
|
||||
postUpScript: "",
|
||||
preDownScript: "",
|
||||
postDownScript: ""
|
||||
}),
|
||||
getters: {
|
||||
getServerInfo(state) {
|
||||
return state;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
SET_ID(id: string) {
|
||||
this.id = id;
|
||||
},
|
||||
SET_IP_SCOPE(ipScope: string) {
|
||||
this.ipScope = ipScope;
|
||||
},
|
||||
SET_LISTEN_PORT(listenPort: number) {
|
||||
this.listenPort = listenPort;
|
||||
},
|
||||
SET_PRIVATE_KEY(privateKey: string) {
|
||||
this.privateKey = privateKey;
|
||||
},
|
||||
SET_PUBLIC_KEY(publicKey: string) {
|
||||
this.publicKey = publicKey;
|
||||
},
|
||||
SET_POST_UP_SCRIPT(postUpScript: number) {
|
||||
this.postUpScript = postUpScript;
|
||||
},
|
||||
SET_PRE_UP_SCRIPT(preDownScript: number) {
|
||||
this.preDownScript = preDownScript;
|
||||
},
|
||||
SET_POST_DOWN_SCRIPT(postDownScript: number) {
|
||||
this.postDownScript = postDownScript;
|
||||
},
|
||||
async getServerApi() {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
getServer()
|
||||
.then(data => {
|
||||
if (data.code === 200) {
|
||||
this.SET_ID(data.data.id);
|
||||
this.SET_IP_SCOPE(data.data.ipScope);
|
||||
this.SET_LISTEN_PORT(data.data.listenPort);
|
||||
this.SET_PRIVATE_KEY(data.data.privateKey);
|
||||
this.SET_PUBLIC_KEY(data.data.publicKey);
|
||||
this.SET_POST_UP_SCRIPT(data.data.postUpScript);
|
||||
this.SET_PRE_UP_SCRIPT(data.data.preDownScript);
|
||||
this.SET_POST_DOWN_SCRIPT(data.data.postDownScript);
|
||||
}
|
||||
storageLocal().setItem("server-info",data.data);
|
||||
resolve(data);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
async updateServerApi(data?: object) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
updateServer(data)
|
||||
.then(res => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
export function useServerStoreHook() {
|
||||
return useServerStore(store);
|
||||
}
|
@ -39,7 +39,7 @@ export const useUserStore = defineStore({
|
||||
this.status = status;
|
||||
},
|
||||
/** 登入 */
|
||||
async loginByUsername(data: any) {
|
||||
async loginByUsername(data?: any) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
login(data)
|
||||
.then(data => {
|
||||
|
@ -45,3 +45,24 @@ export type userType = {
|
||||
isAdmin?: number;
|
||||
status?: number;
|
||||
};
|
||||
|
||||
export type serverType = {
|
||||
id?: string;
|
||||
ipScope?: string;
|
||||
listenPort?: number;
|
||||
privateKey?: string;
|
||||
publicKey?: string;
|
||||
postUpScript?: string;
|
||||
preDownScript?: string;
|
||||
postDownScript?: string;
|
||||
};
|
||||
|
||||
export type globalSettingType = {
|
||||
endpointAddress?: string;
|
||||
dnsServer?: [];
|
||||
MTU?: number;
|
||||
persistentKeepalive?: number;
|
||||
firewallMark?: string;
|
||||
table?: string;
|
||||
configFilePath?: string;
|
||||
};
|
||||
|
@ -11,8 +11,11 @@ import type {
|
||||
} from "./types.d";
|
||||
import { stringify } from "qs";
|
||||
import NProgress from "../progress";
|
||||
import { getToken, formatToken } from "@/utils/auth";
|
||||
import { getToken, formatToken, TokenKey, multipleTabsKey } from "@/utils/auth";
|
||||
import { message } from "@/utils/message";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import Cookies from "js-cookie";
|
||||
import router from "@/router";
|
||||
|
||||
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
||||
const defaultConfig: AxiosRequestConfig = {
|
||||
@ -82,6 +85,16 @@ class PureHttp {
|
||||
const now = new Date().getTime() / 1000;
|
||||
const expired = parseInt(data.expireAt) - now <= 0;
|
||||
if (expired) {
|
||||
message("登陆已过期", { type: "error" });
|
||||
router.replace({
|
||||
path: "/login",
|
||||
query: {
|
||||
redirect: router.currentRoute.value.fullPath
|
||||
}
|
||||
});
|
||||
Cookies.remove(TokenKey);
|
||||
Cookies.remove(multipleTabsKey);
|
||||
storageLocal().clear();
|
||||
resolve(PureHttp.retryOriginalRequest(config));
|
||||
} else {
|
||||
config.headers["Authorization"] = data.token;
|
||||
|
@ -30,7 +30,7 @@ interface MessageParams {
|
||||
onClose?: Function | null;
|
||||
}
|
||||
|
||||
/** 用法非常简单,参考 src/views/components/message/list.vue 文件 */
|
||||
/** 用法非常简单,参考 src/views/components/message/server.vue 文件 */
|
||||
|
||||
/**
|
||||
* `Message` 消息提示函数
|
||||
|
442
src/views/server/clients.vue
Normal file
442
src/views/server/clients.vue
Normal file
@ -0,0 +1,442 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
deleteClient,
|
||||
downloadClient,
|
||||
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 = () => {
|
||||
const serverInfo = storageLocal().getItem("server-info");
|
||||
addDialog({
|
||||
width: "40%",
|
||||
title: "新增",
|
||||
contentRenderer: () => h(editClientForms, { ref: editClientFormRef }),
|
||||
props: {
|
||||
formInline: {
|
||||
id: "",
|
||||
serverId: serverInfo.id,
|
||||
name: "",
|
||||
email: "",
|
||||
subnetRange: "",
|
||||
ipAllocation: "",
|
||||
allowedIPS: "",
|
||||
extraAllowedIPS: "",
|
||||
endpoint: "",
|
||||
useServerDNS: 0,
|
||||
enableAfterCreation: 0,
|
||||
keys: null,
|
||||
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">
|
||||
<el-card
|
||||
v-for="val in clientsList.data"
|
||||
style="width: 540px"
|
||||
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>
|
159
src/views/server/component/detail.vue
Normal file
159
src/views/server/component/detail.vue
Normal file
@ -0,0 +1,159 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { userKey } from "@/utils/auth";
|
||||
import {clientFormRules} from "@/views/server/component/rules";
|
||||
|
||||
// 声明 props 类型
|
||||
export interface DetailFormProps {
|
||||
formInline: {
|
||||
id: string;
|
||||
serverId: string;
|
||||
name: string;
|
||||
email: string;
|
||||
subnetRange: string;
|
||||
ipAllocation: [];
|
||||
allowedIPS: [];
|
||||
extraAllowedIPS: [];
|
||||
endpoint: string;
|
||||
useServerDNS: number;
|
||||
enableAfterCreation: number;
|
||||
keys: {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
presharedKey: string;
|
||||
};
|
||||
enabled: number;
|
||||
};
|
||||
}
|
||||
|
||||
// 声明 props 默认值
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||
const props = withDefaults(defineProps<DetailFormProps>(), {
|
||||
formInline: () => ({
|
||||
id: "",
|
||||
serverId: "",
|
||||
name: "",
|
||||
email: "",
|
||||
subnetRange: "",
|
||||
ipAllocation: [],
|
||||
allowedIPS: [],
|
||||
extraAllowedIPS: [],
|
||||
endpoint: "",
|
||||
useServerDNS: 0,
|
||||
enableAfterCreation: 0,
|
||||
keys: {
|
||||
privateKey: "",
|
||||
publicKey: "",
|
||||
presharedKey: ""
|
||||
},
|
||||
enabled: 1
|
||||
})
|
||||
});
|
||||
|
||||
// vue 规定所有的 prop 都遵循着单向绑定原则,直接修改 prop 时,Vue 会抛出警告。此处的写法仅仅是为了消除警告。
|
||||
// 因为对一个 reactive 对象执行 ref,返回 Ref 对象的 value 值仍为传入的 reactive 对象,
|
||||
// 即 newFormInline === props.formInline 为 true,所以此处代码的实际效果,仍是直接修改 props.formInline。
|
||||
// 但该写法仅适用于 props.formInline 是一个对象类型的情况,原始类型需抛出事件
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/components/props.html#one-way-data-flow
|
||||
const detailForm = ref(props.formInline);
|
||||
const detailFormRef = ref<FormInstance>();
|
||||
|
||||
function getDetailFormRef() {
|
||||
return detailFormRef.value;
|
||||
}
|
||||
|
||||
defineExpose({ getDetailFormRef });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form
|
||||
ref="detailFormRef"
|
||||
:model="detailForm"
|
||||
:rules="clientFormRules"
|
||||
label-width="20%"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item prop="name" label="名称">
|
||||
<el-input v-model="detailForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="email" label="邮箱">
|
||||
<el-input v-model="detailForm.email" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="subnetRange" label="子网范围">
|
||||
<el-input v-model="detailForm.subnetRange" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="ipAllocation" label="客户端IP">
|
||||
<el-select
|
||||
v-model="detailForm.ipAllocation"
|
||||
:clearable="true"
|
||||
:reserve-keyword="false"
|
||||
suffix-icon=""
|
||||
tag-type="warning"
|
||||
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="detailForm.allowedIPS"
|
||||
:clearable="true"
|
||||
:reserve-keyword="false"
|
||||
suffix-icon=""
|
||||
tag-type="warning"
|
||||
popper-class="options-class"
|
||||
placeholder=""
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="extraAllowedIPS" label="其他允许访问的IP段">
|
||||
<el-select
|
||||
v-model="detailForm.extraAllowedIPS"
|
||||
:clearable="true"
|
||||
:reserve-keyword="false"
|
||||
suffix-icon=""
|
||||
tag-type="warning"
|
||||
popper-class="options-class"
|
||||
placeholder=""
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="endpoint" label="链接端点">
|
||||
<el-input v-model="detailForm.endpoint" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="useServerDNS" label="是否使用服务端DNS">
|
||||
<el-radio-group v-model="detailForm.useServerDNS">
|
||||
<el-radio :value="1">是</el-radio>
|
||||
<el-radio :value="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="enableAfterCreation" label="确认后创建">
|
||||
<el-radio-group v-model="detailForm.enableAfterCreation">
|
||||
<el-radio :value="1">是</el-radio>
|
||||
<el-radio :value="0">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="enabled" label="状态">
|
||||
<el-radio-group v-model="detailForm.enabled">
|
||||
<el-radio :value="1">启用</el-radio>
|
||||
<el-radio :value="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.options-class {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
45
src/views/server/component/qrCode.vue
Normal file
45
src/views/server/component/qrCode.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { FormInstance } from "element-plus";
|
||||
import { storageLocal } from "@pureadmin/utils";
|
||||
import { userKey } from "@/utils/auth";
|
||||
import {clientQrCode} from "@/api/clients";
|
||||
|
||||
// 声明 props 类型
|
||||
export interface DetailFormProps {
|
||||
formInline: {
|
||||
id: string;
|
||||
qrCodeStr: string;
|
||||
};
|
||||
}
|
||||
|
||||
// 声明 props 默认值
|
||||
// 推荐阅读:https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
|
||||
const props = withDefaults(defineProps<DetailFormProps>(), {
|
||||
formInline: () => ({
|
||||
id: "",
|
||||
qrCodeStr: ""
|
||||
})
|
||||
});
|
||||
|
||||
const qrCodeData = ref(props.formInline);
|
||||
|
||||
const getQrCode = (id: string) => {
|
||||
clientQrCode(id).then(res => {
|
||||
if (res.code === 200) {
|
||||
qrCodeData.value.qrCodeStr = res.data.qrCode;
|
||||
}
|
||||
});
|
||||
};
|
||||
getQrCode(props.formInline.id);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card body-style="padding: inherit" shadow="hover">
|
||||
<el-image
|
||||
:src="qrCodeData.qrCodeStr"
|
||||
:preview-teleported="true"
|
||||
style="width: 100%; height: 100%"
|
||||
/>
|
||||
</el-card>
|
||||
</template>
|
36
src/views/server/component/rules.ts
Normal file
36
src/views/server/component/rules.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 登录校验 */
|
||||
const clientFormRules = reactive(<FormRules>{
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: "名称不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
ipAllocation: [
|
||||
{
|
||||
required: true,
|
||||
message: "客户端IP不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
allowedIPS: [
|
||||
{
|
||||
required: true,
|
||||
message: "允许链接IP不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
enabled: [
|
||||
{
|
||||
required: true,
|
||||
message: "状态不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export { clientFormRules };
|
@ -1,10 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: "ServerConfig"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>服务端配置页面</div>
|
||||
</template>
|
@ -1,10 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: "Server"
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>服务端列表</div>
|
||||
</template>
|
251
src/views/server/server.vue
Normal file
251
src/views/server/server.vue
Normal file
@ -0,0 +1,251 @@
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref } from "vue";
|
||||
import type { FormInstance } from "element-plus";
|
||||
import { useServerStoreHook } from "@/store/modules/server";
|
||||
import { serverFormRules } from "@/views/server/utils/rules";
|
||||
import { useGlobalSettingStoreHook } from "@/store/modules/globalSetting";
|
||||
import { getPublicIP } from "@/api/server";
|
||||
|
||||
defineOptions({
|
||||
// name 作为一种规范最好必须写上并且和路由的name保持一致
|
||||
name: "Server"
|
||||
});
|
||||
|
||||
const serverFormRef = ref<FormInstance>();
|
||||
|
||||
let serverForm = reactive({
|
||||
id: "",
|
||||
ipScope: "",
|
||||
listenPort: 0,
|
||||
privateKey: "",
|
||||
publicKey: "",
|
||||
postUpScript: "",
|
||||
preDownScript: "",
|
||||
postDownScript: ""
|
||||
});
|
||||
|
||||
const configFormRef = ref<FormInstance>();
|
||||
let configForm = reactive({
|
||||
endpointAddress: "",
|
||||
dnsServer: [],
|
||||
MTU: 0,
|
||||
persistentKeepalive: 0,
|
||||
firewallMark: "",
|
||||
table: "",
|
||||
configFilePath: ""
|
||||
});
|
||||
|
||||
// 获取服务端信息接口
|
||||
const getServerApi = () => {
|
||||
useServerStoreHook()
|
||||
.getServerApi()
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
const data = useServerStoreHook().getServerInfo;
|
||||
serverForm.id = data.id || "";
|
||||
serverForm.ipScope = data.ipScope || "";
|
||||
serverForm.listenPort = data.listenPort || 0;
|
||||
serverForm.privateKey = data.privateKey || "";
|
||||
serverForm.publicKey = data.publicKey || "";
|
||||
serverForm.postUpScript = data.postUpScript || "";
|
||||
serverForm.preDownScript = data.preDownScript || "";
|
||||
serverForm.postDownScript = data.postDownScript || "";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 更新服务端信息
|
||||
const updateServerApi = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid, fields) => {
|
||||
if (!valid) return fields;
|
||||
useServerStoreHook()
|
||||
.updateServerApi(serverForm)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
getServerApi();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 获取全局配置
|
||||
const getGlobalSettingApi = () => {
|
||||
useGlobalSettingStoreHook()
|
||||
.getGlobalSettingApi()
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
const data = useGlobalSettingStoreHook().getGlobalSetting;
|
||||
configForm.endpointAddress = data.endpointAddress || "";
|
||||
configForm.dnsServer = data.dnsServer || [];
|
||||
configForm.MTU = data.MTU || 0;
|
||||
configForm.persistentKeepalive = data.persistentKeepalive || 0;
|
||||
configForm.firewallMark = data.firewallMark || "";
|
||||
configForm.table = data.table || "";
|
||||
configForm.configFilePath = data.configFilePath || "";
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 更新全局配置
|
||||
const updateGlobalSettingApi = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate((valid, fields) => {
|
||||
if (!valid) return fields;
|
||||
useGlobalSettingStoreHook()
|
||||
.updateGlobalSettingApi(configForm)
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
getGlobalSettingApi();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 获取当前主机的公网IP
|
||||
const getPublicIPApi = () => {
|
||||
getPublicIP().then(res => {
|
||||
if (res.code === 200) {
|
||||
useGlobalSettingStoreHook().SET_ENDPOINT_ADDRESS(res.data.IP);
|
||||
configForm.endpointAddress =
|
||||
useGlobalSettingStoreHook().getEndpointAddress;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
getServerApi();
|
||||
getGlobalSettingApi();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="10" style="padding-left: 50px">
|
||||
<el-card class="left-card" style="max-width: 800px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>服务端信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="server-form">
|
||||
<el-form
|
||||
ref="serverFormRef"
|
||||
:model="serverForm"
|
||||
:rules="serverFormRules"
|
||||
label-position="top"
|
||||
>
|
||||
<el-form-item prop="ipScope" label="子网IP段">
|
||||
<el-input v-model="serverForm.ipScope" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="listenPort" label="监听端口">
|
||||
<el-input-number
|
||||
v-model="serverForm.listenPort"
|
||||
:min="49152"
|
||||
:max="65535"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="privateKey" label="私钥">
|
||||
<el-input v-model="serverForm.privateKey" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item prop="publicKey" label="公钥">
|
||||
<el-input v-model="serverForm.publicKey" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item prop="postUpScript" label="postUpScript">
|
||||
<el-input v-model="serverForm.postUpScript" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="postDownScript" label="postDownScript">
|
||||
<el-input v-model="serverForm.preDownScript" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="postDownScript" label="postDownScript">
|
||||
<el-input v-model="serverForm.postDownScript" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="updateServerApi(serverFormRef)"
|
||||
>确认</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="10" style="padding-left: 50px">
|
||||
<el-card style="max-width: 800px">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>全局配置信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="configFormRef" :model="configForm" label-position="top">
|
||||
<el-form-item prop="endpointAddress" label="endpointAddress">
|
||||
<el-input v-model="configForm.endpointAddress" />
|
||||
<el-button size="small" type="warning" class="getIp" @click="getPublicIPApi">获取IP</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="dnsServer" label="dnsServer">
|
||||
<el-select
|
||||
v-model="configForm.dnsServer"
|
||||
:clearable="true"
|
||||
:reserve-keyword="false"
|
||||
suffix-icon=""
|
||||
tag-type="success"
|
||||
popper-class="options-class"
|
||||
placeholder=""
|
||||
multiple
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="MTU" label="MTU">
|
||||
<el-input v-model="configForm.MTU" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="persistentKeepalive" label="persistentKeepalive">
|
||||
<el-input v-model="configForm.persistentKeepalive" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="firewallMark" label="firewallMark">
|
||||
<el-input v-model="configForm.firewallMark" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="table" label="table">
|
||||
<el-input v-model="configForm.table" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="configFilePath" label="configFilePath">
|
||||
<el-input v-model="configForm.configFilePath" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="updateGlobalSettingApi(configFormRef)"
|
||||
>确认</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.options-class {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="scss">
|
||||
.el-row {
|
||||
margin-bottom: 30px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.el-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.el-col {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
border-radius: 4px;
|
||||
min-height: 36px;
|
||||
}
|
||||
.options-class {
|
||||
display: none;
|
||||
}
|
||||
.getIp{
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
22
src/views/server/utils/rules.ts
Normal file
22
src/views/server/utils/rules.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { reactive } from "vue";
|
||||
import type { FormRules } from "element-plus";
|
||||
|
||||
/** 登录校验 */
|
||||
const serverFormRules = reactive(<FormRules>{
|
||||
ipScope: [
|
||||
{
|
||||
required: true,
|
||||
message: "不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
listenPort: [
|
||||
{
|
||||
required: true,
|
||||
message: "不能为空",
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export { serverFormRules };
|
@ -290,6 +290,7 @@ userList(); // 用户列表接口
|
||||
|
||||
.user-list-table-header {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.div-page {
|
||||
|
@ -1,7 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {useServerStoreHook} from "@/store/modules/server";
|
||||
|
||||
defineOptions({
|
||||
name: "Welcome"
|
||||
});
|
||||
|
||||
const initServerInfo = () => {
|
||||
useServerStoreHook().getServerApi();
|
||||
};
|
||||
|
||||
initServerInfo();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
Loading…
Reference in New Issue
Block a user