实现标签页的增删
This commit is contained in:
parent
f60575d15b
commit
a4052c3a74
@ -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>
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
1496
test/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
8
test/src/types.ts
Normal 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;
|
@ -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
61
test/src/wsHandler.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user