caner 1 year ago
parent
commit
cc9a1130fa

+ 1 - 0
.env

@@ -0,0 +1 @@
+VITE_PROXY_URL=http://114.114.114.114:28181

+ 5 - 0
.eslintignore

@@ -0,0 +1,5 @@
+# 排除eslint检查文件
+/dist
+/public
+/dist-electron
+/electron

+ 44 - 0
.eslintrc.json

@@ -0,0 +1,44 @@
+{
+  "root": true,
+  "env": {
+    "node": true,
+    "browser": true,
+    "es2021": true
+  },
+  "parserOptions": {
+    "ecmaVersion": "latest",
+    "parser": "@typescript-eslint/parser",
+    "sourceType": "module"
+  },
+  "extends": [
+    "plugin:vue/vue3-recommended",
+    "airbnb-base"
+  ],
+  "rules": {
+    "no-console": 0, // 禁用打印
+    "comma-dangle": [ 2, "never" ], // 禁止使用拖尾逗号
+    "no-extra-semi": 2, // 禁止不必要的分号
+    "array-bracket-spacing": [ 2, "always" ], // 指定数组的元素之间要以空格隔开
+    "jsx-quotes": 0, // 强制使用单引号
+    "max-len": 0, // 强制一行的最大长度
+    "semi": [ 2, "never" ], // 禁止使用分号
+    "no-unused-vars": 0,
+    "no-unneeded-ternary": 2, // 禁止不必要的嵌套 var isYes = answer === 1 ? true : false;
+    "no-unreachable": 2, // 不能有无法执行的代码
+    "no-unused-expressions": 1, // 禁止无用的表达式
+    "linebreak-style": [ 0, "error", "windows" ],
+    "import/no-unresolved": 0,
+    "import/extensions": 0,
+    "vue/multi-word-component-names": 0,
+    "no-param-reassign": 0,
+    "no-plusplus": 0,
+    "vue/v-on-event-hyphenation": 0,
+    "no-promise-executor-return": 0,
+    "no-shadow": 0,
+    "prefer-destructuring": 0,
+    "no-new": 0,
+    "func-names": 0,
+    "no-nested-ternary": 0,
+    "import/no-extraneous-dependencies": 0 
+  }
+}

+ 30 - 0
.gitignore

@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+yarn.lock
+dist
+package-lock.json
+dist-ssr
+dist-electron
+out

+ 10 - 3
README.md

@@ -1,3 +1,10 @@
-# termcap
-
-electron-ssh 工具
+# SFTP ssh 工具
+```
+本版本为vue3+ts+eslint+electron 自动修正
+```
+## 注意
+```
+1. yarn
+2. npm run dev 启动
+3. npm run build 打包
+```

+ 12 - 0
electron/beforePack.js

@@ -0,0 +1,12 @@
+exports.default = function (context) {
+    const rootPkg = require('../package.json')
+    const package = {
+        "name": rootPkg.name,
+        "version": rootPkg.version,
+        "main": rootPkg.main,
+        "author": rootPkg.author,
+        "description": rootPkg.description,
+        "homepage": rootPkg.homepage
+    }
+    require('fs').writeFileSync('dist/package.json', JSON.stringify(package), { encoding: 'utf-8' })
+}

BIN
electron/icon/playGame.ico


BIN
electron/icon/playGame.png


BIN
electron/icon/playGame@1x.png


BIN
electron/icon/playGame@2x.png


+ 137 - 0
electron/main.js

@@ -0,0 +1,137 @@
+const { app, BrowserWindow, Menu, ipcMain, globalShortcut, screen, Tray } = require('electron');
+const { join } = require('path');
+const { platform } = require('process');
+class MainSerivce {
+  constructor() {
+    Menu.setApplicationMenu(null) // 去掉菜单栏
+    app.commandLine.appendSwitch('wm-window-animations-disabled') // 拖动闪屏
+    app.commandLine.appendSwitch('autoplay-policy', 'no-user-gesture-required');
+    this.loadingWin = null
+    this.mainWin = null
+    this.icon = join(__dirname, './icon/playGame.png')
+    this.contrlEvent = null
+    if (!app.requestSingleInstanceLock({ key: 'contrl' })) {
+      app.quit()
+    } else {
+      app.on('ready', this.onRead.bind(this))
+      app.on('activate', (e, isVisible) => {
+        if (!isVisible && this.mainWin) {
+          // 兼容Mac dock 栏点击
+          this.mainWin.show()
+        }
+      })
+      app.on('window-all-closed', () => app.quit())
+    }
+  }
+
+  createLoading() {
+    const { size: { width, height } } = screen.getPrimaryDisplay()
+    this.loadingWin = new BrowserWindow({
+      frame: false, // 无边框(窗口、工具栏等),只包含网页内容
+      width,
+      height,
+      resizable: false,
+      center: true,
+      icon: this.icon,
+      alwaysOnTop: true,
+      transparent: true // 窗口是否支持透明,如果想做高级效果最好为true
+    })
+    if (app.isPackaged) {
+      this.loadingWin.loadFile(join(__dirname, './loading.html'))
+    } else {
+      this.loadingWin.loadFile('loading.html')
+    }
+    this.loadingWin.on('close', () => {
+      this.loadingWin = null
+    })
+  }
+
+  createWindow() {
+    this.mainWin = new BrowserWindow({
+      minWidth: 1300,
+      minHeight: 760,
+      width: 1300,
+      height: 760,
+      frame: false,
+      transparent: true,
+      icon: this.icon,
+      webPreferences: {
+        contextIsolation: true,
+        nodeIntegration: true,
+        webSecurity: false, // 去掉跨越
+        nodeIntegrationInWorker: true,
+        preload: join(__dirname, './preload.js')
+      },
+      show: false
+    })// 创建一个窗口
+
+    // 不同环境加载不同文件
+    if (app.isPackaged) {
+      this.mainWin.loadFile(join(__dirname, './index.html'))
+    } else {
+      this.mainWin.loadURL('http://localhost:6547/')
+    }
+
+    // 事件监听
+    this.mainWin.on('close', () => { this.mainWin = null })
+  }
+
+  onRead() {
+    this.createLoading()
+    this.createWindow()
+
+    // 图标
+    const tray = new Tray(this.icon)
+    const contextMenu = Menu.buildFromTemplate([
+      {
+        label: '退出',
+        click: () => {
+          this.mainWin.close()
+          app.quit()
+        }
+      }
+    ])
+    tray.setToolTip('demo')
+    tray.on('click', () => this.mainWin.show())
+
+    // 注册调试模式
+    globalShortcut.register('Ctrl+F12', () => {
+      this.mainWin.webContents.toggleDevTools()
+    })
+
+    // 系统环境
+    if (platform === 'win32') {
+      // 右键
+      tray.setContextMenu(contextMenu)
+      // 禁用右键
+      this.mainWin.hookWindowMessage(278, () => {
+        this.mainWin.setEnabled(false);//窗口禁用
+        setTimeout(() => {
+          this.mainWin.setEnabled(true);
+        }, 100) //延时太快会立刻启动,太慢会妨碍窗口其他操作,可自行测试最佳时间
+        return true
+      })
+    } else if (platform === 'darwin') {
+      app.dock.setIcon(join(__dirname, 'electron/icon/playGame@2x.png'))
+    }
+
+    // 通信
+    ipcMain.on('signal', (_, evt, data) => {
+      if (evt === 'close-loading') {
+        if (this.loadingWin) this.loadingWin.close()
+        this.mainWin.show()
+      } else if (evt === 'minWin') {
+        this.mainWin.minimize()
+      } else if (evt === 'closeWin') {
+        this.mainWin.close()
+      } else if (evt === 'maxWin') {
+        const { width, height } = screen.getPrimaryDisplay().size
+        if (data) { this.mainWin.setBounds({ x: 0, y: 0, width, height }) } else {
+          this.mainWin.setBounds({ width: 1300, height: 760 })
+          this.mainWin.center()
+        }
+      }
+    })
+  }
+};
+new MainSerivce()

+ 7 - 0
electron/preload.js

@@ -0,0 +1,7 @@
+const { contextBridge, ipcRenderer } = require('electron');
+
+contextBridge.exposeInMainWorld('$electron', {
+  send: (event, args) => ipcRenderer.send('signal', event, args),
+  once: (event, callBack) => ipcRenderer.once(event, (_, args) => callBack(args)),
+  on: (event, callBack) => ipcRenderer.on(event, (_, args) => callBack(args))
+})

+ 11 - 0
env.d.ts

@@ -0,0 +1,11 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+  import type { DefineComponent } from 'vue'
+  const component: DefineComponent<{}, {}, any>
+  export default component
+}
+
+declare interface Window {
+  $electron: Any
+}

+ 15 - 0
index.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8" />
+  <title>Contrl</title>
+  <style>*{margin: 0;padding: 0;}</style>
+</head>
+
+<body>
+  <div id="app"></div>
+  <script type="module" src="./src/main.ts"></script>
+</body>
+
+</html>

+ 58 - 0
loading.html

@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        html,body{
+            margin: 0;
+            padding: 0;
+            width: 100vw;
+            height: 100vh;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            box-sizing: border-box;
+            overflow: hidden;
+            margin-bottom: 10%;
+        }
+        .loader {
+            border: 5px solid #f3f3f3;
+            border-radius: 50%;
+            border-top: 5px solid #3498db;
+            width: 30px;
+            height: 30px;
+            -webkit-animation: spin 2s linear infinite;
+            animation: spin 2s linear infinite;
+            margin: auto auto;
+        }
+
+        @-webkit-keyframes spin {
+            0% {
+                -webkit-transform: rotate(0deg);
+            }
+
+            100% {
+                -webkit-transform: rotate(360deg);
+            }
+        }
+
+        @keyframes spin {
+            0% {
+                transform: rotate(0deg);
+            }
+
+            100% {
+                transform: rotate(360deg);
+            }
+        }
+    </style>
+</head>
+
+<body>
+    <div class="loader"></div>
+</body>
+
+</html>

+ 55 - 0
package.json

@@ -0,0 +1,55 @@
+{
+  "name": "contrl",
+  "version": "0.1.0",
+  "main": "electron/main.js",
+  "author": "Caner <5658514@qq.com> (https://git.caner.top/Caner/electron-vue-vite)",
+  "description": "demo",
+  "homepage": "https://caner.top",
+  "scripts": {
+    "dev": "vite",
+    "buildElectronFile": "esbuild electron/main.js electron/preload.js --format=cjs --outdir=dist/electron/ --bundle --external:electron --platform=node --minify;",
+    "buildMoveFile": "cp -Force -R electron/icon dist/electron/; cp -Force -R loading.html dist/electron/;",
+    "build": "vue-tsc --noEmit; vite build; yarn buildElectronFile; yarn buildMoveFile; electron-builder build;",
+    "test": "electron-builder build"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "@types/node": "^18.15.3",
+    "@typescript-eslint/parser": "^5.40.0",
+    "@vitejs/plugin-vue": "^4.2.3",
+    "electron": "^25.3.0",
+    "electron-builder": "^23.6.0",
+    "eslint": "^8.40.0",
+    "eslint-config-airbnb-base": "^15.0.0",
+    "eslint-plugin-import": "^2.27.5",
+    "eslint-plugin-vue": "^9.13.0",
+    "sass": "^1.63.3",
+    "typescript": "~5.0.4",
+    "vite": "^4.3.6",
+    "vite-electron-plugin": "^0.8.2",
+    "vite-plugin-eslint": "^1.8.1",
+    "vite-plugin-svg-icons": "^2.0.1",
+    "vue": "^3.3.2",
+    "vue-router": "^4.2.0",
+    "vue-tsc": "^1.6.5"
+  },
+  "build": {
+    "appId": "com.caner.demo",
+    "npmRebuild": false,
+    "productName": "demo",
+    "directories": {
+      "output": "out"
+    },
+    "files": [
+      "!**/*",
+      {
+        "from": "dist/",
+        "to": "./"
+      }
+    ],
+    "asar": true,
+    "icon": "electron/icon/",
+    "electronVersion": "25.3.0",
+    "beforePack": "electron/beforePack.js"
+  }
+}

+ 6 - 0
src/App.vue

@@ -0,0 +1,6 @@
+<template>
+  <router-view />
+</template>
+<script setup lang='ts'>
+window.$electron.send('close-loading')
+</script>

+ 3 - 0
src/assets/icons/close.svg

@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.71148 6.00003L9.89087 2.82065L9.17852 2.10829L5.99916 5.28765L2.81978 2.10828L2.10743 2.82063L5.28677 5.99998L2.10742 9.17933L2.81978 9.89169L5.99913 6.71234L9.17849 9.8917L9.89081 9.17938L6.71148 6.00003Z" />
+</svg>

+ 26 - 0
src/components/icon.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps<{
+  name: string,
+  size?: number,
+  color?: string
+}>()
+const symbolId = computed(() => `#icon-${props.name}`)
+const newColor = computed(() => `${props.color ?? '#ccc'}`)
+const newSize = computed(() => `${props.size ?? 16}`)
+</script>
+<template>
+  <svg
+    aria-hidden="true"
+    :font-size="newSize"
+    :width="newSize"
+    :height="newSize"
+  >
+
+    <use
+      :href="symbolId"
+      :fill="newColor"
+    />
+  </svg>
+</template>

+ 24 - 0
src/components/loading.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="loader" />
+</template>
+<style scoped>
+.loader {
+  border: 5px solid #f3f3f3;
+  border-radius: 50%;
+  border-top: 5px solid #3498db;
+  width: 30px;
+  height: 30px;
+  -webkit-animation: spin 2s linear infinite;
+  animation: spin 2s linear infinite;
+}
+
+@-webkit-keyframes spin {
+  0% { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(360deg); }
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+</style>

+ 23 - 0
src/main.ts

@@ -0,0 +1,23 @@
+import { createApp } from 'vue'
+import { createRouter, RouteRecordRaw, createWebHistory } from 'vue-router'
+import App from './App.vue'
+import Icon from '@/components/icon.vue'
+import 'virtual:svg-icons-register'
+import '@/services/rem'
+// 动态路由
+const routes = Object.values(import.meta.glob('./views/*/route.ts', { eager: true, import: 'default' })) as unknown as RouteRecordRaw[]
+routes.push({ path: '/:path(.*)', redirect: '/' })
+
+const app = createApp(App)
+const router = createRouter({
+  history: createWebHistory(),
+  routes
+})
+app.component('Icon', Icon)
+// 路由守卫
+// router.beforeEach((to, from, next) => {
+//   // do something
+//   next()
+// })
+
+app.use(router).mount('#app')

+ 19 - 0
src/services/rem.ts

@@ -0,0 +1,19 @@
+// 18fontsize = (18/100)rem
+(function (doc, win) {
+  const docEl = doc.documentElement
+  const head = docEl.getElementsByTagName('head')[0]
+  const resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize'
+  const metaEl = doc.createElement('meta')
+  metaEl.name = 'viewport'
+  metaEl.content = 'initial-scale=1,maximum-scale=1, minimum-scale=1'
+  head.appendChild(metaEl)
+  const recalc = function () {
+    // 1rem =100px
+    const width = docEl.clientWidth
+    docEl.style.fontSize = `${100 * (width / 1920)}px`
+  }
+  recalc()
+  if (!doc.addEventListener) { return }
+  win.addEventListener(resizeEvt, recalc, false)
+  console.log('rem自适应')
+}(document, window))

+ 16 - 0
src/views/index/index.vue

@@ -0,0 +1,16 @@
+<script setup lang="ts">
+</script>
+<template>
+  <div>
+    <icon
+      name="close"
+      :size="20"
+    />
+  </div>
+</template>
+<style lang="scss" scoped>
+  div{
+    background: red;
+    -webkit-app-region: drag;
+  }
+</style>

+ 9 - 0
src/views/index/route.ts

@@ -0,0 +1,9 @@
+import home from './index.vue'
+
+export default {
+  path: '/',
+  meta: {
+    authorize: true
+  },
+  component: home
+}

+ 35 - 0
tsconfig.json

@@ -0,0 +1,35 @@
+{
+  "compilerOptions": {
+      "target": "ES2020",
+      "useDefineForClassFields": true,
+      "module": "ESNext",
+      "lib": ["ES2020", "DOM", "DOM.Iterable"],
+      "skipLibCheck": true,
+  
+      /* Bundler mode */
+      "moduleResolution": "bundler",
+      "allowImportingTsExtensions": true,
+      "resolveJsonModule": true,
+      "isolatedModules": true,
+      "noEmit": true,
+      "jsx": "preserve",
+  
+      /* Linting */
+      "strict": true,
+      "noUnusedLocals": true,
+      "noUnusedParameters": true,
+      "noFallthroughCasesInSwitch": true
+  },
+  "include": [
+    "*.d.ts",
+    "src/**/*.ts",
+    "src/**/*.d.ts",
+    "src/**/*.tsx",
+    "src/**/*.vue"
+  ],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    }
+  ]
+}

+ 9 - 0
tsconfig.node.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "composite": true,
+    "module": "ESNext",
+    "moduleResolution": "Node",
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 61 - 0
vite.config.ts

@@ -0,0 +1,61 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import eslint from 'vite-plugin-eslint'
+import { resolve } from 'path'
+import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
+import electron from 'vite-electron-plugin'
+
+export default () => defineConfig({
+  base: './',
+  resolve: {
+    alias: {
+      '@': resolve(__dirname, './src')
+    }
+  },
+  plugins: [
+    vue(),
+    eslint({ fix: true, include: ['**/*.ts', '**/*.vue'] }),
+    createSvgIconsPlugin({
+      iconDirs: [resolve(__dirname, './src/assets/icons')],
+      // Specify symbolId format
+      symbolId: 'icon-[dir]-[name]'
+    }),
+    electron({
+      include: ['electron'],
+      outDir: 'dist/electron/'
+    }),
+  ],
+  server: {
+    host: '0.0.0.0',
+    port: 6547,
+    open: false,
+    strictPort: false,
+    https: false,
+  },
+  // esbuild: {
+  //   drop: ['console', 'debugger'] // build 移除打印
+  // },
+  build: {
+    rollupOptions: {
+      input: {
+        index: resolve(__dirname, 'index.html')
+      },
+      output: { // 静态资源分类打包
+        chunkFileNames: 'js/[hash].js',
+        entryFileNames: 'js/[hash].js',
+        assetFileNames: 'assets/[ext]/[hash].[ext]',
+        // 拆分node_modules包
+        manualChunks: (id) => {
+          if (id.includes('node_modules')) {
+            return id.toString().split('node_modules/')[1].split('/')[0].toString()
+          }
+          return ''
+        }
+      }
+    },
+    outDir:'dist/electron/'
+  },
+  define: {
+    __VUE_OPTIONS_API__: false
+  }
+})