fix: 移除eslint功能

This commit is contained in:
2026-04-10 15:50:05 +08:00
parent c07f2c8dbe
commit 5607d07586
10 changed files with 223 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
{ {
"printWidth": 100, "printWidth": 120,
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,
@@ -8,4 +8,4 @@
"bracketSpacing": true, "bracketSpacing": true,
"arrowParens": "always", "arrowParens": "always",
"endOfLine": "lf" "endOfLine": "lf"
} }

1
package-lock.json generated
View File

@@ -8883,7 +8883,6 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },

View File

@@ -1,8 +1,8 @@
{ {
"name": "my-app", "name": "Zhiju",
"productName": "my-app", "productName": "Zhiju",
"version": "1.0.0", "version": "1.0.0",
"description": "My Electron application description", "description": "Zhiju Ai Assistant",
"main": ".vite/build/main.js", "main": ".vite/build/main.js",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -16,7 +16,7 @@
"format:check": "prettier --check ." "format:check": "prettier --check ."
}, },
"keywords": [], "keywords": [],
"author": "houakang", "author": "zhiju.com.cn",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^20.5.0", "@commitlint/cli": "^20.5.0",

View File

@@ -1,4 +1,4 @@
import { app, BrowserWindow, shell, ipcMain } from 'electron'; import { app, BrowserWindow, shell, ipcMain, Menu } from 'electron';
import path from 'node:path'; import path from 'node:path';
import fs from 'node:fs'; import fs from 'node:fs';
import net from 'node:net'; import net from 'node:net';
@@ -129,9 +129,7 @@ async function startOpencode() {
}); });
opencodeProcess.stdout?.on('data', (d) => console.log(`[opencode] ${d.toString().trim()}`)); opencodeProcess.stdout?.on('data', (d) => console.log(`[opencode] ${d.toString().trim()}`));
opencodeProcess.stderr?.on('data', (d) => opencodeProcess.stderr?.on('data', (d) => console.error(`[opencode error] ${d.toString().trim()}`));
console.error(`[opencode error] ${d.toString().trim()}`)
);
opencodeProcess.once('error', (e) => console.error('[opencode spawn error]', e)); opencodeProcess.once('error', (e) => console.error('[opencode spawn error]', e));
opencodeProcess.once('close', (code) => { opencodeProcess.once('close', (code) => {
console.log(`[opencode exited] code=${code}`); console.log(`[opencode exited] code=${code}`);
@@ -203,10 +201,31 @@ function registerIpcHandlers() {
// Bonjour // Bonjour
ipcMain.handle('bonjour:get-services', () => getDiscoveredServices()); ipcMain.handle('bonjour:get-services', () => getDiscoveredServices());
// 窗口控制
ipcMain.on('window:minimize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win?.minimize();
});
ipcMain.on('window:maximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win?.isMaximized()) {
win.unmaximize();
} else {
win?.maximize();
}
});
ipcMain.on('window:close', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win?.close();
});
} }
// ========== 窗口 ========== // ========== 窗口 ==========
const createWindow = () => { const createWindow = () => {
// 移除菜单栏,保留窗口边框和原生按钮
Menu.setApplicationMenu(null);
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
width: 1280, width: 1280,
height: 800, height: 800,
@@ -217,10 +236,11 @@ const createWindow = () => {
contextIsolation: true, contextIsolation: true,
nodeIntegration: false, nodeIntegration: false,
}, },
titleBarStyle: 'hiddenInset', frame: false,
show: false, show: false,
}); });
mainWindow.webContents.openDevTools();
mainWindow.once('ready-to-show', () => mainWindow.show()); mainWindow.once('ready-to-show', () => mainWindow.show());
mainWindow.webContents.setWindowOpenHandler(({ url }) => { mainWindow.webContents.setWindowOpenHandler(({ url }) => {
@@ -231,9 +251,7 @@ const createWindow = () => {
// 注入 baseUrl让渲染进程的 getBaseUrl() 能拿到正确端口 // 注入 baseUrl让渲染进程的 getBaseUrl() 能拿到正确端口
mainWindow.webContents.on('did-finish-load', () => { mainWindow.webContents.on('did-finish-load', () => {
if (opencodePort) { if (opencodePort) {
mainWindow.webContents.executeJavaScript( mainWindow.webContents.executeJavaScript(`window.__opencodeBaseUrl = 'http://127.0.0.1:${opencodePort}'`);
`window.__opencodeBaseUrl = 'http://127.0.0.1:${opencodePort}'`
);
} }
}); });

View File

@@ -0,0 +1,42 @@
<script setup>
import { computed } from 'vue';
import * as LucideIcons from 'lucide-vue-next';
const props = defineProps({
name: {
type: String,
required: true,
},
size: {
type: [Number, String],
default: 24,
},
color: {
type: String,
default: 'currentColor',
},
strokeWidth: {
type: [Number, String],
default: 2,
},
});
const icon = computed(() => {
// Lucide 图标通常是 PascalCase用户输入可能是 kebab-case
const pascalName = props.name
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join('');
return LucideIcons[pascalName] || LucideIcons[props.name] || null;
});
</script>
<template>
<component :is="icon" v-if="icon" :size="size" :color="color" :stroke-width="strokeWidth" class="lucide-icon" />
<span
v-else
class="lucide-icon-fallback"
:style="{ width: size + 'px', height: size + 'px', display: 'inline-block' }"
></span>
</template>

View File

@@ -1,83 +1,156 @@
<template> <template>
<div class="flex h-screen w-screen overflow-hidden bg-gray-50"> <div class="flex flex-col h-screen w-screen overflow-hidden">
<!-- 侧边 --> <!-- 自定义标题 -->
<aside <nav class="flex items-center justify-between h-8 bg-white border-b border-gray-100 shrink-0 select-none z-50">
:class="[ <!-- 左侧缩放菜单按钮 -->
'flex flex-col bg-white border-r border-gray-200 transition-all duration-300', <div class="flex items-center px-4 no-drag">
appStore.collapsed ? 'w-16' : 'w-56', <el-button link size="small" class="p-1! hover:bg-gray-100 rounded" @click="appStore.toggleSidebar">
]" <LucideIcon v-show="appStore.collapsed" name="panel-left-dashed" size="16" color="#000"></LucideIcon>
> <LucideIcon v-show="!appStore.collapsed" name="panel-right-dashed" size="16" color="#000"></LucideIcon>
<!-- Logo --> </el-button>
<div class="flex items-center h-14 px-4 border-b border-gray-200 shrink-0"> <span class="ml-4 text-sm font-medium pointer-events-none">{{ appStore.title }}</span>
<el-icon class="text-blue-500 text-xl shrink-0"><Monitor /></el-icon>
<span v-if="!appStore.collapsed" class="ml-2 font-semibold text-gray-800 truncate">
{{ appStore.title }}
</span>
</div> </div>
<!-- 导航菜单 --> <!-- 中间拖拽区域 -->
<el-menu <div class="flex-1 h-full"></div>
:default-active="$route.path"
:collapse="appStore.collapsed"
:collapse-transition="false"
router
class="flex-1 border-none"
>
<el-menu-item index="/">
<el-icon><House /></el-icon>
<template #title>首页</template>
</el-menu-item>
<el-menu-item index="/chat">
<el-icon><ChatDotRound /></el-icon>
<template #title>OpenCode 对话</template>
</el-menu-item>
<el-menu-item index="/bonjour">
<el-icon><Search /></el-icon>
<template #title>发现设备</template>
</el-menu-item>
</el-menu>
<!-- 折叠按钮 --> <!-- 右侧窗口控制按钮 -->
<div class="p-3 border-t border-gray-200"> <div class="flex items-center h-full no-drag">
<el-button <button
:icon="appStore.collapsed ? Expand : Fold" class="flex items-center justify-center w-10 h-full hover:bg-gray-100 transition-colors"
circle @click="handleMinimize"
size="small" >
@click="appStore.toggleSidebar" <el-icon :size="14"><SemiSelect /></el-icon>
/> </button>
<button
class="flex items-center justify-center w-10 h-full hover:bg-gray-100 transition-colors"
@click="handleMaximize"
>
<el-icon :size="12"><CopyDocument /></el-icon>
</button>
<button
class="flex items-center justify-center w-10 h-full hover:bg-red-500 hover:text-white transition-colors"
@click="handleClose"
>
<el-icon :size="14"><Close /></el-icon>
</button>
</div> </div>
</aside> </nav>
<!-- 主内容区 --> <div class="flex flex-1 overflow-hidden">
<div class="flex flex-col flex-1 overflow-hidden"> <!-- 侧边栏 -->
<!-- 顶部栏 --> <aside
<header :class="[
class="flex items-center justify-between h-14 px-6 bg-white border-b border-gray-200 shrink-0" 'flex flex-col bg-white border-r border-gray-200 transition-all duration-300 no-drag',
appStore.collapsed ? 'w-16' : 'w-56',
]"
> >
<h1 class="text-base font-medium text-gray-700">{{ currentTitle }}</h1> <!-- Logo -->
<div class="flex items-center gap-2"> <div class="flex items-center h-14 px-4 border-b border-gray-200 shrink-0">
<el-avatar :size="32" class="bg-blue-500">U</el-avatar> <el-icon class="text-blue-500 text-xl shrink-0"><Monitor /></el-icon>
<template v-if="!appStore.collapsed">
<el-button class="ml-2 font-semibold text-gray-800 truncate" link @click="showAppInfo = true">
{{ appStore.title }}
</el-button>
</template>
</div> </div>
</header>
<!-- 页面内容 --> <!-- 导航菜单 -->
<main class="flex-1 overflow-hidden p-6"> <el-menu
<div class="h-full overflow-auto"> :default-active="$route.path"
<router-view /> :collapse="appStore.collapsed"
:collapse-transition="false"
router
class="flex-1 border-none"
>
<el-menu-item index="/">
<el-icon><House /></el-icon>
<template #title>首页</template>
</el-menu-item>
<el-menu-item index="/chat">
<el-icon><ChatDotRound /></el-icon>
<template #title>OpenCode 对话</template>
</el-menu-item>
<el-menu-item index="/bonjour">
<el-icon><Search /></el-icon>
<template #title>发现设备</template>
</el-menu-item>
</el-menu>
<!-- 折叠按钮 -->
<div class="p-3 border-t border-gray-200">
<el-button :icon="appStore.collapsed ? Expand : Fold" circle size="small" @click="appStore.toggleSidebar" />
</div> </div>
</main> </aside>
<!-- 主内容区 -->
<div class="flex flex-col flex-1 overflow-hidden">
<!-- 顶部栏 -->
<header class="flex items-center justify-between h-14 px-6 bg-white border-b border-gray-200 shrink-0">
<h1 class="text-base font-medium text-gray-700">{{ currentTitle }}</h1>
<div class="flex items-center gap-2">
<el-avatar :size="32" class="bg-blue-500">U</el-avatar>
</div>
</header>
<!-- 页面内容 -->
<main class="flex-1 overflow-hidden p-6">
<div class="h-full overflow-auto">
<router-view />
</div>
</main>
</div>
</div> </div>
<!-- 应用信息弹窗 -->
<el-dialog v-model="showAppInfo" :title="appStore.title" width="400">
<div class="p-4 text-center">
<p class="text-lg font-medium mb-2">欢迎使用 {{ appStore.title }}</p>
<p class="text-sm text-gray-500">这是一个基于 Electron Vue 3 的应用示例</p>
</div>
<template #footer>
<div class="flex justify-end">
<el-button type="primary" @click="showAppInfo = false">确定</el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useAppStore } from '@/stores/app'; import { useAppStore } from '@/stores/app';
import { House, Monitor, Expand, Fold, Edit, ChatDotRound, Search } from '@element-plus/icons-vue'; import {
House,
Monitor,
Expand,
Fold,
Edit,
ChatDotRound,
Search,
SemiSelect,
CopyDocument,
Close,
} from '@element-plus/icons-vue';
import LucideIcon from '../components/base/LucideIcon.vue';
const route = useRoute(); const route = useRoute();
const appStore = useAppStore(); const appStore = useAppStore();
const showAppInfo = ref(false);
const currentTitle = computed(() => route.meta?.title || appStore.title); const currentTitle = computed(() => route.meta?.title || appStore.title);
const handleMinimize = () => {
window.electronAPI.send('window:minimize');
};
const handleMaximize = () => {
window.electronAPI.send('window:maximize');
};
const handleClose = () => {
window.electronAPI.send('window:close');
};
</script> </script>

View File

@@ -7,6 +7,7 @@ import router from './router';
import App from './App.vue'; import App from './App.vue';
import './style.css'; import './style.css';
import AppIcon from './components/base/AppIcon.vue'; import AppIcon from './components/base/AppIcon.vue';
import LucideIcon from './components/base/LucideIcon.vue';
const app = createApp(App); const app = createApp(App);
// 注册所有 Element Plus 图标 // 注册所有 Element Plus 图标
@@ -16,6 +17,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
// 注册自定义图标组件 // 注册自定义图标组件
app.component('AppIcon', AppIcon); app.component('AppIcon', AppIcon);
app.component('LucideIcon', LucideIcon);
app.use(createPinia()); app.use(createPinia());
app.use(router); app.use(router);
app.use(ElementPlus); app.use(ElementPlus);

View File

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { ref } from 'vue'; import { ref } from 'vue';
export const useAppStore = defineStore('app', () => { export const useAppStore = defineStore('app', () => {
const title = ref('My App'); const title = ref('智聚超脑');
const collapsed = ref(false); const collapsed = ref(false);
function toggleSidebar() { function toggleSidebar() {

View File

@@ -111,6 +111,15 @@ body,
height: 100%; height: 100%;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
-webkit-app-region: drag;
}
button,
input,
textarea,
a,
.no-drag {
-webkit-app-region: no-drag;
} }
body { body {

View File

@@ -8,8 +8,8 @@
<span class="emoji">👋</span> <span class="emoji">👋</span>
</h1> </h1>
<p class="welcome-subtitle">准备好开始今天的创作了吗</p> <p class="welcome-subtitle">准备好开始今天的创作了吗</p>
<AppIcon :icon="User" color="red" /> <LucideIcon name="user" color="red" />
<AppIcon :icon="Rocket" :size="30" class="hover:text-red-500" /> <LucideIcon name="rocket" :size="30" class="hover:text-red-500" />
</div> </div>
</div> </div>
@@ -48,12 +48,7 @@
</div> </div>
</template> </template>
<div class="action-grid"> <div class="action-grid">
<div <div v-for="action in actions" :key="action.label" class="action-item" @click="action.onClick">
v-for="action in actions"
:key="action.label"
class="action-item"
@click="action.onClick"
>
<div class="action-icon" :style="{ background: action.color + '20' }"> <div class="action-icon" :style="{ background: action.color + '20' }">
<el-icon :size="28" :color="action.color"> <el-icon :size="28" :color="action.color">
<component :is="action.icon" /> <component :is="action.icon" />
@@ -76,12 +71,7 @@
</template> </template>
<el-scrollbar height="280px"> <el-scrollbar height="280px">
<div class="recent-list"> <div class="recent-list">
<div <div v-for="(item, index) in recents" :key="index" class="recent-item" @click="handleFileClick(item)">
v-for="(item, index) in recents"
:key="index"
class="recent-item"
@click="handleFileClick(item)"
>
<div class="file-icon"> <div class="file-icon">
<el-icon :size="20"><Document /></el-icon> <el-icon :size="20"><Document /></el-icon>
</div> </div>
@@ -101,7 +91,6 @@
<script setup> <script setup>
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { User, Rocket } from 'lucide-vue-next';
import { import {
Document, Document,
Plus, Plus,