<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Electron-ChatGPT桌面端ChatGPT實例|electron25+vue3聊天AI模板EXE

      基于electron25+vite4+vue3仿制chatgpt客戶端聊天模板ElectronChatGPT

      electron-chatgpt 使用最新桌面端技術(shù)Electron25.x結(jié)合Vite4.x全家桶技術(shù)開發(fā)跨端模仿ChatGPT智能聊天程序模板。支持經(jīng)典+分欄兩種布局、暗黑+明亮主題模式,集成electron封裝多窗口及通訊功能。

      Electron35-DeepSeek桌面端AI系統(tǒng)|vue3.5+electron+arco客戶端ai模板

      Uniapp-DeepSeek跨三端AI助手|uniapp+vue3+deepseek-v3流式ai聊天模板

      vue3-webseek網(wǎng)頁版AI問答|Vite6+DeepSeek+Arco流式ai聊天打字效果

      技術(shù)棧

      • 編碼工具:vscode
      • 框架技術(shù):electron25+vite4+vue3+pinia2
      • 組件庫:veplus (基于vue3自定義組件庫)
      • 打包工具:electron-builder^23.6.0
      • 調(diào)試工具:electron-devtools-installer^3.2.0
      • 代碼高亮:highlight.js^11.7.0
      • markdown組件:vue3-markdown-it
      • 本地緩存:pinia-plugin-persistedstate^3.1.0
      • electron結(jié)合vite插件:vite-plugin-electron^0.11.2

      項目結(jié)構(gòu)

      基于electron最新版本融合vite4.x技術(shù)搭建模仿chatgpt桌面端程序。

      如果對electron+vite4創(chuàng)建跨端應(yīng)用及多開窗口感興趣,可以去看看之前的這兩篇分享文章。

      http://www.rzrgm.cn/xiaoyan2017/p/17436076.html

      http://www.rzrgm.cn/xiaoyan2017/p/17442502.html

      隨著electron快速迭代更新,加上vite極速編譯,二者配合創(chuàng)建的應(yīng)用運行速度超快。

      Vue3桌面UI組件庫

      考慮到項目比較輕量級,所以采用自研vue3組件庫ve-plus

      關(guān)于veplus組件庫這里不作過多介紹,之前有過一篇分享文章,大家可以去看看。

      http://www.rzrgm.cn/xiaoyan2017/p/17170454.html

      項目布局

      項目整體大致分為頂部導(dǎo)航工具欄+左側(cè)會話記錄/操作鏈接+右側(cè)會話區(qū)/編輯框等模塊。

      <template>
          <div class="vegpt__layout flexbox flex-col">
              <!-- //頂部工具欄 -->
              <Toolbar />
              
              <div class="ve__layout-body flex1 flexbox">
                  <!-- //側(cè)邊欄 -->
                  <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>
      
                  <!-- //主體區(qū)域 -->
                  <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()
      })

      使用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'
            })
          ],
          
          /*構(gòu)建選項*/
          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"] : []
          },
      
          /*開發(fā)服務(wù)器選項*/
          server: {
            // 端口
            port: env.VITE_PORT,
            // ...
          },
      
          resolve: {
            // 設(shè)置別名
            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", 入口。

      Electron自定義無邊框窗口工具欄

      創(chuàng)建窗口的時候配置 frame: false 參數(shù),創(chuàng)建的窗口則沒有系統(tǒng)頂部導(dǎo)航欄及邊框。拖拽區(qū)域/最大化/最小化及關(guān)閉按鈕均需要自定義操作。

      通過設(shè)置css3屬性 -webkit-app-region: drag ,則可對自定義區(qū)域進行拖拽操作,設(shè)置后按鈕/鏈接點擊則會失效,這時通過對按鈕或鏈接設(shè)置-webkit-app-region: no-drag就可恢復(fù)事件響應(yīng)。

      不過設(shè)置-webkit-app-region: drag,點擊鼠標右鍵,會出現(xiàn)上圖系統(tǒng)菜單,經(jīng)過一番調(diào)試,windows下可以暫時通過如下方法屏蔽右鍵菜單。

      // 屏蔽系統(tǒng)右鍵菜單
      win.hookWindowMessage(278, () => {
          win.setEnabled(false)
          setTimeout(() => {
              win.setEnabled(true)
          }, 100)
      
          return true
      })

      components/titlebar目錄自定義工具欄條。

      control.vue自定義最大化/最小化/關(guān)閉按鈕

      <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 },
              // 窗口是否可以關(guān)閉
              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
              })
          }
          // 關(guān)閉窗體
          const handleQuit = () => {
              if(winCfg.window.isMainWin) {
                  MessageBox.confirm('應(yīng)用提示', '是否最小化到托盤, 不退出程序?', {
                      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 操作按鈕,并支持自定義左側(cè)、標題等功能。

      <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>

      Electron創(chuàng)建系統(tǒng)托盤圖標

      // 創(chuàng)建系統(tǒng)托盤圖標
      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: '設(shè)置中心',
                  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: '關(guān)閉托盤閃爍',
                  click: () => {
                      this.flashTray(false)
                  }
              },
              {type: 'separator'},
              /* {
                  label: '重啟',
                  click: () => {
                      // app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
                      // app.exit(0)
                  }
              }, */
              {
                  label: '關(guān)于',
                  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: '關(guān)閉應(yīng)用并退出',
                  icon: join(process.env.ROOT, 'resource/quit.png'),
                  click: () => {
                      dialog.showMessageBox(this.main, {
                          title: '詢問',
                          message: '確定要退出應(yīng)用程序嗎?',
                          buttons: ['取消', '最小化托盤', '退出應(yīng)用'],
                          type: 'error',
                          noLink: false,  // true傳統(tǒng)按鈕樣式  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('退出應(yīng)用')
      
                              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) 都可退出應(yīng)用。
                                  // 前者可以被打斷并觸發(fā)一些事件,而后者將強制應(yīng)用程序退出而不觸發(fā)任何事件或允許應(yīng)用程序取消操作。
                                  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)
      }

      托盤圖標、右鍵菜單圖標及打包圖標均在resource目錄下。

      Electron打包腳本electron-builder

      在根目錄新建一個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主渲染進程通訊傳值

      由于electron主渲染進程一般都是單窗口之間進行傳值。如果需要在多個窗口間傳值,如切換主題功能,則需要在渲染進程發(fā)送請求,主進程監(jiān)聽后再發(fā)送請求給渲染進程(App.vue中監(jiān)聽)。

      <div
          class="toolbar__item"
          :title="`切換 暗黑/明亮 模式(當前 ${appState.config.isDark ? '暗黑' : '明亮'}模式)`"
          @click="changeMode"
      >
          <Icon :name="appState.config.isDark ? 've-icon-sunny' : 've-icon-yewan'" />
      </div>
      
      // 主題切換
      const changeMode = () => {
          appState.config.isDark = !appState.config.isDark
          ipcRenderer.send('win__postData', appState.config.isDark)
      }

      在主進程中使用ipcMain.on監(jiān)聽。

      // 主/渲染進程傳參
      ipcMain.on('win__postData', (event, args) => {
          mainWin.webContents.send('win__postData', args)
      })

      然后在渲染進程App.vue頁面監(jiān)聽并處理通訊傳值。

      /**
       * 接收主進程發(fā)送的事件
       */
      ipcRenderer.on('win__postData', (e, data) => {
          console.log('——+——+——receive multiwin data:', data)
      
          switch(data.type) {
              // 退出登錄
              case 'WIN_LOGOUT':
                  appState.$reset()
                  break;
              // 布局切換
              case 'CHANGE_LAYOUT':
                  appState.config.layout = data.value
                  break;
              // 切換主題
              case 'CHANGE_MODE':
                  appState.config.isDark = data.value
                  appState.changeDark()
                  break;
              // 側(cè)邊欄收縮
              case 'CHANGE_COLLAPSE':
                  appState.config.collapse = data.value
                  break;
            }
      })

      這樣就能簡單實現(xiàn)多窗口傳值了。如果大家有其他方法,歡迎一起交流學(xué)習(xí)哈~

      Ok,基于electron25+vue3開發(fā)桌面端仿chatgpt聊天實例就先分享到這里,希望對大家有所幫助??

      【2025】基于Vite6+DeepSeek從0到1實現(xiàn)AI流式聊天助手

      http://www.rzrgm.cn/xiaoyan2017/p/18773480

      最后附上幾個最新研發(fā)的跨端項目

      flutter3-dymall仿抖音直播商城|Flutter3.27短視頻+直播+聊天App實例

      uniapp+vue3酒店預(yù)訂|vite5+uniapp預(yù)約訂房系統(tǒng)模板(h5+小程序+App端)

      tauri2.0-admin桌面端后臺系統(tǒng)|Tauri2+Vite5+ElementPlus管理后臺EXE程序

      Electron32-ViteOS桌面版os系統(tǒng)|vue3+electron+arco客戶端OS管理模板

      Vite5+Electron聊天室|electron31跨平臺仿微信EXE客戶端|vue3聊天程序

       

      posted @ 2023-06-09 01:00  xiaoyan2017  閱讀(2657)  評論(4)    收藏  舉報
      友情鏈接: UP主小店B站
      主站蜘蛛池模板: 久热这里只精品视频99| 男人进女人下部全黄大色视频 | 亚洲国产成人午夜在线一区| 久久永久视频| 部精品久久久久久久久| 男女一级国产片免费视频| 国产愉拍91九色国产愉拍| 亚洲精品日韩在线观看| 中文成人无字幕乱码精品区| 久久国产精品成人影院| 峡江县| 国产精品天天看天天狠| 高潮潮喷奶水飞溅视频无码| 免费萌白酱国产一区二区三区| 免费天堂无码人妻成人av电影| 亚洲国产精品综合久久2007| 亚洲国产成人精品无码一区二区| 国产精成人品日日拍夜夜| 亚洲日韩性欧美中文字幕| 国产精品入口麻豆| 秋霞人妻无码中文字幕| 激情六月丁香婷婷四房播| 亚洲 日本 欧洲 欧美 视频| 国产人成亚洲第一网站在线播放| 免费无码成人AV片在线| 亚洲av综合久久成人网| 91区国产福利在线观看午夜| 在线国产精品中文字幕| 国产av仑乱内谢| 午夜福利影院不卡影院| 亚洲一区二区三区蜜桃臀| 亚洲中文字幕久久精品品| 最新亚洲av日韩av二区| 激情综合五月| 国产不卡精品视频男人的天堂| 亚洲午夜香蕉久久精品| 久久精品国产亚洲αv忘忧草 | 北条麻妃一区二区三区av高清| 国产老熟女伦老熟妇露脸| 国产美女直播亚洲一区色| 少妇人妻偷人精品免费视频|