修改webui

This commit is contained in:
纸凤孤凰
2024-11-25 02:17:48 +08:00
parent 152be29739
commit 3fbed815a5
18 changed files with 847 additions and 521 deletions

View File

@@ -1,122 +1,301 @@
<template>
<t-space class="full-space">
<template v-if="clientPanelData.length > 0">
<t-tabs
v-model="activeTab"
:addable="true"
theme="card"
@add="showAddTabDialog"
@remove="removeTab"
class="full-tabs"
>
<t-tab-panel
v-for="(config, idx) in clientPanelData"
:key="idx"
:label="config.name"
:removable="true"
:value="idx"
class="full-tab-panel"
>
<component :is="resolveDynamicComponent(getComponent(config.key))" :config="config.data" />
<div class="button-container">
<t-button @click="saveConfig" style="width: 100px; height: 40px">保存</t-button>
<div ref="headerBox" class="title">
<t-divider content="网络配置" align="left" />
<t-divider align="right">
<t-button @click="addConfig()">
<template #icon><add-icon /></template>
添加配置</t-button>
</t-divider>
</div>
<div v-if="loadPage" ref="setting" class="setting">
<t-tabs ref="tabsRef" :style="{width:tabsWidth+'px'}" default-value="all" @change="selectType">
<t-tab-panel value="all" label="全部"></t-tab-panel>
<t-tab-panel value="httpServers" label="HTTP 服务器"></t-tab-panel>
<t-tab-panel value="httpClients" label="HTTP 客户端"></t-tab-panel>
<t-tab-panel value="websocketServers" label="WebSocket 服务器"></t-tab-panel>
<t-tab-panel value="websocketClients" label="WebSocket 客户端"></t-tab-panel>
</t-tabs>
</div>
<div v-if="loadPage" class="card-box" :style="{width:tabsWidth+'px'}">
<div class="setting-box" :style="{maxHeight:cardHeight+'px'}" v-if="cardConfig.length>0">
<div v-for="(item,index) in cardConfig" :key="index">
<t-card :title="item.name" :style="{width:cardWidth+'px'}" :header-bordered="true" class="setting-card">
<template #actions>
<t-space>
<edit2-icon size="20px" @click="editConfig(item)"></edit2-icon>
<t-popconfirm theme="danger" content="确认删除" @confirm="delConfig(item)">
<delete-icon size="20px" ></delete-icon>
</t-popconfirm>
</t-space>
</template>
<div class="setting-content">
<div>类型{{item.type}}</div>
<div class="setting-address">
<div v-if="item.host" class="local">地址{{item.host}}</div>
<div v-if="item.url" class="local">地址{{item.url}}</div>
<div class="none-box"></div>
<div v-if="item.port" class="port">端口{{ item.port }}</div>
<div v-if="item.heartInterval&&!item.port" class="port">心跳间隔{{ item.heartInterval }}</div>
</div>
<div v-if="item.heartInterval&&item.port" class="port">心跳间隔{{ item.heartInterval }}</div>
<div class="setting-address">
<div class="local">Token{{item.token}}</div>
<div class="none-box"></div>
<div class="port">消息格式{{item.messagePostFormat}}</div>
</div>
<div class="setting-status">
<div class="status-tag">
<div >状态</div>
<t-tag class="tag-item" :theme="(item.enable ? 'success' : 'danger')"> {{item.enable ? "启用" : "禁用"}}</t-tag>
</div>
<div class="none-box"></div>
<div class="status-deBug">
<div>调试</div>
<t-tag class="tag-item" :theme="(item.debug ? 'success' : 'danger')"> {{item.debug ? "开启" : "关闭"}}</t-tag>
</div>
</div>
<div class="setting-status">
<div v-if="item.hasOwnProperty('enableWebsocket')" class="status-tag">
<div >WS </div>
<t-tag class="tag-item" :theme="(item.enableWebsocket ? 'success' : 'danger')"> {{item.enableWebsocket ? "启用" : "禁用"}}</t-tag>
</div>
<div class="none-box"></div>
<div v-if="item.hasOwnProperty('enableCors')" class="status-deBug">
<div>跨域</div>
<t-tag class="tag-item" :theme="(item.enableCors ? 'success' : 'danger')"> {{item.enableCors ? "开启" : "关闭"}}</t-tag>
</div>
</div>
<div v-if="item.hasOwnProperty('reportSelfMessage')" class="status-deBug" style="margin-top: 2px">
<div>上报自身消息</div>
<t-tag class="tag-item" :theme="(item.reportSelfMessage ? 'success' : 'danger')"> {{item.reportSelfMessage ? "开启" : "关闭"}}</t-tag>
</div>
<div v-if="item.hasOwnProperty('enableForcePushEvent')" class="status-deBug" style="margin-top: 2px">
<div>强制推送事件</div>
<t-tag class="tag-item" :theme="(item.enableForcePushEvent ? 'success' : 'danger')"> {{item.enableForcePushEvent ? "开启" : "关闭"}}</t-tag>
</div>
</div>
</t-tab-panel>
</t-tabs>
</template>
<template v-else>
<EmptyStateComponent :showAddTabDialog="showAddTabDialog" />
</template>
<t-dialog
v-model:visible="isDialogVisible"
header="添加网络配置"
@close="isDialogVisible = false"
@confirm="addTab"
>
<t-form ref="form" :model="newTab">
<t-form-item :rules="[{ required: true, message: '请输入名称' }]" label="名称" name="name">
</t-card>
</div>
<div style="height: 20vh"></div>
</div>
<t-card class="card-none" v-else >
<div class="card-noneText">暂无网络配置</div>
</t-card>
</div>
<t-dialog
v-model:visible="visibleBody"
:header="dialogTitle"
:destroy-on-close="true"
:show-in-attached-element="true"
placement="center"
:on-confirm="saveConfig"
>
<div slot="body" class="dialog-body">
<t-form ref="form" :data="newTab" labelAlign="left" :model="newTab" >
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请输入名称',trigger: 'blur' }]" label="名称" name="name">
<t-input v-model="newTab.name" />
</t-form-item>
<t-form-item :rules="[{ required: true, message: '请选择类型' }]" label="类型" name="type">
<t-select v-model="newTab.type">
<t-form-item style="text-align: left" :rules="[{ required: true, message: '请选择类型',trigger: 'change'}]" label="类型" name="type">
<t-select v-model="newTab.type" @change="onloadDefault">
<t-option value="httpServers">HTTP 服务器</t-option>
<t-option value="httpClients">HTTP 客户端</t-option>
<t-option value="websocketServers">WebSocket 服务器</t-option>
<t-option value="websocketClients">WebSocket 客户端</t-option>
</t-select>
</t-form-item>
<div>
<component :is="resolveDynamicComponent(getComponent(newTab.type as ConfigKey))" :config="newTab.data" />
</div>
</t-form>
</t-dialog>
</t-space>
</div>
</t-dialog>
</template>
<script setup lang="ts">
import { ref, resolveDynamicComponent, nextTick, Ref, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { AddIcon, DeleteIcon, Edit2Icon } from 'tdesign-icons-vue-next';
import { onMounted, onUnmounted, ref, resolveDynamicComponent } from 'vue';
import emitter from '@/ts/event-bus';
import {
httpServerDefaultConfigs,
httpClientDefaultConfigs,
websocketServerDefaultConfigs,
websocketClientDefaultConfigs,
HttpClientConfig,
httpClientDefaultConfigs,
HttpServerConfig,
WebsocketClientConfig,
WebsocketServerConfig,
httpServerDefaultConfigs,
mergeOneBotConfigs,
NetworkConfig,
OneBotConfig,
mergeOneBotConfigs,
WebsocketClientConfig,
websocketClientDefaultConfigs,
WebsocketServerConfig,
websocketServerDefaultConfigs,
} from '../../../src/onebot/config/config';
import { QQLoginManager } from '@/backend/shell';
import HttpServerComponent from '@/pages/network/HttpServerComponent.vue';
import HttpClientComponent from '@/pages/network/HttpClientComponent.vue';
import WebsocketServerComponent from '@/pages/network/WebsocketServerComponent.vue';
import WebsocketClientComponent from '@/pages/network/WebsocketClientComponent.vue';
import EmptyStateComponent from '@/pages/network/EmptyStateComponent.vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { QQLoginManager } from '@/backend/shell';
const tabsWidth=ref<number>(0)
const menuWidth=ref<number>(0)
const cardWidth=ref<number>(0)
const cardHeight=ref<number>(0)
const mediumScreen = window.matchMedia('(min-width: 768px) and (max-width: 1024px)');
const largeScreen = window.matchMedia('(min-width: 1025px)');
const headerBox=ref<any>(null)
const setting=ref<any>(null)
const loadPage=ref<boolean>(false);
const visibleBody=ref<boolean>(false);
const newTab = ref<{ name: string; data:any; type: string }>({ name: '', data:{},type: '' });
const dialogTitle = ref<string>('');
type ConfigKey = 'httpServers' | 'httpClients' | 'websocketServers' | 'websocketClients';
type ConfigUnion = HttpClientConfig | HttpServerConfig | WebsocketServerConfig | WebsocketClientConfig;
type ComponentUnion =
| typeof HttpServerComponent
| typeof HttpClientComponent
| typeof WebsocketServerComponent
| typeof WebsocketClientComponent;
const componentMap: Record<ConfigKey, ComponentUnion> = {
httpServers: HttpServerComponent,
httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent,
};
const defaultConfigMap: Record<ConfigKey, ConfigUnion> = {
const defaultConfigs: Record<ConfigKey, ConfigUnion> = {
httpServers: httpServerDefaultConfigs,
httpClients: httpClientDefaultConfigs,
websocketServers: websocketServerDefaultConfigs,
websocketClients: websocketClientDefaultConfigs,
};
interface ConfigMap {
httpServers: HttpServerConfig;
httpClients: HttpClientConfig;
websocketServers: WebsocketServerConfig;
websocketClients: WebsocketClientConfig;
const componentMap: Record<
ConfigKey,
| typeof HttpServerComponent
| typeof HttpClientComponent
| typeof WebsocketServerComponent
| typeof WebsocketClientComponent
> = {
httpServers: HttpServerComponent,
httpClients: HttpClientComponent,
websocketServers: WebsocketServerComponent,
websocketClients: WebsocketClientComponent,
};
type ComponentKey = keyof typeof componentMap;
//操作类型
const operateType = ref<string>('');
//配置项索引
const configIndex = ref<number>(0);
//保存时所用数据
interface NetworkConfigType {
[key: string]: any;
websocketClients: any[];
websocketServers: any[];
httpClients: any[];
httpServers: any[];
}
const networkConfig: NetworkConfigType = {
"websocketClients": [],
"websocketServers": [],
"httpClients": [],
"httpServers": []
};
interface ClientPanel<K extends ConfigKey = ConfigKey> {
name: string;
key: K;
data: ConfigMap[K];
//挂载的数据
const WebConfg = ref(new Map<string, Array<null>>([
["all", []],
["httpServers", []],
["httpClients", []],
["websocketServers", []],
["websocketClients", []]
]));
interface TypeChType {
[key: string]: string;
httpServers: string;
httpClients: string;
websocketServers: string;
websocketClients: string;
}
const activeTab = ref<number>(0);
const isDialogVisible = ref(false);
const newTab = ref<{ name: string; type: ConfigKey }>({ name: '', type: 'httpServers' });
const clientPanelData: Ref<ClientPanel[]> = ref([]);
const getComponent = (type: ConfigKey) => {
const typeCh:TypeChType = {
"httpServers":"HTTP 服务器" ,
"httpClients":"HTTP 客户端",
"websocketServers":"WebSocket 服务器",
"websocketClients":"WebSocket 客户端"
};
const getKeyByValue=(obj: TypeChType, value: string): string | undefined =>{
return Object.entries(obj).find(([_, v]) => v === value)?.[0];
}
const cardConfig = ref<any>([])
const getComponent = (type: ComponentKey) => {
return componentMap[type];
};
const addConfig=()=>{
dialogTitle.value="添加配置"
newTab.value={ name: '', data:{},type: '' };
operateType.value='add'
visibleBody.value=true;
}
const editConfig = (item:any) => {
dialogTitle.value="修改配置"
const type = getKeyByValue(typeCh,item.type)
if (type){
newTab.value={ name: item.name, data:item,type:type };
}
operateType.value='edit'
configIndex.value=networkConfig[newTab.value.type].findIndex((obj:any) => obj.name === item.name);
visibleBody.value=true;
}
const delConfig = (item:any) => {
const type = getKeyByValue(typeCh,item.type)
if (type){
newTab.value={ name: item.name, data:item,type:type };
}
configIndex.value=configIndex.value=networkConfig[newTab.value.type].findIndex((obj:any) => obj.name === item.name);
operateType.value='delete'
saveConfig()
}
const selectType = (key: string) => {
cardConfig.value = WebConfg.value.get(key)
}
const onloadDefault = (key: ConfigKey) => {
console.log(key)
newTab.value.data=structuredClone(defaultConfigs[key])
}
//检测重名
const checkName=(name:string) =>{
const allConfigs = WebConfg.value.get('all')?.findIndex((obj:any) => obj.name === name);
if (newTab.value.name===''|| newTab.value.type===''){
MessagePlugin.error('请填写完整信息');
return false;
}
else if(allConfigs===-1||newTab.value.data.name===name){
return true;
}
else {
MessagePlugin.error('名称已存在');
return false;
}
}
//保存
const saveConfig = async () => {
if(operateType.value=='add'){
if(!checkName(newTab.value.name))return;
newTab.value.data.name=newTab.value.name
networkConfig[newTab.value.type].push(newTab.value.data);
}
else if(operateType.value=='edit'){
if(!checkName(newTab.value.name))return;
newTab.value.data.name=newTab.value.name
networkConfig[newTab.value.type][configIndex.value]=newTab.value.data;
}
else if(operateType.value=='delete'){
networkConfig[newTab.value.type].splice(configIndex.value,1)
}
const userConfig = await getOB11Config();
if (!userConfig) return;
userConfig.network = networkConfig;
const success = await setOB11Config(userConfig);
if (success) {
operateType.value='';
configIndex.value=0
MessagePlugin.success('配置保存成功');
await loadConfig()
visibleBody.value=false;
} else {
MessagePlugin.error('配置保存失败');
}
};
const getOB11Config = async (): Promise<OneBotConfig | undefined> => {
const storedCredential = localStorage.getItem('auth');
if (!storedCredential) {
@@ -136,114 +315,165 @@ const setOB11Config = async (config: OneBotConfig): Promise<boolean> => {
const loginManager = new QQLoginManager(storedCredential);
return await loginManager.SetOB11Config(config);
};
const addToPanel = <K extends ConfigKey>(configs: ConfigMap[K][], key: K) => {
configs.forEach((config) => clientPanelData.value.push({ name: config.name, data: config, key }));
};
const addConfigDataToPanel = (data: NetworkConfig) => {
(Object.keys(data) as ConfigKey[]).forEach((key) => {
addToPanel(data[key], key);
//获取卡片数据
const getAllData = (data: NetworkConfig) => {
cardConfig.value=[]
WebConfg.value.set("all", [])
Object.entries(data).forEach(([key, configs]) => {
if (key in defaultConfigs) {
networkConfig[key]=[...configs]
const newConfigsArray = configs.map((config:any) => ({
...config,
type:typeCh[key]
}));
WebConfg.value.set(key,newConfigsArray);
const allConfigs = WebConfg.value.get('all');
if (allConfigs) {
const newAllConfigs = [...allConfigs,...newConfigsArray];
WebConfg.value.set('all', newAllConfigs);
}
cardConfig.value=WebConfg.value.get("all")
}
});
};
const parsePanelData = (): NetworkConfig => {
const result: NetworkConfig = {
httpServers: [],
httpClients: [],
websocketServers: [],
websocketClients: [],
};
clientPanelData.value.forEach((panel) => {
(result[panel.key] as Array<typeof panel.data>).push(panel.data);
});
return result;
};
}
const loadConfig = async () => {
try {
const userConfig = await getOB11Config();
if (!userConfig) return;
const mergedConfig = mergeOneBotConfigs(userConfig);
addConfigDataToPanel(mergedConfig.network);
getAllData(mergedConfig.network)
} catch (error) {
console.error('Error loading config:', error);
}
};
const saveConfig = async () => {
const config = parsePanelData();
const userConfig = await getOB11Config();
if (!userConfig) {
await MessagePlugin.error('无法获取配置!');
return;
const handleResize = () => {
tabsWidth.value = window.innerWidth- 40 -menuWidth.value
if (mediumScreen.matches) {
cardWidth.value = (tabsWidth.value-20)/2;
}
userConfig.network = config;
const success = await setOB11Config(userConfig);
if (success) {
await MessagePlugin.success('配置保存成功');
} else {
await MessagePlugin.error('配置保存失败');
else if(largeScreen.matches) {
cardWidth.value = (tabsWidth.value-40)/3;
}else {
cardWidth.value = tabsWidth.value;
}
loadPage.value=true
setTimeout(()=>{
cardHeight.value=window.innerHeight-headerBox.value.offsetHeight-setting.value.offsetHeight-20;
},300)
};
const showAddTabDialog = () => {
newTab.value = { name: '', type: 'httpServers' };
isDialogVisible.value = true;
};
const addTab = async () => {
const { name, type } = newTab.value;
if (clientPanelData.value.some((panel) => panel.name === name)) {
await MessagePlugin.error('选项卡名称已存在');
return;
emitter.on('sendWidth', (width) => {
if (typeof width === 'number' &&!isNaN(width)) {
menuWidth.value = width;
handleResize()
}
const defaultConfig = structuredClone(defaultConfigMap[type]);
defaultConfig.name = name;
clientPanelData.value.push({ name, data: defaultConfig, key: type });
isDialogVisible.value = false;
await nextTick();
activeTab.value = clientPanelData.value.length - 1;
await MessagePlugin.success('选项卡添加成功');
};
const removeTab = async (payload: { value: string; index: number; e: PointerEvent }) => {
clientPanelData.value.splice(payload.index, 1);
activeTab.value = Math.max(0, activeTab.value - 1);
await saveConfig();
};
});
onMounted(() => {
loadConfig();
loadConfig()
const cachedWidth = localStorage.getItem('menuWidth');
if (cachedWidth) {
menuWidth.value = parseInt(cachedWidth);
setTimeout(()=>{
handleResize()
},300)
}
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped>
.full-space {
.title {
padding: 20px 20px 0 20px;
display: flex;
justify-content: space-between;
}
.setting {
margin: 0 20px;
}
.setting-box {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
overflow-y: auto;
}
.setting-card {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
}
.full-tabs {
.setting-content{
width: 100%;
height: 100%;
text-align: left;
}
.setting-address{
display: flex;
flex-direction: column;
margin-top: 2px;
}
.local{
flex: 5.5;
margin-bottom: 1px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.none-box{
flex: 0.5;
}
.port{
flex: 4;
margin-top: 1px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.setting-status{
display: flex;
margin-top: 2px;
}
.status-deBug{
display: flex;
flex: 4;
}
.status-tag{
display: flex;
flex: 5.5;
}
.full-tab-panel {
flex: 1;
display: flex;
flex-direction: column;
@media (max-width: 1024px) {
.setting-box {
grid-template-columns: 1fr 1fr;
}
}
.button-container {
display: flex;
justify-content: center;
margin-top: 20px;
@media (max-width: 786px) {
.setting-box {
grid-template-columns: 1fr;
}
.setting-address{
display: block;
}
}
.card-box {
margin: 10px 20px 0 20px;
}
.card-none {
line-height: 200px;
}
.card-noneText {
font-size: 16px;
}
.dialog-body{
max-height: 60vh;
overflow-y: auto;
}
::-webkit-scrollbar {
width: 0;
background: transparent;
}
</style>