electron-vite-chatgpt 客户端仿制chatgpt会话模板应用
Electron-Chatgpt 一款基于vite4.x构建 搭配electron25跨端技术实现的桌面端模仿chatgpt会话模式的聊天程序。
技术栈
- 编码工具:vscode
- 框架技术:electron25+vite4+vue3+pinia2
- 组件库:veplus (基于vue3自定义组件库)
- 打包工具:electron-builder^23.6.0
- 调试工具:electron-devtools-installer^3.2.0
- 代码高亮:highlight.js^11.7.0
- markdown组件:vue3-markdown-it
- 本地缓存:pinia-plugin-persistedstate^3.1.0
项目目录结构
布局模板结构
<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主入口配置
新建一个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()
})
使用vite-plugin-electron桥接插件,在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创建无边框窗体
创建窗口的时候,设置frame: false
后创建的窗口没有顶部导航,需要自定义组件实现导航条功能。
<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>
electron创建托盘
// 创建系统托盘图标
createTray() {
console.log('——+——+——Start Create Tray!')
console.log(__dirname)
console.log(join(process.env.ROOT, 'resource/tray.ico'))
const trayMenu = Menu.buildFromTemplate([
{
label: '打开主界面',
icon: join(process.env.ROOT, 'resource/home.png'),
click: () => {
try {
for(let i in this.group) {
let win = this.getWin(i)
if(!win) return
// 是否主窗口
if(this.group[i].isMainWin) {
if(win.isMinimized()) win.restore()
win.show()
}
}
} catch (error) {
console.log(error)
}
}
},
{
label: '设置中心',
icon: join(process.env.ROOT, 'resource/setting.png'),
click: () => {
for(let i in this.group) {
let win = this.getWin(i)
if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_SETTING', value: null })
}
},
},
{
label: '锁屏',
icon: join(process.env.ROOT, 'resource/lock.png'),
click: () => null,
},
{
label: '关闭托盘闪烁',
click: () => {
this.flashTray(false)
}
},
{type: 'separator'},
/* {
label: '重启',
click: () => {
// app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
// app.exit(0)
}
}, */
{
label: '关于',
click: () => {
for(let i in this.group) {
let win = this.getWin(i)
if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_ABOUT', value: null })
}
}
},
{
label: '关闭应用并退出',
icon: join(process.env.ROOT, 'resource/quit.png'),
click: () => {
dialog.showMessageBox(this.main, {
title: '询问',
message: '确定要退出应用程序吗?',
buttons: ['取消', '最小化托盘', '退出应用'],
type: 'error',
noLink: false, // true传统按钮样式 false链接样式
cancelId: 0
}).then(res => {
console.log(res)
const index = res.response
if(index == 0) {
console.log('取消')
}if(index == 1) {
console.log('最小化托盘')
for(let i in this.group) {
let win = this.getWin(i)
if(win) win.hide()
}
}else if(index == 2) {
console.log('退出应用')
try {
for(let i in this.group) {
let win = this.getWin(i)
if(win) win.webContents.send('win__ipcData', { type: 'WIN_LOGOUT', value: null })
}
// app.quit 和 app.exit(0) 都可退出应用。
// 前者可以被打断并触发一些事件,而后者将强制应用程序退出而不触发任何事件或允许应用程序取消操作。
app.quit()
} catch (error) {
console.log(error)
}
}
})
}
}
])
this.tray = new Tray(this.trayIco1)
this.tray.setContextMenu(trayMenu)
this.tray.setToolTip(app.name)
this.tray.on('double-click', () => {
console.log('double clicked')
})
// 开启托盘闪烁
// this.flashTray(true)
}
electron构建打包
新建一个electron-builder.json文件用作打包配置参数。
{
"productName": "Electron-ChatGPT",
"appId": "com.yxy.electron-chatgpt-vue3",
"copyright": "Copyright © 2023-present Andy",
"compression": "maximum",
"asar": true,
"directories": {
"output": "release/${version}"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": true,
"deleteAppDataOnUninstall": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "ElectronVite4Vue3"
},
"win": {
"icon": "./resource/shortcut.ico",
"artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}",
"target": [
{
"target": "nsis",
"arch": ["ia32"]
}
]
},
"mac": {
"icon": "./resource/shortcut.icns",
"artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
},
"linux": {
"icon": "./resource",
"artifactName": "${productName}-v${version}-${platform}-${arch}-setup.${ext}"
}
}
到这里electron+vue3开发跨端会话应用就介绍差不多了。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: