基於electron25+vite4+vue3仿製chatgpt使用者端聊天模板ElectronChatGPT。
electron-chatgpt 使用最新桌面端技術Electron25.x結合Vite4.x全家桶技術開發跨端模仿ChatGPT智慧聊天程式模板。支援經典+分欄兩種佈局、暗黑+明亮主題模式,整合electron封裝多視窗及通訊功能。
關於veplus元件庫這裡不作過多介紹,之前有過一篇分享文章,大家可以去看看。
https://www.cnblogs.com/xiaoyan2017/p/17170454.html
專案整體大致分為頂部導航工具列+左側對談記錄/操作連結+右側對談區/編輯框等模組。
<template> <div class="vegpt__layout flexbox flex-col"> <!-- //頂部工具列 --> <Toolbar /> <div class="ve__layout-body flex1 flexbox"> <!-- //側邊欄 --> <div class="ve__layout-menus flexbox" :class="{'hidden': store.config.collapse}"> <aside class="ve__layout-aside flexbox flex-col"> <ChatNew /> <Scrollbar class="flex1" autohide size="4" gap="1"> <ChatList /> </Scrollbar> <ExtraLink /> <Collapse /> </aside> </div> <!-- //主體區域 --> <div class="ve__layout-main flex1 flexbox flex-col"> <Main /> </div> </div> </div> </template>
根目錄下新建 electron-main.js 作為主程序入口檔案。
/** * 主程序入口 * @author YXY */ const { app, BrowserWindow } = require('electron') const MultiWindow = require('./src/multiwindow') // 遮蔽安全警告 // ectron Security Warning (Insecure Content-Security-Policy) process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true' const createWindow = () => { let win = new MultiWindow() win.createWin({isMainWin: true}) } app.whenReady().then(() => { createWindow() app.on('activate', () => { if(BrowserWindow.getAllWindows().length === 0) createWindow() }) }) app.on('window-all-closed', () => { if(process.platform !== 'darwin') app.quit() })
使用electron的vite外掛,在vite.config.js中設定入口。
import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import electron from 'vite-plugin-electron' import { resolve } from 'path' import { parseEnv } from './src/utils/env' export default defineConfig(({ command, mode }) => { const viteEnv = loadEnv(mode, process.cwd()) const env = parseEnv(viteEnv) return { plugins: [ vue(), electron({ // 主程序入口檔案 entry: 'electron-main.js' }) ], /*構建選項*/ build: { /* minify: 'esbuild', // 打包方式 esbuild(打包快)|terser chunkSizeWarningLimit: 2000, // 打包大小警告 rollupOptions: { output: { chunkFileNames: 'assets/js/[name]-[hash].js', entryFileNames: 'assets/js/[name]-[hash].js', assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', } } */ // 如果打包方式是terser,則設定如下 /* minify: "terser", terserOptions: { compress: { // 去掉所有console和debugger // drop_console: true, // drop_debugger: true, drop_console: command !== 'serve', drop_debugger: command !== 'serve', //pure_funcs:['console.log'] // 移除console.log } } */ }, esbuild: { // 打包去除 console.log 和 debugger drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console", "debugger"] : [] }, /*開發伺服器選項*/ server: { // 埠 port: env.VITE_PORT, // ... }, resolve: { // 設定別名 alias: { '@': resolve(__dirname, 'src'), '@assets': resolve(__dirname, 'src/assets'), '@components': resolve(__dirname, 'src/components'), '@views': resolve(__dirname, 'src/views') } } } })
需要注意:由於目前Electron 尚未支援 "type": "module",需要在package.json中去掉,並且設定 "main": "electron-main.js", 入口。
建立視窗的時候設定 frame: false 引數,建立的視窗則沒有系統頂部導航欄及邊框。拖拽區域/最大化/最小化及關閉按鈕均需要自定義操作。
通過設定css3屬性 -webkit-app-region: drag ,則可對自定義區域進行拖拽操作,設定後按鈕/連結點選則會失效,這時通過對按鈕或連結設定-webkit-app-region: no-drag就可恢復事件響應。
不過設定-webkit-app-region: drag,點選滑鼠右鍵,會出現上圖系統選單,經過一番偵錯,windows下可以暫時通過如下方法遮蔽右鍵選單。
// 遮蔽系統右鍵選單 win.hookWindowMessage(278, () => { win.setEnabled(false) setTimeout(() => { win.setEnabled(true) }, 100) return true })
在components/titlebar目錄自定義工具列條。
control.vue自定義最大化/最小化/關閉按鈕
<template> <div class="vegpt__control ve__nodrag"> <div class="vegpt__control-btns" :style="{'color': color}"> <slot /> <div v-if="isTrue(minimizable)" class="btn win-btn win-min" @click="handleMin"><i class="iconfont ve-icon-minimize"></i></div> <div v-if="isTrue(maximizable) && winCfg.window.resizable" class="btn win-btn win-maxmin" @click="handleRestore"> <i class="iconfont" :class="isMaximized ? 've-icon-maxrestore' : 've-icon-maximize'"></i> </div> <div v-if="isTrue(closable)" class="btn win-btn win-close" @click="handleQuit"><i class="iconfont ve-icon-close"></i></div> </div> </div> </template>
<template> <div class="vegpt__control ve__nodrag"> <div class="vegpt__control-btns" :style="{'color': color}"> <slot /> <div v-if="isTrue(minimizable)" class="btn win-btn win-min" @click="handleMin"><i class="iconfont ve-icon-minimize"></i></div> <div v-if="isTrue(maximizable) && winCfg.window.resizable" class="btn win-btn win-maxmin" @click="handleRestore"> <i class="iconfont" :class="isMaximized ? 've-icon-maxrestore' : 've-icon-maximize'"></i> </div> <div v-if="isTrue(closable)" class="btn win-btn win-close" @click="handleQuit"><i class="iconfont ve-icon-close"></i></div> </div> </div> </template> <script setup> import { onMounted, ref } from 'vue' import { winCfg, setWin } from '@/multiwindow/actions' import { appStore } from '@/pinia/modules/app' import { isTrue } from '@/utils' const appState = appStore() const props = defineProps({ // 標題顏色 color: String, // 視窗是否可以最小化 minimizable: { type: [Boolean, String], default: true }, // 視窗是否可以最大化 maximizable: { type: [Boolean, String], default: true }, // 視窗是否可以關閉 closable: { type: [Boolean, String], default: true } }) // 是否最大化 let isMaximized = ref(false) onMounted(() => { window.electronAPI.invoke('win__isMaximized').then(data => { console.log(data) isMaximized.value = data }) window.electronAPI.receive('win__hasMaximized', (e, data) => { console.log(data) isMaximized.value = data }) }) // 最小化 const handleMin = () => { window.electronAPI.send('win__minimize') } // 最大化/還原 const handleRestore = () => { window.electronAPI.invoke('win__max2min').then(data => { console.log(data) isMaximized.value = data }) } // 關閉表單 const handleQuit = () => { if(winCfg.window.isMainWin) { MessageBox.confirm('應用提示', '是否最小化到托盤, 不退出程式?', { type: 'warning', cancelText: '最小化至托盤', confirmText: '殘忍退出', confirmType: 'danger', width: 300, callback: action => { if(action == 'confirm') { appState.$reset() setWin('close') }else if(action == 'cancel') { setWin('hide', winCfg.window.id) } } }) }else { setWin('close', winCfg.window.id) } } </script>
在 index.vue 中引入 control.vue 操作按鈕,並支援自定義左側、標題等功能。
<template> <div class="vegpt__titlebar" :class="{'fixed': isTrue(fixed), 'transparent fixed': isTrue(transparent)}"> <div class="vegpt__titlebar-wrapper flexbox flex-alignc ve__drag" :style="{'background': bgcolor, 'color': color, 'z-index': zIndex}"> <slot name="left"> <img src="/logo.png" height="20" style="margin-left: 10px;" /> </slot> <div class="vegpt__titlebar-title" :class="{'center': isTrue(center)}"> <slot name="title">{{ title || winCfg.window.title || env.VITE_APPTITLE }}</slot> </div> <!-- 控制按鈕 --> <Control :minimizable="minimizable" :maximizable="maximizable" :closable="closable"> <slot name="btn" /> </Control> </div> </div> </template>
托盤圖示、右鍵選單圖示及打包圖示均在resource目錄下。