zhuangyunsheng 6 mēneši atpakaļ
revīzija
7423846d9f

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+node_modules
+dist
+out
+.DS_Store
+.eslintcache
+*.log*
+
+package-lock.json

+ 2 - 0
.npmrc

@@ -0,0 +1,2 @@
+electron_mirror=https://npmmirror.com/mirrors/electron/
+electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

+ 6 - 0
.prettierignore

@@ -0,0 +1,6 @@
+out
+dist
+pnpm-lock.yaml
+LICENSE.md
+tsconfig.json
+tsconfig.*.json

+ 4 - 0
.prettierrc.yaml

@@ -0,0 +1,4 @@
+singleQuote: true
+semi: false
+printWidth: 100
+trailingComma: none

+ 3 - 0
.vscode/extensions.json

@@ -0,0 +1,3 @@
+{
+  "recommendations": ["dbaeumer.vscode-eslint"]
+}

+ 39 - 0
.vscode/launch.json

@@ -0,0 +1,39 @@
+{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Debug Main Process",
+      "type": "node",
+      "request": "launch",
+      "cwd": "${workspaceRoot}",
+      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
+      "windows": {
+        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
+      },
+      "runtimeArgs": ["--sourcemap"],
+      "env": {
+        "REMOTE_DEBUGGING_PORT": "9222"
+      }
+    },
+    {
+      "name": "Debug Renderer Process",
+      "port": 9222,
+      "request": "attach",
+      "type": "chrome",
+      "webRoot": "${workspaceFolder}/src/renderer",
+      "timeout": 60000,
+      "presentation": {
+        "hidden": true
+      }
+    }
+  ],
+  "compounds": [
+    {
+      "name": "Debug All",
+      "configurations": ["Debug Main Process", "Debug Renderer Process"],
+      "presentation": {
+        "order": 1
+      }
+    }
+  ]
+}

+ 11 - 0
.vscode/settings.json

@@ -0,0 +1,11 @@
+{
+  "[typescript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[javascript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[json]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  }
+}

+ 34 - 0
README.md

@@ -0,0 +1,34 @@
+# easydo-electron
+
+An Electron application with Vue
+
+## Recommended IDE Setup
+
+- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
+
+## Project Setup
+
+### Install
+
+```bash
+$ npm install
+```
+
+### Development
+
+```bash
+$ npm run dev
+```
+
+### Build
+
+```bash
+# For windows
+$ npm run build:win
+
+# For macOS
+$ npm run build:mac
+
+# For Linux
+$ npm run build:linux
+```

+ 12 - 0
build/entitlements.mac.plist

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+  <dict>
+    <key>com.apple.security.cs.allow-jit</key>
+    <true/>
+    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+    <true/>
+    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
+    <true/>
+  </dict>
+</plist>

BIN
build/icon.icns


BIN
build/icon.ico


BIN
build/icon.png


+ 3 - 0
dev-app-update.yml

@@ -0,0 +1,3 @@
+provider: generic
+url: https://example.com/auto-updates
+updaterCacheDirName: easydo-electron-updater

+ 44 - 0
electron-builder.yml

@@ -0,0 +1,44 @@
+appId: com.electron.app
+productName: easydo-electron
+directories:
+  buildResources: build
+files:
+  - '!**/.vscode/*'
+  - '!src/*'
+  - '!electron.vite.config.{js,ts,mjs,cjs}'
+  - '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
+  - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
+asarUnpack:
+  - resources/**
+win:
+  executableName: easydo-electron
+nsis:
+  artifactName: ${name}-${version}-setup.${ext}
+  shortcutName: ${productName}
+  uninstallDisplayName: ${productName}
+  createDesktopShortcut: always
+mac:
+  entitlementsInherit: build/entitlements.mac.plist
+  extendInfo:
+    - NSCameraUsageDescription: Application requests access to the device's camera.
+    - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
+    - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
+    - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
+  notarize: false
+dmg:
+  artifactName: ${name}-${version}.${ext}
+linux:
+  target:
+    - AppImage
+    - snap
+    - deb
+  maintainer: electronjs.org
+  category: Utility
+appImage:
+  artifactName: ${name}-${version}.${ext}
+npmRebuild: false
+publish:
+  provider: generic
+  url: https://example.com/auto-updates
+electronDownload:
+  mirror: https://npmmirror.com/mirrors/electron/

+ 20 - 0
electron.vite.config.mjs

@@ -0,0 +1,20 @@
+import { resolve } from "path"
+import { defineConfig, externalizeDepsPlugin } from "electron-vite"
+import vue from "@vitejs/plugin-vue"
+
+export default defineConfig({
+    main: {
+        plugins: [externalizeDepsPlugin()]
+    },
+    preload: {
+        plugins: [externalizeDepsPlugin()]
+    },
+    renderer: {
+        resolve: {
+            alias: {
+                "@renderer": resolve("src/renderer/src")
+            }
+        },
+        plugins: [vue()]
+    }
+})

+ 30 - 0
eslint.config.mjs

@@ -0,0 +1,30 @@
+import eslintConfig from "@electron-toolkit/eslint-config"
+import eslintConfigPrettier from "@electron-toolkit/eslint-config-prettier"
+import eslintPluginVue from "eslint-plugin-vue"
+import vueParser from "vue-eslint-parser"
+
+export default [
+    { ignores: ["**/node_modules", "**/dist", "**/out"] },
+    eslintConfig,
+    ...eslintPluginVue.configs["flat/recommended"],
+    {
+        files: ["**/*.vue"],
+        languageOptions: {
+            parser: vueParser,
+            parserOptions: {
+                ecmaFeatures: {
+                    jsx: true
+                },
+                extraFileExtensions: [".vue"]
+            }
+        }
+    },
+    {
+        files: ["**/*.{js,jsx,vue}"],
+        rules: {
+            "vue/require-default-prop": "off",
+            "vue/multi-word-component-names": "off"
+        }
+    },
+    eslintConfigPrettier
+]

+ 39 - 0
package.json

@@ -0,0 +1,39 @@
+{
+    "name": "easydo-electron",
+    "version": "1.0.0",
+    "description": "An Electron application with Vue",
+    "main": "./out/main/index.js",
+    "author": "example.com",
+    "homepage": "https://electron-vite.org",
+    "scripts": {
+        "format": "prettier --write .",
+        "lint": "eslint --cache .",
+        "start": "electron-vite preview",
+        "dev": "electron-vite dev",
+        "build": "electron-vite build",
+        "postinstall": "electron-builder install-app-deps",
+        "build:unpack": "npm run build && electron-builder --dir",
+        "build:win": "npm run build && electron-builder --win",
+        "build:mac": "npm run build && electron-builder --mac",
+        "build:linux": "npm run build && electron-builder --linux"
+    },
+    "dependencies": {
+        "@electron-toolkit/preload": "^3.0.1",
+        "@electron-toolkit/utils": "^4.0.0",
+        "electron-updater": "^6.3.9"
+    },
+    "devDependencies": {
+        "@electron-toolkit/eslint-config": "^2.0.0",
+        "@electron-toolkit/eslint-config-prettier": "^3.0.0",
+        "@vitejs/plugin-vue": "^5.2.3",
+        "electron": "^35.1.5",
+        "electron-builder": "^25.1.8",
+        "electron-vite": "^3.1.0",
+        "eslint": "^9.24.0",
+        "eslint-plugin-vue": "^10.0.0",
+        "prettier": "^3.5.3",
+        "vite": "^6.2.6",
+        "vue": "^3.5.13",
+        "vue-eslint-parser": "^10.1.3"
+    }
+}

BIN
resources/icon.png


+ 90 - 0
src/main/index.js

@@ -0,0 +1,90 @@
+import { app, shell, BrowserWindow, ipcMain } from "electron"
+import { join } from "path"
+import { electronApp, optimizer, is } from "@electron-toolkit/utils"
+import icon from "../../resources/icon.png?asset"
+
+let mainWindow = null
+
+function createWindow() {
+    // Create the browser window.
+    mainWindow = new BrowserWindow({
+        width: 900,
+        height: 670,
+        show: false,
+        autoHideMenuBar: true,
+        ...(process.platform === "linux" ? { icon } : {}),
+        webPreferences: {
+            preload: join(__dirname, "../preload/index.js"),
+            sandbox: false
+        }
+    })
+
+    mainWindow.on("ready-to-show", () => {
+        mainWindow.show()
+    })
+
+    mainWindow.webContents.setWindowOpenHandler(details => {
+        shell.openExternal(details.url)
+        return { action: "deny" }
+    })
+
+    // HMR for renderer base on electron-vite cli.
+    // Load the remote URL for development or the local html file for production.
+    if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
+        mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"])
+    } else {
+        mainWindow.loadFile(join(__dirname, "../renderer/index.html"))
+    }
+}
+
+const additionalData = { myKey: "myValue" }
+const gotTheLock = app.requestSingleInstanceLock(additionalData)
+if (!gotTheLock) {
+    app.quit()
+} else {
+    app.on("second-instance", (event, commandLine, workingDirectory, additionalData) => {
+        // 有人试图运行第二个实例,我们应该关注我们的窗口
+        if (mainWindow) {
+            if (mainWindow.isMinimized()) mainWindow.restore()
+            mainWindow.focus()
+        }
+    })
+
+    // This method will be called when Electron has finished
+    // initialization and is ready to create browser windows.
+    // Some APIs can only be used after this event occurs.
+    app.whenReady().then(() => {
+        // Set app user model id for windows
+        electronApp.setAppUserModelId("com.electron")
+    
+        // Default open or close DevTools by F12 in development
+        // and ignore CommandOrControl + R in production.
+        // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
+        app.on("browser-window-created", (_, window) => {
+            optimizer.watchWindowShortcuts(window)
+        })
+    
+        // IPC test
+        ipcMain.on("ping", () => console.log("pong"))
+    
+        createWindow()
+    
+        app.on("activate", function () {
+            // On macOS it's common to re-create a window in the app when the
+            // dock icon is clicked and there are no other windows open.
+            if (BrowserWindow.getAllWindows().length === 0) createWindow()
+        })
+    })
+
+    // Quit when all windows are closed, except on macOS. There, it"s common
+    // for applications and their menu bar to stay active until the user quits
+    // explicitly with Cmd + Q.
+    app.on("window-all-closed", () => {
+        if (process.platform !== "darwin") {
+            app.quit()
+        }
+    })
+    
+    // In this file you can include the rest of your app's specific main process
+    // code. You can also put them in separate files and require them here.
+}

+ 20 - 0
src/preload/index.js

@@ -0,0 +1,20 @@
+import { contextBridge } from "electron"
+import { electronAPI } from "@electron-toolkit/preload"
+
+// Custom APIs for renderer
+const api = {}
+
+// Use `contextBridge` APIs to expose Electron APIs to
+// renderer only if context isolation is enabled, otherwise
+// just add to the DOM global.
+if (process.contextIsolated) {
+    try {
+        contextBridge.exposeInMainWorld("electron", electronAPI)
+        contextBridge.exposeInMainWorld("api", api)
+    } catch (error) {
+        console.error(error)
+    }
+} else {
+    window.electron = electronAPI
+    window.api = api
+}

+ 17 - 0
src/renderer/index.html

@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+
+<head>
+    <meta charset="UTF-8" />
+    <title>Easydo-Electron</title>
+    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
+    <meta http-equiv="Content-Security-Policy"
+        content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:" />
+</head>
+
+<body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+</body>
+
+</html>

+ 26 - 0
src/renderer/src/App.vue

@@ -0,0 +1,26 @@
+<template>
+    <div style="display: flex">
+        <img alt="logo" class="logo" src="./assets/electron.png" />
+    </div>
+    <div class="creator">Powered by electron-vite</div>
+    <div class="text">
+        Build an Electron app with
+        <span class="vue">Vue</span>
+    </div>
+    <p class="tip">Please try pressing <code>F12</code> to open the devTool</p>
+    <div class="actions">
+        <div class="action">
+            <a href="https://electron-vite.org/" target="_blank" rel="noreferrer">Documentation</a>
+        </div>
+        <div class="action">
+            <a target="_blank" rel="noreferrer" @click="ipcHandle">Send IPC</a>
+        </div>
+    </div>
+    <Versions />
+</template>
+
+<script setup>
+import Versions from "./components/Versions.vue"
+
+const ipcHandle = () => window.electron.ipcRenderer.send("ping")
+</script>

+ 67 - 0
src/renderer/src/assets/base.css

@@ -0,0 +1,67 @@
+:root {
+  --ev-c-white: #ffffff;
+  --ev-c-white-soft: #f8f8f8;
+  --ev-c-white-mute: #f2f2f2;
+
+  --ev-c-black: #1b1b1f;
+  --ev-c-black-soft: #222222;
+  --ev-c-black-mute: #282828;
+
+  --ev-c-gray-1: #515c67;
+  --ev-c-gray-2: #414853;
+  --ev-c-gray-3: #32363f;
+
+  --ev-c-text-1: rgba(255, 255, 245, 0.86);
+  --ev-c-text-2: rgba(235, 235, 245, 0.6);
+  --ev-c-text-3: rgba(235, 235, 245, 0.38);
+
+  --ev-button-alt-border: transparent;
+  --ev-button-alt-text: var(--ev-c-text-1);
+  --ev-button-alt-bg: var(--ev-c-gray-3);
+  --ev-button-alt-hover-border: transparent;
+  --ev-button-alt-hover-text: var(--ev-c-text-1);
+  --ev-button-alt-hover-bg: var(--ev-c-gray-2);
+}
+
+:root {
+  --color-background: var(--ev-c-black);
+  --color-background-soft: var(--ev-c-black-soft);
+  --color-background-mute: var(--ev-c-black-mute);
+
+  --color-text: var(--ev-c-text-1);
+}
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+  margin: 0;
+  font-weight: normal;
+}
+
+ul {
+  list-style: none;
+}
+
+body {
+  min-height: 100vh;
+  color: var(--color-text);
+  background: var(--color-background);
+  line-height: 1.6;
+  font-family:
+    Inter,
+    -apple-system,
+    BlinkMacSystemFont,
+    'Segoe UI',
+    Roboto,
+    Oxygen,
+    Ubuntu,
+    Cantarell,
+    'Fira Sans',
+    'Droid Sans',
+    'Helvetica Neue',
+    sans-serif;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}

BIN
src/renderer/src/assets/electron.png


+ 163 - 0
src/renderer/src/assets/main.css

@@ -0,0 +1,163 @@
+@import './base.css';
+
+body {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  overflow: hidden;
+  background-image: url('./wavy-lines.svg');
+  background-size: cover;
+  user-select: none;
+}
+
+code {
+  font-weight: 600;
+  padding: 3px 5px;
+  border-radius: 2px;
+  background-color: var(--color-background-mute);
+  font-family:
+    ui-monospace,
+    SFMono-Regular,
+    SF Mono,
+    Menlo,
+    Consolas,
+    Liberation Mono,
+    monospace;
+  font-size: 85%;
+}
+
+#app {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+  margin-bottom: 80px;
+}
+
+.logo {
+  margin-bottom: 20px;
+  -webkit-user-drag: none;
+  height: 128px;
+  width: 128px;
+  will-change: filter;
+  transition: filter 300ms;
+}
+
+.logo:hover {
+  filter: drop-shadow(0 0 1.2em #6988e6aa);
+}
+
+.creator {
+  font-size: 14px;
+  line-height: 16px;
+  color: var(--ev-c-text-2);
+  font-weight: 600;
+  margin-bottom: 10px;
+}
+
+.text {
+  font-size: 28px;
+  color: var(--ev-c-text-1);
+  font-weight: 700;
+  line-height: 32px;
+  text-align: center;
+  margin: 0 10px;
+  padding: 16px 0;
+}
+
+.tip {
+  font-size: 16px;
+  line-height: 24px;
+  color: var(--ev-c-text-2);
+  font-weight: 600;
+}
+
+.vue {
+  background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff);
+  background-clip: text;
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  font-weight: 700;
+}
+
+.actions {
+  display: flex;
+  padding-top: 32px;
+  margin: -6px;
+  flex-wrap: wrap;
+  justify-content: flex-start;
+}
+
+.action {
+  flex-shrink: 0;
+  padding: 6px;
+}
+
+.action a {
+  cursor: pointer;
+  text-decoration: none;
+  display: inline-block;
+  border: 1px solid transparent;
+  text-align: center;
+  font-weight: 600;
+  white-space: nowrap;
+  border-radius: 20px;
+  padding: 0 20px;
+  line-height: 38px;
+  font-size: 14px;
+  border-color: var(--ev-button-alt-border);
+  color: var(--ev-button-alt-text);
+  background-color: var(--ev-button-alt-bg);
+}
+
+.action a:hover {
+  border-color: var(--ev-button-alt-hover-border);
+  color: var(--ev-button-alt-hover-text);
+  background-color: var(--ev-button-alt-hover-bg);
+}
+
+.versions {
+  position: absolute;
+  bottom: 30px;
+  margin: 0 auto;
+  padding: 15px 0;
+  font-family: 'Menlo', 'Lucida Console', monospace;
+  display: inline-flex;
+  overflow: hidden;
+  align-items: center;
+  border-radius: 22px;
+  background-color: #202127;
+  backdrop-filter: blur(24px);
+}
+
+.versions li {
+  display: block;
+  float: left;
+  border-right: 1px solid var(--ev-c-gray-1);
+  padding: 0 20px;
+  font-size: 14px;
+  line-height: 14px;
+  opacity: 0.8;
+  &:last-child {
+    border: none;
+  }
+}
+
+@media (max-width: 720px) {
+  .text {
+    font-size: 20px;
+  }
+}
+
+@media (max-width: 620px) {
+  .versions {
+    display: none;
+  }
+}
+
+@media (max-width: 350px) {
+  .tip,
+  .actions {
+    display: none;
+  }
+}

+ 25 - 0
src/renderer/src/assets/wavy-lines.svg

@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1422 800" opacity="0.3">
+  <defs>
+    <linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="oooscillate-grad">
+      <stop stop-color="hsl(206, 75%, 49%)" stop-opacity="1" offset="0%"></stop>
+      <stop stop-color="hsl(331, 90%, 56%)" stop-opacity="1" offset="100%"></stop>
+    </linearGradient>
+  </defs>
+  <g stroke-width="1" stroke="url(#oooscillate-grad)" fill="none" stroke-linecap="round">
+    <path d="M 0 448 Q 355.5 -100 711 400 Q 1066.5 900 1422 448" opacity="0.05"></path>
+    <path d="M 0 420 Q 355.5 -100 711 400 Q 1066.5 900 1422 420" opacity="0.11"></path>
+    <path d="M 0 392 Q 355.5 -100 711 400 Q 1066.5 900 1422 392" opacity="0.18"></path>
+    <path d="M 0 364 Q 355.5 -100 711 400 Q 1066.5 900 1422 364" opacity="0.24"></path>
+    <path d="M 0 336 Q 355.5 -100 711 400 Q 1066.5 900 1422 336" opacity="0.30"></path>
+    <path d="M 0 308 Q 355.5 -100 711 400 Q 1066.5 900 1422 308" opacity="0.37"></path>
+    <path d="M 0 280 Q 355.5 -100 711 400 Q 1066.5 900 1422 280" opacity="0.43"></path>
+    <path d="M 0 252 Q 355.5 -100 711 400 Q 1066.5 900 1422 252" opacity="0.49"></path>
+    <path d="M 0 224 Q 355.5 -100 711 400 Q 1066.5 900 1422 224" opacity="0.56"></path>
+    <path d="M 0 196 Q 355.5 -100 711 400 Q 1066.5 900 1422 196" opacity="0.62"></path>
+    <path d="M 0 168 Q 355.5 -100 711 400 Q 1066.5 900 1422 168" opacity="0.68"></path>
+    <path d="M 0 140 Q 355.5 -100 711 400 Q 1066.5 900 1422 140" opacity="0.75"></path>
+    <path d="M 0 112 Q 355.5 -100 711 400 Q 1066.5 900 1422 112" opacity="0.81"></path>
+    <path d="M 0 84 Q 355.5 -100 711 400 Q 1066.5 900 1422 84" opacity="0.87"></path>
+    <path d="M 0 56 Q 355.5 -100 711 400 Q 1066.5 900 1422 56" opacity="0.94"></path>
+  </g>
+</svg>

+ 13 - 0
src/renderer/src/components/Versions.vue

@@ -0,0 +1,13 @@
+<template>
+    <ul class="versions">
+        <li class="electron-version">Electron v{{ versions.electron }}</li>
+        <li class="chrome-version">Chromium v{{ versions.chrome }}</li>
+        <li class="node-version">Node v{{ versions.node }}</li>
+    </ul>
+</template>
+
+<script setup>
+import { reactive } from "vue"
+
+const versions = reactive({ ...window.electron.process.versions })
+</script>

+ 6 - 0
src/renderer/src/main.js

@@ -0,0 +1,6 @@
+import "./assets/main.css"
+
+import { createApp } from "vue"
+import App from "./App.vue"
+
+createApp(App).mount("#app")