实现标签页的增删

This commit is contained in:
锦恢 2025-03-27 01:43:55 +08:00
parent f60575d15b
commit a4052c3a74
14 changed files with 209 additions and 1617 deletions

View File

@ -6,7 +6,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'chat' }); import { defineComponent } from 'vue';
defineComponent({ name: 'chat' });
</script> </script>
<style scoped> <style scoped>

View File

@ -1,6 +1,8 @@
<template> <template>
<div class="main-panel-container"> <div class="main-panel-container">
<div class="tabs-container"> <div class="tabs-container">
<el-scrollbar>
<div class="scroll-tabs-container">
<span <span
class="tab" class="tab"
v-for="(tab, index) of tabs.content" v-for="(tab, index) of tabs.content"
@ -8,22 +10,31 @@
:class="{ 'active-tab': tabs.activeIndex === index }" :class="{ 'active-tab': tabs.activeIndex === index }"
@click="setActiveTab(index)" @click="setActiveTab(index)"
> >
<span>
<span :class="`iconfont ${tab.icon}`"></span> <span :class="`iconfont ${tab.icon}`"></span>
<span>{{ tab.name }}</span> <span>{{ tab.name }}</span>
</span>
<span <span
class="iconfont icon-close" class="iconfont icon-close"
@click.stop="closeTab(index)" @click.stop="closeTab(index)"
></span> ></span>
</span> </span>
</div>
</el-scrollbar>
<span <span
class="add-button iconfont icon-add" class="add-button iconfont icon-add"
@click="addNewTab" @click="addNewTab"
> >
</span> </span>
</div> </div>
<div class="main-panel"> <div class="main-panel">
<router-view /> <router-view />
</div> </div>
</div> </div>
</template> </template>
@ -52,6 +63,11 @@ defineComponent({ name: 'main-panel' });
height: 95%; height: 95%;
} }
.scroll-tabs-container {
width: fit-content;
display: flex;
}
.tabs-container { .tabs-container {
height: 78px; height: 78px;
width: 90%; width: 90%;
@ -62,20 +78,31 @@ defineComponent({ name: 'main-panel' });
padding: 0 10px; padding: 0 10px;
} }
.tabs-container .el-scrollbar {
height: fit-content;
}
.tabs-container .tab { .tabs-container .tab {
margin: 5px; margin: 5px;
font-size: 13px;
width: 120px;
border-radius: .5em; border-radius: .5em;
background-color: var(--sidebar); background-color: var(--sidebar);
padding: 10px; padding: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer;
transition: var(--animation-3s); transition: var(--animation-3s);
justify-content: space-between;
position: relative; position: relative;
} }
.tabs-container .tab > span:first-child {
display: flex;
align-items: center;
}
.tabs-container .tab:hover { .tabs-container .tab:hover {
background-color: var(--sidebar-hover); background-color: var(--input-active-background);
} }
.tabs-container .tab.active-tab { .tabs-container .tab.active-tab {
@ -89,6 +116,16 @@ defineComponent({ name: 'main-panel' });
.tabs-container .icon-close { .tabs-container .icon-close {
margin-left: 10px; margin-left: 10px;
margin-right: 0 !important;
border-radius: .5em;
cursor: pointer;
padding: 3px;
transition: var(--animation-3s);
}
.tabs-container .icon-close:hover {
background-color: var(--main-light-color);
transition: var(--animation-3s);
} }
.tabs-container .add-button { .tabs-container .add-button {

View File

@ -1,29 +1,45 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import Chat from './chat/index.vue';
import Resource from './chat/index.vue';
import Prompt from './prompt/index.vue';
import Tool from './tool/index.vue';
interface Tab { interface Tab {
name: string; name: string;
icon: string; icon: string;
type: string; type: string;
component: any;
} }
export const debugModes = [
Resource, Prompt, Tool, Chat
]
export const tabs = reactive({ export const tabs = reactive({
content: [ content: [
{ {
name: '空白的测试', name: '空白测试 1',
icon: 'icon-blank', icon: 'icon-blank',
type: 'blank' type: 'blank',
component: undefined
} }
] as Tab[], ] as Tab[],
activeIndex: 0 activeIndex: 0,
get activeTab() {
return this.content[this.activeIndex];
}
}); });
let tabCounter = 1; let tabCounter = 1;
export function addNewTab() { export function addNewTab() {
const newTab = { const newTab = {
name: `新标签页 ${tabCounter++}`, name: `空白测试 ${++ tabCounter}`,
icon: 'icon-blank', icon: 'icon-blank',
type: 'blank' type: 'blank',
component: undefined
}; };
tabs.content.push(newTab); tabs.content.push(newTab);
tabs.activeIndex = tabs.content.length - 1; tabs.activeIndex = tabs.content.length - 1;

View File

@ -6,7 +6,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'prompt' }); import { defineComponent } from 'vue';
defineComponent({ name: 'prompt' });
</script> </script>
<style scoped> <style scoped>

View File

@ -6,7 +6,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'resource' }); import { defineComponent } from 'vue';
defineComponent({ name: 'resource' });
</script> </script>
<style scoped> <style scoped>

View File

@ -6,7 +6,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineOptions({ name: 'tool' }); import { defineComponent } from 'vue';
defineComponent({ name: 'tool' });
</script> </script>
<style scoped> <style scoped>

View File

@ -1,6 +1,7 @@
<template> <template>
<div style="height: 100%;"> <div style="height: 100%;">
<Welcome></Welcome> <Welcome v-if="!tabs.activeTab.component"></Welcome>
<component v-else :is="tabs.activeTab.component" />
</div> </div>
</template> </template>
@ -8,6 +9,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import Welcome from './welcome.vue'; import Welcome from './welcome.vue';
import { tabs } from '@/components/main-panel/panel';
defineComponent({ name: 'TEMPLATE_NAME' }); defineComponent({ name: 'TEMPLATE_NAME' });
</script> </script>

View File

@ -6,6 +6,7 @@
class="debug-option" class="debug-option"
v-for="(option, index) of debugOptions" v-for="(option, index) of debugOptions"
:key="index" :key="index"
@click="chooseDebugMode(index)"
> >
<span> <span>
<span :class="`iconfont ${option.icon}`"></span> <span :class="`iconfont ${option.icon}`"></span>
@ -17,7 +18,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { debugModes, tabs } from '@/components/main-panel/panel';
import { defineComponent, markRaw } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
defineComponent({ name: 'welcome' }); defineComponent({ name: 'welcome' });
@ -47,6 +49,13 @@ const debugOptions = [
} }
]; ];
function chooseDebugMode(index: number) {
const activeTab = tabs.activeTab;
activeTab.component = markRaw(debugModes[index]);
activeTab.icon = debugOptions[index].icon;
activeTab.name = debugOptions[index].name;
}
</script> </script>
<style> <style>

1496
test/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,15 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"@types/pako": "^2.0.3", "@types/pako": "^2.0.3",
"@types/ws": "^8.18.0",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.6.3" "typescript": "^5.6.3"
}, },
"dependencies": { "dependencies": {
"body-parser": "^1.20.3", "body-parser": "^1.20.3",
"bson": "^6.8.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"electron": "^33.0.1",
"express": "^4.21.1", "express": "^4.21.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"pako": "^2.1.0", "pako": "^2.1.0"
"pdf-lib": "^1.17.1",
"puppeteer-core": "^19.4.1"
} }
} }

View File

@ -1,54 +1,19 @@
import { app, BrowserWindow } from 'electron'; // server/src/server.ts
import express, { Request, Response } from 'express'; import express from 'express';
import morgan from 'morgan'; import { WSServer } from './wsHandler';
import cors from 'cors';
import { createWindow } from './windows'; const app = express();
const PORT = 3000;
const corsOptions = { // 初始化 WebSocket 服务器
// 一些旧版浏览器(如 IE11、各种 SmartTV在 204 状态下会有问题 const wsServer = new WSServer(8080);
optionsSuccessStatus: 200
};
const backendApp = express(); // HTTP 接口
app.get('/', (req, res) => {
backendApp.use(express.json()); res.send('WebSocket Server is running');
backendApp.use(cors(corsOptions));
backendApp.use(morgan('dev'));
backendApp.get('/', (req: Request, res: Response) => {
res.send('<h1>Hello, World!</h1><br><img src="https://picx.zhimg.com/v2-b4251de7d2499e942c7ebf447a90d2eb_l.jpg"/>');
}); });
// backendApp.post('/vcd/save-view', Vcd.saveView); // 启动 HTTP 服务器
// backendApp.post('/vcd/save-view-as', Vcd.saveViewAs); app.listen(PORT, () => {
// backendApp.post('/vcd/load-view', Vcd.loadView); console.log(`HTTP server running on port ${PORT}`);
// backendApp.post('/netlist/save-as-svg', Netlist.saveAsSvg);
// backendApp.post('/netlist/save-as-pdf', Netlist.saveAsPdf);
// backendApp.post('/netlist/goto-definition', Netlist.gotoDefinition);
// backendApp.post('/codedoc/get-doc-ir', CodeDoc.getDocIR);
// backendApp.post('/codedoc/download-svg', CodeDoc.downloadSvg);
const PORT = process.env.PORT || 3000;
backendApp.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// 下面注册 electron 窗口
app.on('ready', () => {
createWindow();
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
}); });

8
test/src/types.ts Normal file
View File

@ -0,0 +1,8 @@
// server/src/types.ts
export interface IMessage {
type: string;
data: Record<string, unknown>;
timestamp?: number;
}
export type MessageHandler = (message: IMessage) => void;

View File

@ -1,35 +0,0 @@
import { BrowserWindow, dialog } from 'electron';
import * as path from 'path';
export function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: false,
contextIsolation: true,
},
});
mainWindow.loadFile('public/index.html');
}
// 在主进程中定义一个方法来显示保存对话框
export async function showSaveViewDialog(option: Electron.SaveDialogOptions): Promise<string | undefined> {
const result = await dialog.showSaveDialog(option);
if (!result.canceled && result.filePath) {
return result.filePath;
} else {
return undefined;
}
}
export async function showOpenViewDialog(option: Electron.OpenDialogOptions): Promise<string | undefined> {
const result = await dialog.showOpenDialog(option);
if (!result.canceled && result.filePaths.length > 0) {
return result.filePaths[0];
} else {
return undefined;
}
}

61
test/src/wsHandler.ts Normal file
View File

@ -0,0 +1,61 @@
// server/src/wsHandler.ts
import { WebSocketServer, WebSocket } from 'ws';
import { IMessage } from './types';
export class WSServer {
private wss: WebSocketServer;
private clients = new Set<WebSocket>();
constructor(port: number) {
this.wss = new WebSocketServer({ port });
this.setupConnection();
}
private setupConnection() {
this.wss.on('connection', (ws) => {
this.clients.add(ws);
console.log('Client connected');
ws.on('message', (data) => {
try {
const message: IMessage = JSON.parse(data.toString());
this.handleMessage(ws, message);
} catch (err) {
console.error('Message parse error:', err);
}
});
ws.on('close', () => {
this.clients.delete(ws);
console.log('Client disconnected');
});
});
}
private handleMessage(ws: WebSocket, message: IMessage) {
console.log('Received:', message);
// 模拟 VS Code 的 postMessage 响应
if (message.type === 'client-message') {
this.send(ws, {
type: 'server-response',
data: {
original: message.data,
response: 'Message received at ' + new Date().toISOString()
}
});
}
}
public send(ws: WebSocket, message: IMessage) {
if (ws.readyState === ws.OPEN) {
ws.send(JSON.stringify(message));
}
}
public broadcast(message: IMessage) {
this.clients.forEach(client => {
this.send(client, message);
});
}
}