commit
2555b99a5d
81 changed files with 13851 additions and 0 deletions
@ -0,0 +1,14 @@ |
|||
{ |
|||
"comments": false, |
|||
"env": { |
|||
"main": { |
|||
"presets": [ |
|||
["@babel/preset-env", { |
|||
"targets": { "node": 7 } |
|||
}], |
|||
"@babel/preset-typescript" |
|||
] |
|||
} |
|||
}, |
|||
"plugins": ["@babel/plugin-transform-runtime"] |
|||
} |
@ -0,0 +1,14 @@ |
|||
# http://editorconfig.org |
|||
root = true # 应在文件顶部指定的特殊属性,true表示停止搜索当前文件 |
|||
|
|||
[*] |
|||
charset = utf-8 # 统一字符格式 |
|||
indent_style = space # 缩进方式为空格 |
|||
indent_size = 2 # 缩进为两个空格 |
|||
end_of_line = lf # 换行符统一用lf |
|||
insert_final_newline = true # 保证文件结尾留一个空行 |
|||
trim_trailing_whitespace = true # 移除新一行前的空白字符 |
|||
|
|||
[*.md] |
|||
insert_final_newline = false |
|||
trim_trailing_whitespace = false |
@ -0,0 +1,2 @@ |
|||
# vite 服务器端口 |
|||
PORT=3344 |
@ -0,0 +1,5 @@ |
|||
node_modules |
|||
out |
|||
.idea |
|||
.vscode |
|||
dist/electron |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"tabWidth": 2, |
|||
"useTabs": false, |
|||
"semi": false, |
|||
"singleQuote": false, |
|||
"TrailingCooma": "all", |
|||
"bracketSpacing": true, |
|||
"jsxBracketSameLine": false, |
|||
"arrowParens": "avoid", |
|||
"printWidth": 140 |
|||
} |
@ -0,0 +1,15 @@ |
|||
https://www.jianshu.com/p/4699b825d285 |
|||
|
|||
|
|||
// // 指定了就出不来了,官网的解释是:No need to specify which files to include in the app |
|||
// "files": [ |
|||
// "dist/electron/**/*" |
|||
// ], |
|||
|
|||
|
|||
https://www.wyr.me/post/680 |
|||
|
|||
<!-- 切换 --> |
|||
https://github.com/webpack/webpack/issues/11767 |
|||
|
|||
https://vite-rollup-plugins.patak.dev/ |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"name": "my-electron-app", |
|||
"version": "1.0.0", |
|||
"lockfileVersion": 1 |
|||
} |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"name": "my-electron-app", |
|||
"version": "1.0.0", |
|||
"description": "description", |
|||
"main": "./electron/entry.js", |
|||
"scripts": {}, |
|||
"keywords": [], |
|||
"author": "TopOne", |
|||
"license": "ISC", |
|||
"devDependencies": {}, |
|||
"dependencies": {} |
|||
} |
@ -0,0 +1,26 @@ |
|||
const { app, BrowserWindow } = require('electron') |
|||
const path = require('path') |
|||
|
|||
function createWindow () { |
|||
const win = new BrowserWindow({ |
|||
width: 800, |
|||
height: 600, |
|||
webPreferences: { |
|||
preload: path.join(__dirname, 'preload.js') |
|||
} |
|||
}) |
|||
|
|||
win.loadFile('index.html') |
|||
} |
|||
|
|||
app.whenReady().then(() => { |
|||
createWindow() |
|||
app.on('activate', function () { |
|||
if (BrowserWindow.getAllWindows().length === 0) createWindow() |
|||
}) |
|||
}) |
|||
|
|||
app.on('window-all-closed', function () { |
|||
if (process.platform !== 'darwin') app.quit() |
|||
}) |
|||
|
File diff suppressed because it is too large
@ -0,0 +1,140 @@ |
|||
{ |
|||
"name": "my-electron-app", |
|||
"version": "1.0.0", |
|||
"description": "description", |
|||
"main": "dist/electron/entry.js", |
|||
"scripts": { |
|||
"dev": "node script/run.js", |
|||
"build": "node script/run.js --build", |
|||
"build:in": "npm run build:vite && node script/build.js && npm run package", |
|||
"buildaa": "npm run build:vite && ts-node -r tsconfig-paths/register script/build --env=production && npm run package", |
|||
"webpack": "node script/webpack/runMain.js", |
|||
"deva": "ts-node -r tsconfig-paths/register script/dev-runner --env=development --watch", |
|||
"devaa": "npm run dev:all", |
|||
"dev:all": "concurrently -n=vue,ele -c=green,blue \"npm run dev:vue\" \"npm run dev:ele\"", |
|||
"dev:vue": "node script/before", |
|||
"dev:ele": "node -r ts-node/register script/build-main --env=development --watch", |
|||
"dev:test": "electron-forge start --inspect-electron", |
|||
"package": "electron-builder build --x64 --win", |
|||
"packageaaa": "electron-forge package", |
|||
"make": "electron-forge make", |
|||
"build:vite": "vite build", |
|||
"serve": "vite preview" |
|||
}, |
|||
"keywords": [], |
|||
"author": "TopOne", |
|||
"license": "ISC", |
|||
"devDependencies": { |
|||
"@babel/core": "^7.14.8", |
|||
"@babel/plugin-transform-runtime": "^7.14.5", |
|||
"@babel/preset-env": "^7.14.8", |
|||
"@babel/preset-stage-0": "^7.8.3", |
|||
"@babel/preset-typescript": "^7.14.5", |
|||
"@rollup/plugin-alias": "^3.1.4", |
|||
"@rollup/plugin-commonjs": "^19.0.1", |
|||
"@rollup/plugin-json": "^4.1.0", |
|||
"@rollup/plugin-node-resolve": "^13.0.4", |
|||
"@rollup/plugin-replace": "^3.0.0", |
|||
"@rollup/plugin-typescript": "^8.2.3", |
|||
"@types/minimist": "^1.2.2", |
|||
"@types/node": "^15.14.3", |
|||
"@types/react": "^17.0.0", |
|||
"@types/react-dom": "^17.0.0", |
|||
"@types/react-router-dom": "^5.1.7", |
|||
"@vitejs/plugin-react-refresh": "^1.3.1", |
|||
"axios": "^0.21.1", |
|||
"babel-loader": "^8.2.2", |
|||
"babel-minify-webpack-plugin": "^0.3.1", |
|||
"cfonts": "^2.9.3", |
|||
"chalk": "^4.1.1", |
|||
"classnames": "^2.3.1", |
|||
"concurrently": "^6.2.0", |
|||
"cross-env": "^7.0.3", |
|||
"dotenv": "^10.0.0", |
|||
"electron": "^13.1.7", |
|||
"electron-builder": "^22.11.7", |
|||
"electron-debug": "^3.2.0", |
|||
"electron-devtools-installer": "^3.2.0", |
|||
"electron-squirrel-startup": "^1.0.0", |
|||
"execa": "^5.1.1", |
|||
"i18next": "^20.3.4", |
|||
"i18next-browser-languagedetector": "^6.1.2", |
|||
"node-loader": "^2.0.0", |
|||
"react": "^17.0.0", |
|||
"react-dom": "^17.0.0", |
|||
"react-i18next": "^11.11.3", |
|||
"react-redux": "^7.2.4", |
|||
"react-router-dom": "^5.2.0", |
|||
"redux": "^4.1.0", |
|||
"redux-devtools": "^3.7.0", |
|||
"redux-saga": "^1.1.3", |
|||
"sass": "^1.35.1", |
|||
"styled-jsx": "^3.4.4", |
|||
"ts-loader": "^9.2.4", |
|||
"ts-node": "^10.1.0", |
|||
"tsconfig-paths": "^3.10.1", |
|||
"typescript": "^4.3.2", |
|||
"vite": "^2.4.3", |
|||
"vite-plugin-html": "^2.0.7", |
|||
"vite-plugin-windicss": "^1.2.0", |
|||
"vitejs-plugin-electron": "^0.1.3", |
|||
"webpack": "^5.47.0", |
|||
"webpack-cli": "^4.7.2", |
|||
"webpack-dev-server": "^3.11.2", |
|||
"windicss": "^3.1.3" |
|||
}, |
|||
"dependencies": {}, |
|||
"build": { |
|||
"productName": "my-project", |
|||
"appId": "com.example.yourapp", |
|||
"copyright": "xxxx", |
|||
"directories": { |
|||
"output": "out", |
|||
"app": "./dist" |
|||
}, |
|||
"nsis": { |
|||
"oneClick": false, |
|||
"allowElevation": true, |
|||
"createDesktopShortcut": true, |
|||
"createStartMenuShortcut": true, |
|||
"allowToChangeInstallationDirectory": true, |
|||
"perMachine": true |
|||
}, |
|||
"dmg": { |
|||
"contents": [ |
|||
{ |
|||
"x": 410, |
|||
"y": 150, |
|||
"type": "link", |
|||
"path": "/Applications" |
|||
}, |
|||
{ |
|||
"x": 130, |
|||
"y": 150, |
|||
"type": "file" |
|||
} |
|||
] |
|||
}, |
|||
"extraResources": { |
|||
"from": "src/preload", |
|||
"to": "src/preload" |
|||
}, |
|||
"mac": { |
|||
"icon": "resource/electron/static/icon.png" |
|||
}, |
|||
"win": { |
|||
"icon": "resource/electron/static/icon.png", |
|||
"target": [ |
|||
{ |
|||
"target": "nsis", |
|||
"arch": [ |
|||
"ia32" |
|||
] |
|||
} |
|||
] |
|||
}, |
|||
"linux": { |
|||
"icon": "resource/electron/static/icon.png" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
<!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"> |
|||
<title>Document</title> |
|||
</head> |
|||
<body> |
|||
aaaaaaaaaaaaaaaa |
|||
</body> |
|||
</html> |
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,5 @@ |
|||
// @ts-nocheck
|
|||
|
|||
const {buildMain} = require("./webpack/runMain") |
|||
|
|||
buildMain() |
@ -0,0 +1,36 @@ |
|||
// @ts-nocheck
|
|||
import { spawn } from "child_process" |
|||
import { join } from "path" |
|||
import { rollup, OutputOptions } from "rollup" |
|||
import * as chalk from "chalk" |
|||
import options from "../rollup.config" |
|||
|
|||
export function tscCheck() { |
|||
return new Promise((resolve, reject) => { |
|||
let tscProcess = spawn("node", [join(__dirname, "../../node_modules/typescript/bin/tsc")], { |
|||
stdio: "pipe", |
|||
// env: Object.assign(process.env, { NODE_ENV: argv.env }),
|
|||
}) |
|||
if (tscProcess) { |
|||
tscProcess.stdout.on("end", data => { |
|||
console.log(`[tsc check end]`) |
|||
resolve(tscProcess) |
|||
}) |
|||
tscProcess.stderr.on("data", data => { |
|||
console.error(`[tsc check err]: ${data}`) |
|||
reject() |
|||
}) |
|||
} else { |
|||
reject() |
|||
} |
|||
}) |
|||
} |
|||
export async function buildMain() { |
|||
try { |
|||
const opts = options(process.env.NODE_ENV) |
|||
const build = await rollup(opts) |
|||
build.write(opts.output as OutputOptions) |
|||
} catch (error) { |
|||
console.log(`\n${"[build-main.ts]"} ${chalk.red("构建报错")}\n`, error, "\n") |
|||
} |
|||
} |
@ -0,0 +1,47 @@ |
|||
// @ts-nocheck
|
|||
import { exec, spawn, ChildProcess } from "child_process" |
|||
import { join } from "path" |
|||
import * as electron from "electron" |
|||
import { main } from "../../package.json" |
|||
|
|||
export function devElectron() { |
|||
return new Promise((resolve, reject) => { |
|||
let electronProcess = spawn(electron as unknown as string, ['--inspect=5858',join(__dirname, `../../${main}`)], { |
|||
stdio: "pipe", |
|||
// env: Object.assign(process.env, { NODE_ENV: argv.env }),
|
|||
}) |
|||
if (electronProcess) { |
|||
electronProcess.stdout.on("data", data => { |
|||
console.log(`${data}`) |
|||
}) |
|||
resolve(electronProcess) |
|||
} else { |
|||
reject() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
|
|||
export function buildElectron() { |
|||
return new Promise((resolve, reject) => { |
|||
let tscProcess = spawn("node", [join(__dirname, "../../node_modules/@electron-forge/cli/dist/electron-forge.js"), "package"], { |
|||
stdio: "pipe", |
|||
// env: Object.assign(process.env, { NODE_ENV: argv.env }),
|
|||
}) |
|||
if (tscProcess) { |
|||
tscProcess.stdout.on("data", data => { |
|||
console.log(`[electron build]: ${data}`) |
|||
}) |
|||
tscProcess.stdout.on("end", data => { |
|||
console.log(`[electron build end]`) |
|||
resolve(tscProcess) |
|||
}) |
|||
tscProcess.stderr.on("data", data => { |
|||
console.error(`[electron build err]: ${data}`) |
|||
reject() |
|||
}) |
|||
} else { |
|||
reject() |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,54 @@ |
|||
// @ts-nocheck
|
|||
import { exec, spawn, ChildProcess } from "child_process" |
|||
import { join } from "path" |
|||
|
|||
export function devVite() { |
|||
return new Promise((resolve, reject) => { |
|||
let viteProcess = spawn("node", [join(__dirname, "../../node_modules/vite/bin/vite.js")], { |
|||
stdio: "pipe", |
|||
// env: Object.assign(process.env, { NODE_ENV: argv.env }),
|
|||
}) |
|||
if (viteProcess) { |
|||
let isReady = false |
|||
viteProcess.stdout.on("data", data => { |
|||
console.log(`${data}`) |
|||
if (!isReady && data.indexOf("ready") != -1) { |
|||
resolve(viteProcess) |
|||
} |
|||
}) |
|||
|
|||
viteProcess.stderr.on("data", data => { |
|||
console.error(`[vite err]: ${data}`) |
|||
reject() |
|||
}) |
|||
|
|||
viteProcess.on("close", code => { |
|||
console.log(`[vite close]: exited with code ${code}`) |
|||
reject() |
|||
}) |
|||
} else { |
|||
reject() |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export function buildVite() { |
|||
return new Promise((resolve, reject) => { |
|||
let viteProcess = spawn("node", [join(__dirname, "../../node_modules/vite/bin/vite.js"), "build"], { |
|||
stdio: "pipe", |
|||
// env: Object.assign(process.env, { NODE_ENV: argv.env }),
|
|||
}) |
|||
if (viteProcess) { |
|||
viteProcess.stdout.on("end", data => { |
|||
console.log(`\n[vite build end]`) |
|||
resolve(viteProcess) |
|||
}) |
|||
viteProcess.stderr.on("data", data => { |
|||
console.error(`\n[vite build err]: ${data}`) |
|||
reject() |
|||
}) |
|||
} else { |
|||
reject() |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,82 @@ |
|||
// @ts-nocheck
|
|||
import * as chalk from "chalk" |
|||
import { devVite } from "./code/runVite" |
|||
import { devElectron } from "./code/runElectron" |
|||
|
|||
import { ChildProcess } from "child_process" |
|||
import { watch } from "rollup" |
|||
import options from "./rollup.config" |
|||
const { startMain } = require("./webpack/runMain") |
|||
|
|||
;(async () => { |
|||
let vitePorcess = await devVite() |
|||
console.log("[vite]", chalk.green(`vite ready.`)) |
|||
let child: ChildProcess | null |
|||
let manualRestart = false |
|||
|
|||
startMain(async () => { |
|||
if (child && child.kill) { |
|||
manualRestart = true |
|||
child.kill() |
|||
child = null |
|||
setTimeout(() => { |
|||
manualRestart = false |
|||
}, 5000) |
|||
} |
|||
try { |
|||
child = await devElectron() |
|||
console.log("[electron]", chalk.green(`electron ready.`)) |
|||
child.on("close", () => { |
|||
if (!manualRestart) { |
|||
// https://juejin.cn/post/6844904071682326535
|
|||
vitePorcess.kill() |
|||
child.kill() |
|||
process.exit() |
|||
} |
|||
}) |
|||
} catch (ev) { |
|||
console.log(ev.error) |
|||
vitePorcess.kill() |
|||
child.kill() |
|||
process.exit() |
|||
} |
|||
}) |
|||
})() |
|||
|
|||
// const watcher = watch(opts)
|
|||
// let child: ChildProcess | null
|
|||
// let manualRestart = false
|
|||
// watcher.on("change", filename => {
|
|||
// const log = chalk.green(`change -- ${filename}`)
|
|||
// console.log(TAG, log)
|
|||
// })
|
|||
// watcher.on("event",async ev => {
|
|||
// if (ev.code === "END") {
|
|||
// if (child && child.kill) {
|
|||
// manualRestart = true
|
|||
// child.kill()
|
|||
// child = null
|
|||
// setTimeout(() => {
|
|||
// manualRestart = false
|
|||
// }, 5000)
|
|||
// }
|
|||
// try {
|
|||
// child = await runElectron()
|
|||
// console.log('[electron]', chalk.green(`electron ready.`))
|
|||
// child.on("close", () => {
|
|||
// if (!manualRestart) {
|
|||
// vitePorcess.kill()
|
|||
// child.kill()
|
|||
// process.exit()
|
|||
// }
|
|||
// })
|
|||
// } catch (ev) {
|
|||
// console.log(ev.error)
|
|||
// vitePorcess.kill()
|
|||
// child.kill()
|
|||
// process.exit()
|
|||
// }
|
|||
// } else if (ev.code === "ERROR") {
|
|||
// console.log(ev.error)
|
|||
// }
|
|||
// })
|
@ -0,0 +1,58 @@ |
|||
const { say } = require("cfonts") |
|||
const chalk = require("chalk") |
|||
|
|||
export function logStats(proc, data) { |
|||
let log = "" |
|||
|
|||
log += chalk.yellow.bold(`┏ ${proc} Process ${new Array(19 - proc.length + 1).join("-")}`) |
|||
log += "\n\n" |
|||
|
|||
if (typeof data === "object") { |
|||
data |
|||
.toString({ |
|||
colors: true, |
|||
chunks: false, |
|||
}) |
|||
.split(/\r?\n/) |
|||
.forEach(line => { |
|||
log += " " + line + "\n" |
|||
}) |
|||
} else { |
|||
log += ` ${data}\n` |
|||
} |
|||
|
|||
log += "\n" + chalk.yellow.bold(`┗ ${new Array(28 + 1).join("-")}`) + "\n" |
|||
|
|||
console.log(log) |
|||
} |
|||
|
|||
export function electronLog(data, color) { |
|||
let log = "" |
|||
data = data.toString().split(/\r?\n/) |
|||
data.forEach(line => { |
|||
log += ` ${line}\n` |
|||
}) |
|||
if (/[0-9A-z]+/.test(log)) { |
|||
console.log( |
|||
chalk[color].bold("┏ Electron -------------------") + "\n\n" + log + chalk[color].bold("┗ ----------------------------") + "\n" |
|||
) |
|||
} |
|||
} |
|||
|
|||
export function greeting() { |
|||
const cols = process.stdout.columns |
|||
let text |
|||
|
|||
if (cols > 104) text = "electron-vue" |
|||
else if (cols > 76) text = "electron-|vue" |
|||
else text = false |
|||
|
|||
if (text) { |
|||
say(text, { |
|||
colors: ["yellow"], |
|||
font: "simple3d", |
|||
space: false, |
|||
}) |
|||
} else console.log(chalk.yellow.bold("\n electron-vue")) |
|||
console.log(chalk.blue(" getting ready...") + "\n") |
|||
} |
@ -0,0 +1,41 @@ |
|||
import { join } from "path" |
|||
import { RollupOptions } from "rollup" |
|||
import { builtins } from "./utils" |
|||
|
|||
const commonjs = require("@rollup/plugin-commonjs") |
|||
const json = require("@rollup/plugin-json") |
|||
const typescript = require("@rollup/plugin-typescript") |
|||
const alias = require("@rollup/plugin-alias") |
|||
const {nodeResolve} = require("@rollup/plugin-node-resolve") |
|||
|
|||
export default (env = "production") => { |
|||
console.log(env); |
|||
|
|||
const options: RollupOptions = { |
|||
input: join(__dirname, "../src/main/index.ts"), |
|||
output: { |
|||
file: join(__dirname, "../dist/electron/entry.js"), |
|||
format: "cjs", |
|||
name: "ElectronMainBundle", |
|||
sourcemap: true, |
|||
}, |
|||
plugins: [ |
|||
nodeResolve(), |
|||
commonjs(), |
|||
json(), |
|||
typescript(), |
|||
alias({ |
|||
entries: [ |
|||
{ find: "@", replacement: join(__dirname, "../src/render") }, |
|||
{ find: "@render", replacement: join(__dirname, "../src/render") }, |
|||
{ find: "@main", replacement: join(__dirname, "../src/main") }, |
|||
{ find: "@src", replacement: join(__dirname, "../src") }, |
|||
{ find: "@root", replacement: join(__dirname, "..") }, |
|||
], |
|||
}), |
|||
], |
|||
external: [...builtins(), "electron"], |
|||
} |
|||
|
|||
return options |
|||
} |
@ -0,0 +1,19 @@ |
|||
const crossEnv = require("cross-env") |
|||
const minimist = require("minimist") |
|||
const {join} = require("path") |
|||
|
|||
const argv = minimist(process.argv.slice(2)) |
|||
|
|||
if (argv.build) { |
|||
crossEnv([ |
|||
"NODE_ENV=production", |
|||
"npm run build:in" |
|||
]) |
|||
} else { |
|||
const process = crossEnv([ |
|||
`STATIC=${join(__dirname, '../resource/electron/static')}`, |
|||
"PORT=3344", |
|||
"NODE_ENV=development", |
|||
"ts-node -r tsconfig-paths/register script/dev-runner --watch" |
|||
]) |
|||
} |
@ -0,0 +1,25 @@ |
|||
import { builtinModules } from "module" |
|||
import { get } from "http" |
|||
import { green } from "chalk" |
|||
import { exec, spawn, ChildProcess } from "child_process" |
|||
|
|||
/** 轮询监听 vite 启动 */ |
|||
export function waitOn(arg0: { port: string | number; interval?: number }) { |
|||
return new Promise(resolve => { |
|||
const { port, interval = 149 } = arg0 |
|||
const url = `http://localhost:${port}` |
|||
let counter = 0 |
|||
const timer: NodeJS.Timer = setInterval(() => { |
|||
get(url, res => { |
|||
clearInterval(timer) |
|||
console.log("[waitOn]", green(`"${url}" are already responsive.`), `(${res.statusCode}: ${res.statusMessage})`) |
|||
resolve(res.statusCode) |
|||
}).on("error", err => { |
|||
console.log("[waitOn]", `counter: ${counter++}`) |
|||
}) |
|||
}, interval) |
|||
}) |
|||
} |
|||
|
|||
/** node.js builtins module */ |
|||
export const builtins = () => builtinModules.filter(x => !/^_|^(internal|v8|node-inspect)\/|\//.test(x)) |
@ -0,0 +1,146 @@ |
|||
const chalk = require('chalk') |
|||
const electron = require('electron') |
|||
const path = require('path') |
|||
const { say } = require('cfonts') |
|||
const { spawn } = require('child_process') |
|||
const webpack = require('webpack') |
|||
// const WebpackDevServer = require('webpack-dev-server')
|
|||
// const webpackHotMiddleware = require('webpack-hot-middleware')
|
|||
|
|||
const mainConfig = require('./webpack.main.config') |
|||
// const rendererConfig = require('./webpack.renderer.config')
|
|||
|
|||
let electronProcess = null |
|||
let manualRestart = false |
|||
// let hotMiddleware
|
|||
|
|||
function logStats (proc, data) { |
|||
let log = '' |
|||
|
|||
log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) |
|||
log += '\n\n' |
|||
|
|||
if (typeof data === 'object') { |
|||
data.toString({ |
|||
colors: true, |
|||
chunks: false |
|||
}).split(/\r?\n/).forEach(line => { |
|||
log += ' ' + line + '\n' |
|||
}) |
|||
} else { |
|||
log += ` ${data}\n` |
|||
} |
|||
|
|||
log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' |
|||
|
|||
console.log(log) |
|||
} |
|||
|
|||
function buildMain () { |
|||
return new Promise((resolve, reject) => { |
|||
mainConfig.mode = 'production' |
|||
webpack(mainConfig, (err, stats) => { |
|||
if (err) reject(err.stack || err) |
|||
else if (stats.hasErrors()) { |
|||
let err = '' |
|||
stats.toString({ |
|||
chunks: false, |
|||
colors: true |
|||
}) |
|||
.split(/\r?\n/) |
|||
.forEach(line => { |
|||
err += ` ${line}\n` |
|||
}) |
|||
|
|||
reject(err) |
|||
} |
|||
else { |
|||
resolve(stats.toString({ |
|||
chunks: false, |
|||
colors: true |
|||
})) |
|||
} |
|||
}) |
|||
}) |
|||
} |
|||
function startMain (callback) { |
|||
return new Promise((resolve, reject) => { |
|||
mainConfig.entry.main = [path.join(__dirname, '../../src/main/index.dev.ts')]//.concat(mainConfig.entry.main)
|
|||
mainConfig.mode = 'development' |
|||
const compiler = webpack(mainConfig) |
|||
|
|||
compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { |
|||
logStats('Main', chalk.white.bold('compiling...')) |
|||
// hotMiddleware.publish({ action: 'compiling' })
|
|||
done() |
|||
}) |
|||
|
|||
compiler.watch({}, (err, stats) => { |
|||
if (err) { |
|||
console.log(err) |
|||
return |
|||
} |
|||
|
|||
logStats('Main', stats) |
|||
|
|||
callback && callback() |
|||
|
|||
resolve() |
|||
}) |
|||
}) |
|||
} |
|||
module.exports = { |
|||
startMain, |
|||
buildMain, |
|||
} |
|||
|
|||
|
|||
function electronLog (data, color) { |
|||
let log = '' |
|||
data = data.toString().split(/\r?\n/) |
|||
data.forEach(line => { |
|||
log += ` ${line}\n` |
|||
}) |
|||
if (/[0-9A-z]+/.test(log)) { |
|||
console.log( |
|||
chalk[color].bold('┏ Electron -------------------') + |
|||
'\n\n' + |
|||
log + |
|||
chalk[color].bold('┗ ----------------------------') + |
|||
'\n' |
|||
) |
|||
} |
|||
} |
|||
|
|||
function greeting () { |
|||
const cols = process.stdout.columns |
|||
let text = '' |
|||
|
|||
if (cols > 104) text = 'electron-vue' |
|||
else if (cols > 76) text = 'electron-|vue' |
|||
else text = false |
|||
|
|||
if (text) { |
|||
say(text, { |
|||
colors: ['yellow'], |
|||
font: 'simple3d', |
|||
space: false |
|||
}) |
|||
} else console.log(chalk.yellow.bold('\n electron-vue')) |
|||
console.log(chalk.blue(' getting ready...') + '\n') |
|||
} |
|||
|
|||
|
|||
// function init () {
|
|||
// greeting()
|
|||
// startMain()
|
|||
// Promise.all([startRenderer(), startMain()])
|
|||
// .then(() => {
|
|||
// startElectron()
|
|||
// })
|
|||
// .catch(err => {
|
|||
// console.error(err)
|
|||
// })
|
|||
// }
|
|||
|
|||
// init()
|
@ -0,0 +1,65 @@ |
|||
'use strict' |
|||
|
|||
process.env.BABEL_ENV = 'main' |
|||
|
|||
const path = require('path') |
|||
const { dependencies } = require('../../package.json') |
|||
const webpack = require('webpack') |
|||
|
|||
const MinifyPlugin = require("babel-minify-webpack-plugin") |
|||
|
|||
let mainConfig = { |
|||
entry: { |
|||
main: path.join(__dirname, '../../src/main/index.ts') |
|||
}, |
|||
externals: [ |
|||
...Object.keys(dependencies || {}) |
|||
], |
|||
module: { |
|||
rules: [ |
|||
{ |
|||
test: /\.(js|ts)$/, |
|||
use: ['babel-loader','ts-loader'], |
|||
exclude: /node_modules/ |
|||
}, |
|||
{ |
|||
test: /\.node$/, |
|||
use: 'node-loader' |
|||
} |
|||
] |
|||
}, |
|||
// https://www.webpackjs.com/configuration/node/
|
|||
node: { |
|||
// __dirname: process.env.NODE_ENV !== 'production', // 不转化为字符串
|
|||
// __filename: process.env.NODE_ENV !== 'production' // 不转化为字符串
|
|||
}, |
|||
output: { |
|||
filename: 'entry.js', // [name]
|
|||
libraryTarget: 'commonjs2', |
|||
path: path.join(__dirname, '../../dist/electron') |
|||
}, |
|||
plugins: [ |
|||
new webpack.NoEmitOnErrorsPlugin() |
|||
], |
|||
resolve: { |
|||
alias: { |
|||
"@": path.join(__dirname, "../../src/render"), |
|||
"@render": path.join(__dirname, "../../src/render"), |
|||
"@main": path.join(__dirname, "../../src/main"), |
|||
"@src": path.join(__dirname, "../../src"), |
|||
"@root": path.join(__dirname, "../../"), |
|||
}, |
|||
extensions: ['.js', '.json', '.node', '.ts'] |
|||
}, |
|||
target: 'electron-main' |
|||
} |
|||
|
|||
if (process.env.NODE_ENV !== 'production') { |
|||
mainConfig.plugins.push( |
|||
new webpack.DefinePlugin({ |
|||
'__static': `"${path.join(__dirname, '../../resource/electron/static').replace(/\\/g, '\\\\')}"` |
|||
}) |
|||
) |
|||
} |
|||
|
|||
module.exports = mainConfig |
@ -0,0 +1,7 @@ |
|||
/** |
|||
* !!! ensure process.cwd() correct |
|||
*/ |
|||
process.chdir(__dirname.slice(0, __dirname.lastIndexOf('dist'))) |
|||
|
|||
|
|||
export {}; |
@ -0,0 +1,63 @@ |
|||
import Shared from "./share" |
|||
import { Menu, Tray, ipcMain, BrowserWindow, App } from "electron" |
|||
const path = require("path") |
|||
|
|||
// 隐藏主窗口,并创建托盘,绑定关闭事件
|
|||
export default function setTray(app: App, mainWindow: BrowserWindow) { |
|||
if (Shared.data.miniWindow) { |
|||
mainWindow.hide() |
|||
return |
|||
} |
|||
// 用一个 Tray 来表示一个图标,这个图标处于正在运行的系统的通知区
|
|||
// 通常被添加到一个 context menu 上.
|
|||
// 系统托盘右键菜单
|
|||
const trayMenuTemplate = [ |
|||
{ |
|||
// 系统托盘图标目录
|
|||
label: "打开主窗口", |
|||
click: () => { |
|||
mainWindow.show() |
|||
}, |
|||
}, |
|||
{ |
|||
// 系统托盘图标目录
|
|||
label: "打开悬浮窗", |
|||
click: () => { |
|||
ipcMain.emit("showSuspensionWindow") |
|||
}, |
|||
}, |
|||
{ |
|||
// 系统托盘图标目录
|
|||
label: "退出", |
|||
click: () => { |
|||
Shared.data.forceClose = true |
|||
app.quit() |
|||
}, |
|||
}, |
|||
] |
|||
// 设置系统托盘图标
|
|||
const iconPath = path.join(__static, "/icon.png") |
|||
|
|||
Shared.data.miniWindow = new Tray(iconPath) |
|||
|
|||
// 图标的上下文菜单
|
|||
const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) |
|||
|
|||
// 展示主窗口,隐藏主窗口 mainWindow.hide()
|
|||
mainWindow.hide() |
|||
|
|||
// 设置托盘悬浮提示
|
|||
Shared.data.miniWindow.setToolTip("never forget") |
|||
|
|||
// 设置托盘菜单
|
|||
Shared.data.miniWindow.setContextMenu(contextMenu) |
|||
|
|||
// 单击托盘小图标显示应用
|
|||
Shared.data.miniWindow.on("double-click", () => { |
|||
// 显示主程序
|
|||
mainWindow.show() |
|||
// 关闭托盘显示
|
|||
// Shared.data.miniWindow.destroy();
|
|||
}) |
|||
return Shared.data.miniWindow |
|||
} |
@ -0,0 +1,20 @@ |
|||
import { ipcMain, dialog } from "electron" |
|||
|
|||
/** |
|||
* 格式:@类型:扩展:函数 |
|||
*/ |
|||
|
|||
// 保存数据
|
|||
ipcMain.on("@func:buildin:close", data => { |
|||
// dialog.showMessageBox(
|
|||
// {
|
|||
// type: "info",
|
|||
// title: "Information",
|
|||
// defaultId: 0,
|
|||
// cancelId: 0,
|
|||
// message: "确定要关闭吗?" + data,
|
|||
// buttons: ["没事", "最小化到托盘", "直接退出"],
|
|||
// },
|
|||
// index => {}
|
|||
// )
|
|||
}) |
@ -0,0 +1,119 @@ |
|||
import Shared from "./share" |
|||
import { getFileUrl } from './util' |
|||
import { BrowserWindow, ipcMain, screen, Menu, shell, webContents, app } from "electron" |
|||
// webContents
|
|||
console.log(webContents.getAllWebContents()) |
|||
|
|||
const window: any = null //BrowserWindow.fromWebContents(webContents.getFocusedWebContents())
|
|||
|
|||
ipcMain.on("@float:setPosition", (event, x, y) => { |
|||
Shared.data.floatWindow?.setPosition(x, y) |
|||
}) |
|||
|
|||
ipcMain.on("showSuspensionWindow", () => { |
|||
if (Shared.data.floatWindow) { |
|||
if (Shared.data.floatWindow.isVisible()) { |
|||
// createSuspensionWindow()
|
|||
} else { |
|||
Shared.data.floatWindow.showInactive() |
|||
} |
|||
} else { |
|||
createSuspensionWindow() |
|||
} |
|||
}) |
|||
|
|||
ipcMain.on("createSuspensionMenu", e => { |
|||
const rightM = Menu.buildFromTemplate([ |
|||
{ label: "开始全部任务", enabled: false }, |
|||
{ label: "暂停全部任务", enabled: false }, |
|||
{ |
|||
label: "本次传输完自动关机", |
|||
click: () => { |
|||
ipcMain.emit("@func:buildin:saveData", 32232) |
|||
}, |
|||
}, |
|||
{ type: "separator" }, |
|||
{ |
|||
label: "隐藏悬浮窗", |
|||
click: () => { |
|||
if (window) { |
|||
window.webContents.send("hideSuspension", false) |
|||
} |
|||
Shared.data.floatWindow.hide() |
|||
}, |
|||
}, |
|||
{ |
|||
label: "打开主窗口", |
|||
click: () => { |
|||
// && !Shared.data.mainWindow.isVisible()
|
|||
if (Shared.data.mainWindow) { |
|||
Shared.data.mainWindow.show() |
|||
} |
|||
// window.webContents.send('hideSuspension', false)
|
|||
}, |
|||
}, |
|||
{ type: "separator" }, |
|||
{ |
|||
label: "加入qq群", |
|||
click: () => { |
|||
shell.openExternal( |
|||
"tencent://groupwpa/?subcmd=all¶m=7B2267726F757055696E223A3831343237303636392C2274696D655374616D70223A313533393531303138387D0A" |
|||
) |
|||
}, |
|||
}, |
|||
{ |
|||
label: "GitHub地址", |
|||
click: () => { |
|||
shell.openExternal("https://github.com/lihaotian0607/auth") |
|||
}, |
|||
}, |
|||
{ |
|||
label: "退出软件", |
|||
click: () => { |
|||
Shared.data.forceClose = true |
|||
app.quit() |
|||
}, |
|||
}, |
|||
]) |
|||
rightM.popup({}) |
|||
}) |
|||
|
|||
function createSuspensionWindow() { |
|||
Shared.data.floatWindow = new BrowserWindow({ |
|||
width: 102, // 悬浮窗口的宽度 比实际DIV的宽度要多2px 因为有1px的边框
|
|||
height: 27, // 悬浮窗口的高度 比实际DIV的高度要多2px 因为有1px的边框
|
|||
type: "toolbar", // 创建的窗口类型为工具栏窗口
|
|||
frame: false, // 要创建无边框窗口
|
|||
resizable: false, // 禁止窗口大小缩放
|
|||
show: false, // 先不让窗口显示
|
|||
webPreferences: { |
|||
devTools: false, // 关闭调试工具
|
|||
nodeIntegration: true, |
|||
contextIsolation: false, |
|||
}, |
|||
transparent: true, // 设置透明
|
|||
alwaysOnTop: true, // 窗口是否总是显示在其他窗口之前
|
|||
}) |
|||
const size = screen.getPrimaryDisplay().workAreaSize // 获取显示器的宽高
|
|||
const winSize = Shared.data.floatWindow.getSize() // 获取窗口宽高
|
|||
// 设置窗口的位置 注意x轴要桌面的宽度 - 窗口的宽度
|
|||
Shared.data.floatWindow.setPosition(size.width - winSize[0], 100) |
|||
Shared.data.floatWindow.setPosition(size.width / 2, size.height / 2) |
|||
Shared.data.floatWindow.loadURL(getFileUrl("float")) |
|||
|
|||
Shared.data.floatWindow.once("ready-to-show", () => { |
|||
Shared.data.floatWindow.show() |
|||
}) |
|||
Shared.data.floatWindow.on("double-click", () => { |
|||
alert(123) |
|||
}) |
|||
Shared.data.floatWindow.on("close", () => { |
|||
Shared.data.floatWindow = null |
|||
}) |
|||
} |
|||
|
|||
ipcMain.on("hideSuspensionWindow", () => { |
|||
if (Shared.data.floatWindow) { |
|||
Shared.data.floatWindow.hide() |
|||
} |
|||
}) |
@ -0,0 +1,18 @@ |
|||
|
|||
// Install `electron-debug` with `devtron`
|
|||
import electronDebug from "electron-debug" |
|||
|
|||
electronDebug({ showDevTools: true }) |
|||
|
|||
// Install `vue-devtools`
|
|||
require('electron').app.on('ready', () => { |
|||
let installExtension = require('electron-devtools-installer') |
|||
installExtension.default(installExtension.VUEJS_DEVTOOLS) |
|||
.then(() => {}) |
|||
.catch((err:Error) => { |
|||
console.log('Unable to install `vue-devtools`: \n', err) |
|||
}) |
|||
}) |
|||
|
|||
// Require `main` process to boot app
|
|||
import './index' |
@ -0,0 +1,133 @@ |
|||
/** |
|||
* electron 主文件 |
|||
*/ |
|||
"use strict" |
|||
import Shared from "./share" |
|||
import setTray from "./disk" |
|||
import { getFileUrl } from "./util" |
|||
// import '../renderer/store'
|
|||
import "./facilities" |
|||
import { app, BrowserWindow, dialog } from "electron" |
|||
|
|||
Shared.data = { |
|||
mainWindow: null, // 主窗口
|
|||
floatWindow: null, // 浮动窗口
|
|||
miniWindow: null, |
|||
forceClose: false, |
|||
lastChoice: -1, // 做过的选择
|
|||
} |
|||
console.log("asdasadsads") |
|||
/** |
|||
* Set `__static` path to static files in production |
|||
* https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
|
|||
*/ |
|||
let isDev = process.env.NODE_ENV == "development" ? true : false |
|||
|
|||
if (!isDev) { |
|||
// @ts-ignore
|
|||
global.__static = require("path").join(__dirname, "/static").replace(/\\/g, "\\\\") |
|||
} |
|||
|
|||
function createWindow() { |
|||
/** |
|||
* Initial window options |
|||
*/ |
|||
Shared.data.mainWindow = new BrowserWindow({ |
|||
height: 400, |
|||
useContentSize: true, |
|||
width: 600, |
|||
resizable: true, |
|||
minWidth: 450, |
|||
minHeight: 400, |
|||
icon: __static + "/icon.png", |
|||
// frame: false,
|
|||
// transparent: true,
|
|||
alwaysOnTop: false, |
|||
webPreferences: { |
|||
nodeIntegration: true, |
|||
contextIsolation: false, |
|||
}, |
|||
}) |
|||
|
|||
Shared.data.mainWindow.loadURL(getFileUrl("")) |
|||
Shared.data.mainWindow.on("close", (event: any) => { |
|||
if (Shared.data.forceClose) { |
|||
Shared.data.mainWindow = null |
|||
app.quit() |
|||
} else if (Shared.data.mainWindow) { |
|||
if (Shared.data.lastChoice === 1) { |
|||
if (Shared.data.miniWindow) { |
|||
Shared.data.mainWindow.hide() // 调用 最小化实例方法
|
|||
} else { |
|||
setTray(app, Shared.data.mainWindow) |
|||
} |
|||
event.preventDefault() |
|||
} else { |
|||
const choice = dialog.showMessageBoxSync(Shared.data.mainWindow, { |
|||
type: "info", |
|||
title: "Information", |
|||
defaultId: 0, |
|||
cancelId: 0, |
|||
message: "确定要关闭吗?", |
|||
buttons: ["没事", "最小化到托盘", "直接退出"], |
|||
}) |
|||
if (choice === 1) { |
|||
Shared.data.lastChoice = 1 |
|||
if (Shared.data.miniWindow) { |
|||
Shared.data.mainWindow.hide() // 调用 最小化实例方法
|
|||
} else { |
|||
setTray(app, Shared.data.mainWindow) |
|||
} |
|||
event.preventDefault() |
|||
} else if (choice === 2) { |
|||
Shared.data.mainWindow = null |
|||
// app.quit()
|
|||
// 不要用quit();试了会弹两次
|
|||
Shared.data.forceClose = true |
|||
app.quit() // exit()直接关闭客户端,不会执行quit();
|
|||
} else { |
|||
event.preventDefault() |
|||
} |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const gotTheLock = app.requestSingleInstanceLock() |
|||
if (!gotTheLock) { |
|||
app.exit() |
|||
} else { |
|||
require("./menu") |
|||
require("./float") |
|||
|
|||
app.on("second-instance", (event, commandLine, workingDirectory) => { |
|||
// 当运行第二个实例时,将会聚焦到mainWindow这个窗口
|
|||
if (Shared.data.mainWindow) { |
|||
if (Shared.data.mainWindow.isMinimized()) Shared.data.mainWindow.restore() |
|||
Shared.data.mainWindow.focus() |
|||
Shared.data.mainWindow.show() |
|||
} |
|||
}) |
|||
|
|||
app.on("ready", createWindow) |
|||
|
|||
app.on("before-quit", event => { |
|||
if (Shared.data.forceClose) { |
|||
app.exit() |
|||
} else { |
|||
event.preventDefault() |
|||
} |
|||
}) |
|||
|
|||
app.on("window-all-closed", () => { |
|||
if (process.platform !== "darwin") { |
|||
app.exit() |
|||
} |
|||
}) |
|||
|
|||
app.on("activate", () => { |
|||
if (Shared.data.mainWindow === null) { |
|||
createWindow() |
|||
} |
|||
}) |
|||
} |
@ -0,0 +1,66 @@ |
|||
import electron from "electron" |
|||
|
|||
const BrowserWindow = electron.BrowserWindow |
|||
const Menu = electron.Menu |
|||
const app = electron.app |
|||
const dialog = electron.dialog |
|||
const ipcMain = electron.ipcMain |
|||
|
|||
let newwin:electron.BrowserWindow|null = null |
|||
|
|||
let template = [ |
|||
{ |
|||
label: "关于", |
|||
click: function (item:any, focusedWindow:any) { |
|||
// https://www.electronjs.org/docs/api/browser-window#winsetmenubarvisibilityvisible-windows-linux
|
|||
if (focusedWindow && !newwin) { |
|||
newwin = new BrowserWindow({ |
|||
width: 600, |
|||
height: 200, |
|||
// modal: true,
|
|||
show: false, |
|||
resizable: true, |
|||
parent: focusedWindow, // win是主窗口
|
|||
//
|
|||
webPreferences: { |
|||
// 下面两个必须这么用,看a.md的文档
|
|||
nodeIntegration: true, |
|||
contextIsolation: false, |
|||
// 预加载动画
|
|||
// preload: join(__dirname, "../../src/preload/index.js"),
|
|||
}, |
|||
}) |
|||
// 隐藏菜单
|
|||
newwin.setMenuBarVisibility(false) |
|||
newwin.loadURL(process.env.NODE_ENV === "development" ? `http://localhost:${process.env.PORT}/#/about` : `file://${__dirname}/index.html#/about`); |
|||
newwin.on("ready-to-show", () => { |
|||
newwin?.show() |
|||
}) |
|||
newwin.on("closed", () => { |
|||
newwin = null |
|||
}) |
|||
} |
|||
}, |
|||
}, |
|||
] |
|||
|
|||
// if (process.platform === 'darwin') {
|
|||
//
|
|||
// }
|
|||
//
|
|||
// if (process.platform === 'win32') {
|
|||
//
|
|||
// }
|
|||
app.on("ready", function () { |
|||
const menu = Menu.buildFromTemplate(template) |
|||
Menu.setApplicationMenu(menu) |
|||
}) |
|||
app.on("browser-window-created", function () { |
|||
// let reopenMenuItem = findReopenMenuItem()
|
|||
// if (reopenMenuItem) reopenMenuItem.enabled = false
|
|||
}) |
|||
app.on("window-all-closed", function () { |
|||
app.exit() |
|||
// let reopenMenuItem = findReopenMenuItem()
|
|||
// if (reopenMenuItem) reopenMenuItem.enabled = true
|
|||
}) |
@ -0,0 +1,189 @@ |
|||
// const path = require('path')
|
|||
import { BrowserWindow } from "electron" |
|||
import Shared from "./share" |
|||
import { getFileUrl } from "./util" |
|||
const electron = require("electron") |
|||
const setTray = require("./disk").default |
|||
// const BrowserWindow = electron.BrowserWindow
|
|||
const Menu = electron.Menu |
|||
const app = electron.app |
|||
const dialog = electron.dialog |
|||
const ipcMain = electron.ipcMain |
|||
|
|||
let newwin: BrowserWindow | null |
|||
|
|||
let template = [ |
|||
{ |
|||
label: "选择保存目录", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
dialog |
|||
.showOpenDialog(focusedWindow, { |
|||
properties: ["openDirectory"], |
|||
}) |
|||
.then(result => { |
|||
ipcMain.emit("@menu:selectDir", result) |
|||
}) |
|||
.catch(err => { |
|||
throw err |
|||
}) |
|||
}, |
|||
}, |
|||
{ |
|||
label: "置顶", |
|||
key: "alwaysTop", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
if (Shared.data.mainWindow.isAlwaysOnTop()) { |
|||
Shared.data.mainWindow.setAlwaysOnTop(false) |
|||
} else { |
|||
Shared.data.mainWindow.setAlwaysOnTop(true) |
|||
} |
|||
}, |
|||
}, |
|||
{ |
|||
label: "重载", |
|||
accelerator: "CmdOrCtrl+R", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
if (focusedWindow) { |
|||
// 重载之后, 刷新并关闭所有的次要窗体
|
|||
if (focusedWindow.id === 1) { |
|||
BrowserWindow.getAllWindows().forEach(function (win) { |
|||
if (win.id > 1) { |
|||
win.close() |
|||
} |
|||
}) |
|||
} |
|||
focusedWindow.reload() |
|||
} |
|||
}, |
|||
}, |
|||
{ |
|||
label: "功能", |
|||
submenu: [ |
|||
{ |
|||
label: "悬浮窗", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
ipcMain.emit("showSuspensionWindow") |
|||
}, |
|||
}, |
|||
{ |
|||
label: "最小化到托盘", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
Shared.data.lastChoice = 1 |
|||
if (Shared.data.miniWindow) { |
|||
Shared.data.mainWindow.hide() // 调用 最小化实例方法
|
|||
} else { |
|||
setTray(app, Shared.data.mainWindow) |
|||
} |
|||
}, |
|||
}, |
|||
{ |
|||
label: "切换全屏", |
|||
accelerator: (function () { |
|||
if (process.platform === "darwin") { |
|||
return "Ctrl+Command+F" |
|||
} else { |
|||
return "F11" |
|||
} |
|||
})(), |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
if (focusedWindow) { |
|||
focusedWindow.setFullScreen(!focusedWindow.isFullScreen()) |
|||
} |
|||
}, |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
label: "开发者", |
|||
submenu: [ |
|||
{ |
|||
label: "切换开发者工具", |
|||
accelerator: (function () { |
|||
if (process.platform === "darwin") { |
|||
return "Alt+Command+I" |
|||
} else { |
|||
return "Ctrl+Shift+I" |
|||
} |
|||
})(), |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
if (focusedWindow) { |
|||
// @ts-ignore
|
|||
focusedWindow.toggleDevTools() |
|||
} |
|||
}, |
|||
}, |
|||
], |
|||
}, |
|||
// {
|
|||
// label: '重新启动',
|
|||
// click: function(item, focusedWindow) {
|
|||
// app.exit()
|
|||
// app.relaunch()
|
|||
// // app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
|
|||
// // app.quit()
|
|||
// }
|
|||
// },
|
|||
{ |
|||
label: "关于", |
|||
click: function (item: any, focusedWindow: BrowserWindow) { |
|||
// https://www.electronjs.org/docs/api/browser-window#winsetmenubarvisibilityvisible-windows-linux
|
|||
if (focusedWindow && !newwin) { |
|||
newwin = new BrowserWindow({ |
|||
width: 600, |
|||
height: 200, |
|||
minimizable: false, |
|||
darkTheme: true, |
|||
modal: true, |
|||
show: false, |
|||
resizable: false, |
|||
webPreferences: { |
|||
nodeIntegration: true, |
|||
contextIsolation: false, |
|||
}, |
|||
// parent: focusedWindow // win是主窗口
|
|||
}) |
|||
// 隐藏菜单
|
|||
newwin.setMenuBarVisibility(false) |
|||
// vue是单页面,需要改成多页面才行
|
|||
newwin.loadURL(getFileUrl("about")) |
|||
newwin.on("ready-to-show", () => { |
|||
newwin?.show() |
|||
}) |
|||
newwin.on("close", () => { |
|||
newwin = null |
|||
}) |
|||
} |
|||
}, |
|||
}, |
|||
] |
|||
// function findTopItem() {
|
|||
// const menu = Menu.getApplicationMenu()
|
|||
// if (!menu) return
|
|||
// let reopenMenuItem
|
|||
// menu.items.forEach(function(item) {
|
|||
// if (item.key === 'alwaysTop') {
|
|||
// reopenMenuItem = item
|
|||
// }
|
|||
// // if (item.submenu) {
|
|||
// // item.submenu.items.forEach(function(item) {
|
|||
// // if (item.key === 'alwaysTop') {
|
|||
// // reopenMenuItem = item
|
|||
// // }
|
|||
// // })
|
|||
// // }
|
|||
// })
|
|||
// console.log(reopenMenuItem)
|
|||
// return reopenMenuItem
|
|||
// }
|
|||
app.on("ready", function () { |
|||
const menu = Menu.buildFromTemplate(<any>template) |
|||
Menu.setApplicationMenu(menu) |
|||
}) |
|||
app.on("browser-window-created", function () { |
|||
// let reopenMenuItem = findReopenMenuItem()
|
|||
// if (reopenMenuItem) reopenMenuItem.enabled = false
|
|||
}) |
|||
app.on("window-all-closed", function () { |
|||
// let reopenMenuItem = findReopenMenuItem()
|
|||
// if (reopenMenuItem) reopenMenuItem.enabled = true
|
|||
}) |
@ -0,0 +1,12 @@ |
|||
interface IPayload{ |
|||
data: { |
|||
[propName:string]: any; |
|||
}; |
|||
[propName:string]: any; |
|||
} |
|||
|
|||
const payload:IPayload = { |
|||
data: {} |
|||
} |
|||
|
|||
export default payload |
@ -0,0 +1,12 @@ |
|||
|
|||
export function getFileUrl(route: string, isSPA: boolean = true) { |
|||
if (isSPA) { |
|||
const winURL = |
|||
process.env.NODE_ENV === "development" |
|||
? `http://localhost:${process.env.PORT}/#/${route}` |
|||
: `file://${__dirname}/index.html#/${route}` |
|||
return winURL |
|||
} else { |
|||
return "" |
|||
} |
|||
} |
@ -0,0 +1,125 @@ |
|||
// @ts-nocheck
|
|||
|
|||
/** docoment 加载完成 */ |
|||
function domReady(...args) { |
|||
const condition = args.length ? [...args] : ['complete', 'interactive'] |
|||
return new Promise(resolve => { |
|||
if (condition.includes(document.readyState)) { |
|||
resolve(true) |
|||
} else { |
|||
document.addEventListener('readystatechange', () => { |
|||
if (condition.includes(document.readyState)) { |
|||
resolve(true) |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
/** 插入 loading */ |
|||
function loadingBootstrap() { |
|||
const loadingStyle = document.createElement('style'); |
|||
const loadingBox = document.createElement('div'); |
|||
|
|||
loadingStyle.id = 'preload-loading-style'; |
|||
loadingBox.id = 'preload-loading-box'; |
|||
|
|||
loadingStyle.textContent += ` |
|||
/* https://projects.lukehaas.me/css-loaders/ */ |
|||
.loading-box { height: 100vh; width: 100vw; position: fixed; left: 0; top: 0; display: flex; align-items: center; background-color: #242424; z-index: 9; } |
|||
.load1 .loader, |
|||
.load1 .loader:before, |
|||
.load1 .loader:after { |
|||
background: #ffffff; |
|||
-webkit-animation: load1 1s infinite ease-in-out; |
|||
animation: load1 1s infinite ease-in-out; |
|||
width: 1em; |
|||
height: 4em; |
|||
} |
|||
.load1 .loader { |
|||
color: #ffffff; |
|||
text-indent: -9999em; |
|||
margin: 88px auto; |
|||
position: relative; |
|||
font-size: 11px; |
|||
-webkit-transform: translateZ(0); |
|||
-ms-transform: translateZ(0); |
|||
transform: translateZ(0); |
|||
-webkit-animation-delay: -0.16s; |
|||
animation-delay: -0.16s; |
|||
} |
|||
.load1 .loader:before, |
|||
.load1 .loader:after { |
|||
position: absolute; |
|||
top: 0; |
|||
content: ''; |
|||
} |
|||
.load1 .loader:before { |
|||
left: -1.5em; |
|||
-webkit-animation-delay: -0.32s; |
|||
animation-delay: -0.32s; |
|||
} |
|||
.load1 .loader:after { |
|||
left: 1.5em; |
|||
} |
|||
@-webkit-keyframes load1 { |
|||
0%, |
|||
80%, |
|||
100% { |
|||
box-shadow: 0 0; |
|||
height: 4em; |
|||
} |
|||
40% { |
|||
box-shadow: 0 -2em; |
|||
height: 5em; |
|||
} |
|||
} |
|||
@keyframes load1 { |
|||
0%, |
|||
80%, |
|||
100% { |
|||
box-shadow: 0 0; |
|||
height: 4em; |
|||
} |
|||
40% { |
|||
box-shadow: 0 -2em; |
|||
height: 5em; |
|||
} |
|||
}`;
|
|||
|
|||
loadingBox.classList.add('loading-box', 'load1'); |
|||
loadingBox.innerHTML += '<div class="loader"></div>'; |
|||
|
|||
const appendLoading = () => { |
|||
document.head.appendChild(loadingStyle); |
|||
document.body.appendChild(loadingBox); |
|||
}; |
|||
|
|||
const removeLoading = () => { |
|||
const _loadingStyle = document.getElementById('preload-loading-style'); |
|||
const _loadingBox = document.getElementById('preload-loading-box'); |
|||
|
|||
// Ensure the remove child exists.
|
|||
_loadingStyle && document.head.removeChild(_loadingStyle); |
|||
_loadingBox && document.body.removeChild(_loadingBox); |
|||
}; |
|||
|
|||
return { loadingStyle, loadingBox, removeLoading, appendLoading } |
|||
} |
|||
|
|||
; (async function () { |
|||
await domReady(); |
|||
|
|||
let _isCallRemoveLoading = false; |
|||
const { removeLoading, appendLoading } = loadingBootstrap(); |
|||
|
|||
window.removeLoading = () => { |
|||
_isCallRemoveLoading = true; |
|||
removeLoading(); |
|||
}; |
|||
|
|||
// 5 秒超时自动关闭
|
|||
setTimeout(() => !_isCallRemoveLoading && removeLoading(), 4999); |
|||
|
|||
appendLoading(); |
|||
})(); |
@ -0,0 +1,91 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
|
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>electron-vue3-vite</title> |
|||
<style> |
|||
/* https://projects.lukehaas.me/css-loaders/ */ |
|||
body { margin: 0; background-color: #242424; } |
|||
.loading-box { height: 100vh; display: flex; align-items: center; } |
|||
|
|||
.load1 .loader, |
|||
.load1 .loader:before, |
|||
.load1 .loader:after { |
|||
background: #ffffff; |
|||
-webkit-animation: load1 1s infinite ease-in-out; |
|||
animation: load1 1s infinite ease-in-out; |
|||
width: 1em; |
|||
height: 4em; |
|||
} |
|||
|
|||
.load1 .loader { |
|||
color: #ffffff; |
|||
text-indent: -9999em; |
|||
margin: 88px auto; |
|||
position: relative; |
|||
font-size: 11px; |
|||
-webkit-transform: translateZ(0); |
|||
-ms-transform: translateZ(0); |
|||
transform: translateZ(0); |
|||
-webkit-animation-delay: -0.16s; |
|||
animation-delay: -0.16s; |
|||
} |
|||
|
|||
.load1 .loader:before, |
|||
.load1 .loader:after { |
|||
position: absolute; |
|||
top: 0; |
|||
content: ''; |
|||
} |
|||
|
|||
.load1 .loader:before { |
|||
left: -1.5em; |
|||
-webkit-animation-delay: -0.32s; |
|||
animation-delay: -0.32s; |
|||
} |
|||
|
|||
.load1 .loader:after { |
|||
left: 1.5em; |
|||
} |
|||
|
|||
@-webkit-keyframes load1 { |
|||
|
|||
0%, |
|||
80%, |
|||
100% { |
|||
box-shadow: 0 0; |
|||
height: 4em; |
|||
} |
|||
|
|||
40% { |
|||
box-shadow: 0 -2em; |
|||
height: 5em; |
|||
} |
|||
} |
|||
|
|||
@keyframes load1 { |
|||
|
|||
0%, |
|||
80%, |
|||
100% { |
|||
box-shadow: 0 0; |
|||
height: 4em; |
|||
} |
|||
|
|||
40% { |
|||
box-shadow: 0 -2em; |
|||
height: 5em; |
|||
} |
|||
} |
|||
</style> |
|||
</head> |
|||
|
|||
<body> |
|||
<div class="loading-box load1"> |
|||
<div class="loader"></div> |
|||
</div> |
|||
</body> |
|||
|
|||
</html> |
@ -0,0 +1,58 @@ |
|||
import { HashRouter as Router, Switch, Redirect, Route, Link } from "react-router-dom" |
|||
import React, { Fragment } from "react" |
|||
|
|||
import { pageList } from "@/plugins/pageHoc" |
|||
|
|||
import Layout from "@/views/Layout" |
|||
|
|||
import Auth from "@/ui/Auth" |
|||
import routes, { Loading } from "./route" |
|||
|
|||
function LoadWrapper(props: any) { |
|||
if (props.isLazy) { |
|||
const LoadingComp=props.loading?props.loading: ()=><Loading color="#ff0000"></Loading> //NormalLoading;
|
|||
return <React.Suspense fallback={<LoadingComp></LoadingComp>}>{props.children}</React.Suspense> |
|||
} |
|||
return <Fragment>{props.children}</Fragment> |
|||
} |
|||
|
|||
function RouteMap(props: any) { |
|||
const routes: any[] = props.routes |
|||
|
|||
return ( |
|||
<Switch> |
|||
{routes.map((route, index) => { |
|||
const { exact = false } = route |
|||
if (route.redirect) { |
|||
return ( |
|||
<Route key={index} path={route.path} exact={exact}> |
|||
<Redirect to={route.redirect}></Redirect> |
|||
</Route> |
|||
) |
|||
} |
|||
if (route.component) { |
|||
return ( |
|||
<Auth key={index} needAuth={!!route.meta?.auth} path={route.path} exact={exact}> |
|||
<LoadWrapper loading={route.loading} isLazy={typeof route.component.$$typeof === "symbol"}> |
|||
<route.component meta={route.meta}>{route.children && <RouteMap routes={route.children}></RouteMap>}</route.component> |
|||
</LoadWrapper> |
|||
</Auth> |
|||
) |
|||
} |
|||
})} |
|||
</Switch> |
|||
) |
|||
} |
|||
|
|||
export default function () { |
|||
return ( |
|||
<Router> |
|||
<RouteMap routes={routes}></RouteMap> |
|||
{/* <Layout |
|||
render={() => { |
|||
return <RouteMap routes={routes}></RouteMap> |
|||
}} |
|||
></Layout> */} |
|||
</Router> |
|||
) |
|||
} |
After Width: | Height: | Size: 4.0 MiB |
After Width: | Height: | Size: 1.2 MiB |
@ -0,0 +1,10 @@ |
|||
{ |
|||
"login":{ |
|||
"title": "Bronze Age 2022", |
|||
"username_placeholder": "Username", |
|||
"password_placeholder": "Password", |
|||
"btnText": "Login" |
|||
}, |
|||
"home":"Bronze Age 2021", |
|||
"welcome":"Welcome To Home" |
|||
} |
@ -0,0 +1,10 @@ |
|||
{ |
|||
"login":{ |
|||
"title": "青铜时代2022", |
|||
"username_placeholder": "用户名", |
|||
"password_placeholder": "密码", |
|||
"btnText": "登录" |
|||
}, |
|||
"home":"青铜时代2021", |
|||
"welcome":"欢迎来首页" |
|||
} |
@ -0,0 +1,112 @@ |
|||
/* cyrillic-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wmhduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
|||
} |
|||
/* cyrillic */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wkxduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
|||
} |
|||
/* greek-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wmxduz8A.woff2) format('woff2'); |
|||
unicode-range: U+1F00-1FFF; |
|||
} |
|||
/* greek */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wlBduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0370-03FF; |
|||
} |
|||
/* vietnamese */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wmBduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
|||
} |
|||
/* latin-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wmRduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
|||
} |
|||
/* latin */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 200; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3i94_wlxdu.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
|||
/* cyrillic-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmhduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; |
|||
} |
|||
/* cyrillic */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; |
|||
} |
|||
/* greek-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxduz8A.woff2) format('woff2'); |
|||
unicode-range: U+1F00-1FFF; |
|||
} |
|||
/* greek */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0370-03FF; |
|||
} |
|||
/* vietnamese */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; |
|||
} |
|||
/* latin-ext */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRduz8A.woff2) format('woff2'); |
|||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; |
|||
} |
|||
/* latin */ |
|||
@font-face { |
|||
font-family: 'Source Sans Pro'; |
|||
font-style: normal; |
|||
font-weight: 300; |
|||
src: url(https://fonts.gstatic.com/s/sourcesanspro/v14/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2) format('woff2'); |
|||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; |
|||
} |
@ -0,0 +1,127 @@ |
|||
/* http://meyerweb.com/eric/tools/css/reset/ |
|||
v2.0 | 20110126 |
|||
License: none (public domain) |
|||
*/ |
|||
|
|||
html, |
|||
body, |
|||
div, |
|||
span, |
|||
applet, |
|||
object, |
|||
iframe, |
|||
h1, |
|||
h2, |
|||
h3, |
|||
h4, |
|||
h5, |
|||
h6, |
|||
p, |
|||
blockquote, |
|||
pre, |
|||
a, |
|||
abbr, |
|||
acronym, |
|||
address, |
|||
big, |
|||
cite, |
|||
code, |
|||
del, |
|||
dfn, |
|||
em, |
|||
img, |
|||
ins, |
|||
kbd, |
|||
q, |
|||
s, |
|||
samp, |
|||
small, |
|||
strike, |
|||
strong, |
|||
sub, |
|||
sup, |
|||
tt, |
|||
var, |
|||
b, |
|||
u, |
|||
i, |
|||
center, |
|||
dl, |
|||
dt, |
|||
dd, |
|||
ol, |
|||
ul, |
|||
li, |
|||
fieldset, |
|||
form, |
|||
label, |
|||
legend, |
|||
table, |
|||
caption, |
|||
tbody, |
|||
tfoot, |
|||
thead, |
|||
tr, |
|||
th, |
|||
td, |
|||
article, |
|||
aside, |
|||
canvas, |
|||
details, |
|||
embed, |
|||
figure, |
|||
figcaption, |
|||
footer, |
|||
header, |
|||
hgroup, |
|||
menu, |
|||
nav, |
|||
output, |
|||
ruby, |
|||
section, |
|||
summary, |
|||
time, |
|||
mark, |
|||
audio, |
|||
video { |
|||
margin: 0; |
|||
padding: 0; |
|||
border: 0; |
|||
font-size: 100%; |
|||
font: inherit; |
|||
vertical-align: baseline; |
|||
} |
|||
/* HTML5 display-role reset for older browsers */ |
|||
article, |
|||
aside, |
|||
details, |
|||
figcaption, |
|||
figure, |
|||
footer, |
|||
header, |
|||
hgroup, |
|||
menu, |
|||
nav, |
|||
section { |
|||
display: block; |
|||
} |
|||
|
|||
ol, |
|||
ul { |
|||
list-style: none; |
|||
} |
|||
blockquote, |
|||
q { |
|||
quotes: none; |
|||
} |
|||
blockquote:before, |
|||
blockquote:after, |
|||
q:before, |
|||
q:after { |
|||
content: ""; |
|||
content: none; |
|||
} |
|||
table { |
|||
border-collapse: collapse; |
|||
border-spacing: 0; |
|||
} |
@ -0,0 +1,28 @@ |
|||
@import "./reset.scss"; |
|||
@import "./_font.scss"; |
|||
|
|||
body, |
|||
button, |
|||
input, |
|||
select, |
|||
textarea { |
|||
font: 12px/1.5 tahoma, arial, "Hiragino Sans GB", "\5b8b\4f53", sans-serif; |
|||
} |
|||
|
|||
body{ |
|||
// background-image: url("@/assets/images/0.jpg"); |
|||
// background-attachment: fixed; |
|||
// background-position: center top 36px; |
|||
// background-repeat: no-repeat; |
|||
// background-size: cover; |
|||
} |
|||
a { |
|||
// color: initial; |
|||
display: inline-block; |
|||
text-decoration: none; |
|||
color: inherit; |
|||
} |
|||
|
|||
.clearfix { |
|||
@include clearfix; |
|||
} |
@ -0,0 +1,14 @@ |
|||
$color: green; |
|||
|
|||
|
|||
@mixin clearfix { |
|||
&:after { |
|||
clear: both; |
|||
content: "."; |
|||
display: block; |
|||
height: 0; |
|||
line-height: 0; |
|||
overflow: hidden; |
|||
} |
|||
*height: 1%; |
|||
} |
@ -0,0 +1,11 @@ |
|||
import React from "react"; |
|||
import cs from "classnames" |
|||
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; |
|||
|
|||
export default function () { |
|||
return ( |
|||
<div className={cs("header clearfix")}> |
|||
这是一个标题 |
|||
</div> |
|||
); |
|||
} |
@ -0,0 +1,120 @@ |
|||
|
|||
.bg-bubbles { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 100%; |
|||
|
|||
z-index: 1; |
|||
|
|||
li { |
|||
position: absolute; |
|||
list-style: none; |
|||
display: block; |
|||
width: 40px; |
|||
height: 40px; |
|||
background-color: rgba(255, 255, 255, 0.15); |
|||
bottom: -160px; |
|||
|
|||
-webkit-animation: square 25s infinite; |
|||
animation: square 25s infinite; |
|||
|
|||
-webkit-transition-timing-function: linear; |
|||
transition-timing-function: linear; |
|||
|
|||
&:nth-child(1) { |
|||
left: 10%; |
|||
} |
|||
|
|||
&:nth-child(2) { |
|||
left: 20%; |
|||
|
|||
width: 80px; |
|||
height: 80px; |
|||
|
|||
animation-delay: 2s; |
|||
animation-duration: 17s; |
|||
} |
|||
|
|||
&:nth-child(3) { |
|||
left: 25%; |
|||
animation-delay: 4s; |
|||
} |
|||
|
|||
&:nth-child(4) { |
|||
left: 40%; |
|||
width: 60px; |
|||
height: 60px; |
|||
|
|||
animation-duration: 22s; |
|||
|
|||
background-color: rgba(255, 255, 255, 0.25); |
|||
} |
|||
|
|||
&:nth-child(5) { |
|||
left: 70%; |
|||
} |
|||
|
|||
&:nth-child(6) { |
|||
left: 80%; |
|||
width: 120px; |
|||
height: 120px; |
|||
|
|||
animation-delay: 3s; |
|||
background-color: rgba(255, 255, 255, 0.2); |
|||
} |
|||
|
|||
&:nth-child(7) { |
|||
left: 32%; |
|||
width: 160px; |
|||
height: 160px; |
|||
|
|||
animation-delay: 7s; |
|||
} |
|||
|
|||
&:nth-child(8) { |
|||
left: 55%; |
|||
width: 20px; |
|||
height: 20px; |
|||
|
|||
animation-delay: 15s; |
|||
animation-duration: 40s; |
|||
} |
|||
|
|||
&:nth-child(9) { |
|||
left: 25%; |
|||
width: 10px; |
|||
height: 10px; |
|||
|
|||
animation-delay: 2s; |
|||
animation-duration: 40s; |
|||
background-color: rgba(255, 255, 255, 0.3); |
|||
} |
|||
|
|||
&:nth-child(10) { |
|||
left: 90%; |
|||
width: 160px; |
|||
height: 160px; |
|||
|
|||
animation-delay: 11s; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@-webkit-keyframes square { |
|||
0% { |
|||
transform: translateY(0); |
|||
} |
|||
100% { |
|||
transform: translateY(-700px) rotate(600deg); |
|||
} |
|||
} |
|||
@keyframes square { |
|||
0% { |
|||
transform: translateY(0); |
|||
} |
|||
100% { |
|||
transform: translateY(-700px) rotate(600deg); |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
import React from "react" |
|||
import style from "./Bubbles.module.scss" |
|||
export default function () { |
|||
return ( |
|||
<ul className={style["bg-bubbles"]}> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
<li></li> |
|||
</ul> |
|||
) |
|||
} |
@ -0,0 +1,36 @@ |
|||
.ring { |
|||
position: relative; |
|||
width: 45px; |
|||
height: 45px; |
|||
margin: 0 auto; |
|||
border: 4px solid #4b9cdb; |
|||
border-radius: 100%; |
|||
} |
|||
|
|||
.ball { |
|||
position: absolute; |
|||
top: -11px; |
|||
left: 0; |
|||
width: 16px; |
|||
height: 16px; |
|||
border-radius: 100%; |
|||
background: #4282b3; |
|||
} |
|||
|
|||
.loading .holder { |
|||
position: absolute; |
|||
width: 12px; |
|||
height: 45px; |
|||
left: 17px; |
|||
top: 0px; |
|||
animation: loadingE 1.3s linear infinite; |
|||
} |
|||
|
|||
@keyframes loadingE { |
|||
0% { |
|||
transform: rotate(0deg); |
|||
} |
|||
100% { |
|||
transform: rotate(360deg); |
|||
} |
|||
} |
@ -0,0 +1,14 @@ |
|||
import React from "react" |
|||
import style from "./Rainbow.module.scss" |
|||
|
|||
console.log(style) |
|||
|
|||
export default () => ( |
|||
<div className={style.loading}> |
|||
<div className={style.ring}> |
|||
<div className={style.holder}> |
|||
<div className={style.ball}></div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
) |
@ -0,0 +1,16 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="en"> |
|||
<head> |
|||
<meta charset="UTF-8" /> |
|||
<link rel="icon" href="/favicon.ico" /> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|||
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self' https: 'unsafe-inline'; style-src * 'unsafe-inline'; font-src * data:;"> --> |
|||
<!-- <meta http-equiv="X-Content-Security-Policy" content="default-src 'self' https: 'unsafe-inline'; style-src * 'unsafe-inline'; font-src * data:;"> --> |
|||
<!-- --> |
|||
<title><%- title %></title> |
|||
</head> |
|||
<body> |
|||
<div id="root"></div> |
|||
<script type="module" src="/main.tsx"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,24 @@ |
|||
import "@/assets/style/common.scss"; |
|||
import store from "@/store"; |
|||
import React from "react"; |
|||
import ReactDOM from "react-dom"; |
|||
import "@/plugins/i18n" |
|||
import {Provider} from 'react-redux' |
|||
|
|||
import electron from "@/plugins/electron" |
|||
|
|||
import Router from "./AppRouter"; |
|||
|
|||
// 静态资源地址变量请用"__static",在html中使用请用__static
|
|||
console.log(electron); |
|||
console.log(__staticVar); |
|||
|
|||
|
|||
ReactDOM.render( |
|||
<React.StrictMode> |
|||
<Provider store={store}> |
|||
<Router></Router> |
|||
</Provider> |
|||
</React.StrictMode>, |
|||
document.getElementById("root") |
|||
); |
@ -0,0 +1,3 @@ |
|||
const electron = require("electron") // 只能用require
|
|||
|
|||
export default electron |
@ -0,0 +1,28 @@ |
|||
import LanguageDetector from "i18next-browser-languagedetector" |
|||
import i18n from "i18next" |
|||
import enUsTrans from "@/assets/locales/en-us.json" |
|||
import zhCnTrans from "@/assets/locales/zh-cn.json" |
|||
import { initReactI18next } from "react-i18next" |
|||
|
|||
i18n |
|||
.use(LanguageDetector) //嗅探当前浏览器语言
|
|||
.use(initReactI18next) //init i18next
|
|||
.init({ |
|||
//引入资源文件
|
|||
resources: { |
|||
en: { |
|||
translation: enUsTrans, |
|||
}, |
|||
zh: { |
|||
translation: zhCnTrans, |
|||
}, |
|||
}, |
|||
//选择默认语言,选择内容为上述配置中的key,即en/zh
|
|||
fallbackLng: "zh", |
|||
debug: false, |
|||
interpolation: { |
|||
escapeValue: false, // not needed for react as it escapes by default
|
|||
}, |
|||
}) |
|||
|
|||
export default i18n |
@ -0,0 +1,27 @@ |
|||
import React, { Component } from "react" |
|||
|
|||
export const pageList: any[] = [] |
|||
|
|||
export default function (options: any) { |
|||
return (WrappedComponent: any) => { |
|||
const { |
|||
path, // 页面路径
|
|||
} = options |
|||
|
|||
class HOC extends React.Component { |
|||
constructor(props: any) { |
|||
super(props) |
|||
} |
|||
componentDidMount() {} |
|||
|
|||
componentWillUnmount() {} |
|||
|
|||
render() { |
|||
return <WrappedComponent {...options} {...this.props} /> |
|||
} |
|||
} |
|||
|
|||
pageList.push([{ ...options, Component: HOC }]) |
|||
return HOC |
|||
} |
|||
} |
@ -0,0 +1,4 @@ |
|||
export interface IOption { |
|||
page: string; // 页面路径
|
|||
title: string; // 页面标题
|
|||
} |
@ -0,0 +1,78 @@ |
|||
import Page404 from "@/views/Auth/Page404" |
|||
import Login from "@/views/Login" |
|||
import React, { lazy } from "react" |
|||
|
|||
export const Loading = (props: any) => <div style={{ color: props.color }}>Lodeing.22..</div> |
|||
|
|||
let delay = |
|||
(Comp: any, duration = 1000) => |
|||
() => |
|||
new Promise<any>(resolve => |
|||
Comp() |
|||
.then((res: any) => { |
|||
setTimeout(() => { |
|||
resolve(res) |
|||
}, duration) |
|||
}) |
|||
.catch(() => { |
|||
resolve({ |
|||
default: () => <div>Error</div>, |
|||
}) |
|||
}) |
|||
) |
|||
|
|||
export default [ |
|||
{ |
|||
path: "/about", |
|||
component: lazy(() => import("@/views/About")), |
|||
exact: false, |
|||
meta: { |
|||
auth: false, |
|||
}, |
|||
loading: () => <Loading color="red"></Loading>, |
|||
children: [ |
|||
{ |
|||
path: "/about/test", |
|||
exact: true, |
|||
meta: { |
|||
auth: false, |
|||
}, |
|||
loading: () => <Loading color="green"></Loading>, |
|||
component: lazy(delay(() => import("@/views/Login/Test"))), |
|||
}, |
|||
], |
|||
}, |
|||
{ |
|||
path: "/float", |
|||
component: lazy(() => import("@/views/Float")), |
|||
exact: true, |
|||
meta: { |
|||
auth: false, |
|||
}, |
|||
children: [], |
|||
}, |
|||
{ |
|||
path: "/home", |
|||
component: lazy(() => import("@/views/Home")), |
|||
exact: true, |
|||
loading: () => <Loading color="blue"></Loading>, |
|||
meta: { |
|||
auth: false, |
|||
}, |
|||
children: [], |
|||
}, |
|||
{ |
|||
path: "/", |
|||
exact: true, |
|||
redirect: "/home", |
|||
}, |
|||
{ |
|||
path: "/login", |
|||
component: Login //lazy(() => import("@/views/Login")),
|
|||
}, |
|||
{ |
|||
path: "*", |
|||
exact: false, |
|||
component: Page404, |
|||
}, |
|||
] |
@ -0,0 +1 @@ |
|||
export * from "./todo" |
@ -0,0 +1,17 @@ |
|||
import {ADD, REMOVE, ADD_ASYNC} from "@/store/constant/todo" |
|||
|
|||
let nextTodoId = 0 |
|||
|
|||
type Action = (param: any) => IAction |
|||
|
|||
export const addTodo: Action = text => ({ |
|||
type: ADD_ASYNC, |
|||
id: nextTodoId++, |
|||
text |
|||
}) |
|||
|
|||
export const removeTodo: Action = id => ({ |
|||
type: REMOVE, |
|||
id: id |
|||
}) |
|||
|
@ -0,0 +1,4 @@ |
|||
export const ADD = "ADD_TODO" |
|||
export const ADD_ASYNC = "ADD_ASYNC" |
|||
|
|||
export const REMOVE = "REMOVE_TODO" |
@ -0,0 +1,13 @@ |
|||
import {applyMiddleware, combineReducers, createStore} from 'redux' |
|||
import reducer from "./reducer" |
|||
import createSagaMiddleware from 'redux-saga' |
|||
import mySaga from './saga' |
|||
|
|||
const sagaMiddleware = createSagaMiddleware() |
|||
|
|||
const reduces = combineReducers(reducer) |
|||
const store = createStore(reduces, applyMiddleware(sagaMiddleware)) |
|||
|
|||
sagaMiddleware.run(mySaga) |
|||
|
|||
export default store |
@ -0,0 +1,5 @@ |
|||
import todo from "./todo" |
|||
|
|||
export default { |
|||
todo |
|||
} |
@ -0,0 +1,26 @@ |
|||
import {ADD, REMOVE} from "@/store/constant/todo" |
|||
import {combineReducers} from "redux"; |
|||
|
|||
let initData: ITodo[] = [] |
|||
|
|||
const todos = (state = initData, action: IAction) => { |
|||
switch (action.type) { |
|||
case ADD: |
|||
return [ |
|||
...state, |
|||
{ |
|||
id: action.id, |
|||
text: action.text |
|||
} |
|||
] |
|||
case REMOVE: |
|||
const list = state.filter(v => v.id != action.id) |
|||
return [ |
|||
...list |
|||
] |
|||
default: |
|||
return state |
|||
} |
|||
} |
|||
|
|||
export default todos; |
@ -0,0 +1,52 @@ |
|||
import { call, put, takeEvery, takeLatest } from "redux-saga/effects" |
|||
import { ADD, REMOVE, ADD_ASYNC } from "@/store/constant/todo" |
|||
import axios, { Method } from "axios" |
|||
|
|||
const instance = axios.create({ |
|||
timeout: 3000, |
|||
}) |
|||
const http = (method: Method = "GET", url: string) => { |
|||
return instance({ |
|||
method, |
|||
url, |
|||
}) |
|||
} |
|||
|
|||
let Api: any = { |
|||
fetchUser: async (id: any) => { |
|||
return await http("GET", "https://gank.io/api/v2/banners") |
|||
}, |
|||
} |
|||
|
|||
// worker Saga : 将在 USER_FETCH_REQUESTED action 被 dispatch 时调用
|
|||
function* fetchUser(action: any) { |
|||
try { |
|||
// @ts-ignore
|
|||
const user = yield call(Api.fetchUser, action?.id) |
|||
console.log(user) |
|||
yield put({ type: ADD, id: action.id, text: user.data.data[0].image }) |
|||
} catch (e) { |
|||
yield put({ type: "USER_FETCH_FAILED", message: e.message }) |
|||
} |
|||
} |
|||
|
|||
/* |
|||
在每个 `USER_FETCH_REQUESTED` action 被 dispatch 时调用 fetchUser |
|||
允许并发(译注:即同时处理多个相同的 action) |
|||
*/ |
|||
function* mySaga() { |
|||
yield takeEvery(ADD_ASYNC, fetchUser) |
|||
} |
|||
|
|||
/* |
|||
也可以使用 takeLatest |
|||
|
|||
不允许并发,dispatch 一个 `USER_FETCH_REQUESTED` action 时, |
|||
如果在这之前已经有一个 `USER_FETCH_REQUESTED` action 在处理中, |
|||
那么处理中的 action 会被取消,只会执行当前的 |
|||
*/ |
|||
// function* mySaga() {
|
|||
// yield takeLatest("USER_FETCH_REQUESTED", fetchUser)
|
|||
// }
|
|||
|
|||
export default mySaga |
@ -0,0 +1,4 @@ |
|||
interface ITodo { |
|||
id: number; |
|||
text: string; |
|||
} |
@ -0,0 +1,37 @@ |
|||
import React, { Component, ReactElement } from "react" |
|||
// 高阶组件,就是对原来的组件进行封装
|
|||
import { Route, Redirect, NavLink } from "react-router-dom" |
|||
|
|||
interface IProps { |
|||
needAuth?: boolean |
|||
path: string |
|||
exact?: boolean |
|||
} |
|||
|
|||
class Auth extends Component<IProps> { |
|||
constructor(props: IProps) { |
|||
super(props) |
|||
} |
|||
|
|||
render() { |
|||
const { path, needAuth = false, exact = false } = this.props // 结构语法的概
|
|||
if (!needAuth) { |
|||
return ( |
|||
// 如果已经登陆,就直接显示原来的组件
|
|||
<Route exact={exact} path={path}> |
|||
{this.props.children} |
|||
</Route> |
|||
) |
|||
} else { |
|||
return ( |
|||
<div> |
|||
<NavLink to="/login">你还有没有登陆好吧!</NavLink> |
|||
|
|||
{/* <Redirect to={{ pathname: '/login', state: { from: { path }} }} > </Redirect> */} |
|||
</div> |
|||
) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default Auth |
@ -0,0 +1,27 @@ |
|||
import React from "react" |
|||
import { useLocation, Route, Switch, useHistory } from "react-router-dom" |
|||
|
|||
function Test() { |
|||
return <div>test</div> |
|||
} |
|||
|
|||
export default function About(props: any) { |
|||
let history = useHistory() |
|||
// let location = useLocation()
|
|||
function back(){ |
|||
console.log(1231); |
|||
|
|||
history.goBack() |
|||
} |
|||
return ( |
|||
<div className=""> |
|||
<div onClick={() =>back()}>阿萨 阿松大asdasd</div> |
|||
<div>22{props.children}{location.href}</div> |
|||
<p>sadsa</p> |
|||
<p>sadsa</p> |
|||
<Route path="/about/aa" exact={true}> |
|||
<Test></Test> |
|||
</Route> |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,10 @@ |
|||
import React from "react" |
|||
import { NavLink } from "react-router-dom" |
|||
|
|||
export default function Page404() { |
|||
return ( |
|||
<div> |
|||
<NavLink to="/">阿松大asddadsa</NavLink> |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,10 @@ |
|||
.view-float { |
|||
width: 100px; |
|||
height: 25px; |
|||
line-height: 25px; |
|||
background-color: #fff; |
|||
position: fixed; |
|||
overflow: auto; |
|||
user-select: none; |
|||
border: 1px solid red; |
|||
} |
@ -0,0 +1,43 @@ |
|||
import React, { MouseEventHandler, useEffect } from "react" |
|||
import electron from "@/plugins/electron" |
|||
import style from "./index.module.scss" |
|||
import { useLocation, Route, Switch, useHistory } from "react-router-dom" |
|||
|
|||
export default function Float(props: any) { |
|||
useEffect(() => { |
|||
// let win = electron.remote.getCurrentWindow()
|
|||
let biasX = 0 |
|||
let biasY = 0 |
|||
document.addEventListener("mousedown", function (e) { |
|||
switch (e.button) { |
|||
case 0: |
|||
biasX = e.x |
|||
biasY = e.y |
|||
document.addEventListener("mousemove", moveEvent) |
|||
break |
|||
case 2: |
|||
electron.ipcRenderer.send("createSuspensionMenu") |
|||
break |
|||
} |
|||
}) |
|||
|
|||
document.addEventListener("mouseup", function () { |
|||
biasX = 0 |
|||
biasY = 0 |
|||
document.removeEventListener("mousemove", moveEvent) |
|||
}) |
|||
|
|||
function moveEvent(e: any) { |
|||
electron.ipcRenderer.send('@float:setPosition', e.screenX - biasX, e.screenY - biasY) |
|||
// win.setPosition(e.screenX - biasX, e.screenY - biasY)
|
|||
} |
|||
}, []) |
|||
function dblclick(e: MouseEventHandler<HTMLDivElement>) { |
|||
alert("asd") |
|||
} |
|||
return ( |
|||
<div className={style["view-float"]} onDoubleClick={(e: any) => dblclick(e)}> |
|||
悬浮窗 |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,49 @@ |
|||
import { addTodo, removeTodo } from "@/store/action/todo" |
|||
import React, { FormEvent, useRef, useContext } from "react" |
|||
import { connect } from "react-redux" |
|||
import { NavLink } from "react-router-dom" |
|||
|
|||
export interface HomeProps { |
|||
add(text: string): void |
|||
todo: ITodo[] |
|||
remove(id: number): void |
|||
} |
|||
|
|||
function Home(props: HomeProps) { |
|||
const { todo, add, remove } = props |
|||
const inputRef = useRef<HTMLInputElement>(null) |
|||
|
|||
function addOne(e: FormEvent) { |
|||
e.preventDefault() |
|||
let text = inputRef.current!.value |
|||
if (text) { |
|||
inputRef.current!.value = "" |
|||
add(text) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<div> |
|||
<NavLink to="/home">首页</NavLink> <br /> |
|||
<NavLink to="/about">关于</NavLink><br /> |
|||
<NavLink to="/login">登录</NavLink><br /> |
|||
<NavLink to="/float">Float</NavLink><br /> |
|||
<div> |
|||
<img src='__static/icon.png' style={{width:"50px",height: "50px"}} alt=""/> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
|||
|
|||
const mapStateToProps = (state: any) => { |
|||
return { |
|||
todo: state.todo, |
|||
} |
|||
} |
|||
|
|||
const mapDispatchToProps = (dispatch: any) => ({ |
|||
add: (text: string) => dispatch(addTodo(text)), |
|||
remove: (id: string | number) => dispatch(removeTodo(id)), |
|||
}) |
|||
|
|||
export default connect(mapStateToProps, mapDispatchToProps)(Home) |
@ -0,0 +1,16 @@ |
|||
import React, { ReactElement } from "react" |
|||
import { useLocation } from "react-router-dom" |
|||
|
|||
// interface IProps {
|
|||
// render(): ReactElement
|
|||
// }
|
|||
|
|||
export default function (props: any) { |
|||
return ( |
|||
<div> |
|||
<div className="content"> |
|||
<props.render></props.render> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,9 @@ |
|||
import React from "react"; |
|||
|
|||
export default function(){ |
|||
return ( |
|||
<div> |
|||
saadsadsaddddddddddddddddd |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1,107 @@ |
|||
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,300); |
|||
|
|||
$color: #53e3a6; |
|||
.login.page { |
|||
font-family: "Source Sans Pro", sans-serif; |
|||
color: white; |
|||
font-weight: 300; |
|||
.wrapper { |
|||
background: #50a3a2; |
|||
background: -webkit-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); |
|||
background: -moz-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); |
|||
background: -o-linear-gradient(top left, #50a3a2 0%, #53e3a6 100%); |
|||
background: linear-gradient(to bottom right, #50a3a2 0%, #53e3a6 100%); |
|||
|
|||
position: absolute; |
|||
top: 50%; |
|||
left: 0; |
|||
width: 100%; |
|||
height: 400px; |
|||
margin-top: -200px; |
|||
overflow: hidden; |
|||
|
|||
&.form-success { |
|||
form { |
|||
opacity: 0; |
|||
pointer-events: none; |
|||
} |
|||
.container { |
|||
h1 { |
|||
transform: translateY(85px); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.container { |
|||
max-width: 600px; |
|||
margin: 0 auto; |
|||
padding: 80px 0; |
|||
height: 400px; |
|||
text-align: center; |
|||
|
|||
h1 { |
|||
font-size: 40px; |
|||
transition-duration: 1s; |
|||
transition-timing-function: ease-in-put; |
|||
font-weight: 200; |
|||
} |
|||
} |
|||
|
|||
form { |
|||
padding: 20px 0; |
|||
position: relative; |
|||
z-index: 2; |
|||
transition: opacity 0.5s linear; |
|||
|
|||
input { |
|||
display: block; |
|||
appearance: none; |
|||
outline: 0; |
|||
border: 1px solid rgba(255, 255, 255, 0.4); |
|||
background-color: rgba(255, 255, 255, 0.2); |
|||
width: 250px; |
|||
|
|||
border-radius: 3px; |
|||
padding: 10px 15px; |
|||
margin: 0 auto 10px auto; |
|||
display: block; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
|
|||
color: white; |
|||
|
|||
transition-duration: 0.25s; |
|||
font-weight: 300; |
|||
|
|||
&:hover { |
|||
background-color: rgba(255, 255, 255, 0.4); |
|||
} |
|||
|
|||
&:focus { |
|||
background-color: white; |
|||
width: 300px; |
|||
|
|||
color: $color; |
|||
} |
|||
} |
|||
|
|||
button { |
|||
appearance: none; |
|||
outline: 0; |
|||
background-color: white; |
|||
border: 0; |
|||
padding: 10px 15px; |
|||
color: $color; |
|||
border-radius: 3px; |
|||
width: 250px; |
|||
cursor: pointer; |
|||
font-size: 18px; |
|||
transition-duration: 0.25s; |
|||
|
|||
&:hover { |
|||
background-color: rgb(245, 247, 249); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
import React, { useRef } from "react" |
|||
import cn from "classnames" |
|||
import style from "./index.module.scss" |
|||
import { useLocation, Route, Switch } from "react-router-dom" |
|||
import { useState } from "react" |
|||
import Bubbles from "@/components/Loading/Bubbles" |
|||
import { Trans, Translation, useTranslation } from "react-i18next" |
|||
|
|||
export default function Login(props: any) { |
|||
const [isSuccess, setIsSuccess] = useState(false) |
|||
const [usename, setUsername] = useState("23") |
|||
const [password, setPassword] = useState("") |
|||
const { t ,i18n} = useTranslation() |
|||
|
|||
function clickLogin(event: any) { |
|||
event.preventDefault() |
|||
|
|||
setIsSuccess(true) |
|||
setTimeout(() => { |
|||
setUsername('') |
|||
setPassword('') |
|||
setIsSuccess(false) |
|||
}, 2000) |
|||
} |
|||
|
|||
return ( |
|||
<div className={cn(style.login, style.page)}> |
|||
|
|||
<div style={{background:"red"}}> |
|||
<button onClick={()=>i18n.changeLanguage(i18n.language=='en'?'zh':'en')}>{i18n.language=='en'?'zh':'en'}</button> |
|||
<h1>{t('home')}阿松大</h1> |
|||
<h1>{usename}</h1> |
|||
<h1>{password}</h1> |
|||
<h2><Trans>home</Trans></h2> |
|||
<Translation>{t => <h3>{t('home')}</h3>}</Translation> |
|||
</div> |
|||
|
|||
<div className={cn(style.wrapper, isSuccess ? style["form-success"] : "")}> |
|||
<div className={cn(style.container)}> |
|||
<h1><Trans>login.title</Trans></h1> |
|||
<form className={cn(style.form)}> |
|||
<input type="text" value={usename} onChange={(e)=>setUsername(e.target.value)} placeholder={t('login.username_placeholder')} /> |
|||
<input type="password" value={password} onChange={(e)=>setPassword(e.target.value)} placeholder={t('login.password_placeholder')} autoComplete="" /> |
|||
<button type="submit" id={cn(style["login-login"])} onClick={clickLogin}> |
|||
<Trans>login.btnText</Trans> |
|||
</button> |
|||
</form> |
|||
</div> |
|||
<Bubbles></Bubbles> |
|||
</div> |
|||
</div> |
|||
) |
|||
} |
@ -0,0 +1 @@ |
|||
/// <reference types="vite/client" />
|
@ -0,0 +1,18 @@ |
|||
const { say } = require("cfonts") |
|||
const chalk = require("chalk") |
|||
|
|||
|
|||
function electronLog(data, color) { |
|||
let log = "" |
|||
data = data.toString().split(/\r?\n/) |
|||
data.forEach(line => { |
|||
log += ` ${line}\n` |
|||
}) |
|||
if (/[0-9A-z]+/.test(log)) { |
|||
console.log( |
|||
chalk[color].bold("┏ Electron -------------------") + "\n\n" + log + chalk[color].bold("┗ ----------------------------") + "\n" |
|||
) |
|||
} |
|||
} |
|||
|
|||
electronLog("dfssssssssssssssssssssssssssssssssssssssssssssssss","red") |
@ -0,0 +1,35 @@ |
|||
|
|||
{ |
|||
"compilerOptions": { |
|||
"target": "ESNext", |
|||
"lib": ["DOM", "DOM.Iterable", "ESNext"], |
|||
"allowJs": false, |
|||
"skipLibCheck": false, |
|||
"esModuleInterop": false, |
|||
"allowSyntheticDefaultImports": true, |
|||
"strict": true, |
|||
"forceConsistentCasingInFileNames": true, |
|||
"module": "ESNext", |
|||
"moduleResolution": "node", |
|||
"resolveJsonModule": true, |
|||
"isolatedModules": true, |
|||
"noEmit": false, |
|||
"jsx": "react", |
|||
"rootDir": ".", |
|||
"baseUrl": ".", |
|||
"paths": { |
|||
"@/*": ["src/render/*"], |
|||
"@render/*": ["src/render/*"], |
|||
"@main/*": ["src/main/*"], |
|||
"@src/*": ["src/*"], |
|||
"@root/*": ["./*"] |
|||
}, |
|||
}, |
|||
"include": ["src", "types"], |
|||
"exclude": ["node_modules"], |
|||
"ts-node": { |
|||
"compilerOptions": { |
|||
"module": "CommonJS" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,12 @@ |
|||
|
|||
interface IAny{ |
|||
[props: string]: any; |
|||
} |
|||
|
|||
interface IAction extends IAny{ |
|||
type: string |
|||
} |
|||
|
|||
|
|||
declare const __static: string; |
|||
declare const __staticVar: string; |
@ -0,0 +1,65 @@ |
|||
import { defineConfig } from "vite" |
|||
import reactRefresh from "@vitejs/plugin-react-refresh" |
|||
// import WindiCSS from "vite-plugin-windicss"
|
|||
const { resolve, join } = require("path") |
|||
import electron from "vitejs-plugin-electron" |
|||
import { minifyHtml, injectHtml } from "vite-plugin-html" |
|||
import replace from '@rollup/plugin-replace'; |
|||
|
|||
|
|||
let isDev = process.env.NODE_ENV === "development" |
|||
|
|||
let plugins = [] |
|||
let staticPath = isDev? '/static': 'static' |
|||
plugins.push( |
|||
replace({ |
|||
preventAssignment: true, |
|||
"__static": staticPath, |
|||
"__staticVar": `"${staticPath}"` |
|||
}) |
|||
) |
|||
|
|||
// https://vitejs.dev/config/
|
|||
export default defineConfig({ |
|||
root: resolve(__dirname, "src/render"), |
|||
base: "./", |
|||
publicDir: resolve(__dirname, "resource/electron"), |
|||
css: { |
|||
preprocessorOptions: { |
|||
scss: { |
|||
additionalData: `@import "@/assets/style/global.scss";`, |
|||
}, |
|||
}, |
|||
}, |
|||
server: { |
|||
port: +process.env.PORT, |
|||
}, |
|||
build: { |
|||
outDir: resolve(__dirname, "dist/electron"), |
|||
emptyOutDir: true, |
|||
minify: false, |
|||
commonjsOptions: {}, |
|||
assetsDir: "", // 相对路径 加载问题
|
|||
sourcemap: true, |
|||
}, |
|||
resolve: { |
|||
alias: { |
|||
"@": join(__dirname, "src/render"), |
|||
"@render": join(__dirname, "src/render"), |
|||
"@main": join(__dirname, "src/main"), |
|||
"@src": join(__dirname, "src"), |
|||
"@root": __dirname, |
|||
}, |
|||
}, |
|||
//
|
|||
plugins: [ |
|||
...plugins, |
|||
reactRefresh(), |
|||
minifyHtml(), |
|||
injectHtml({ |
|||
injectData: { |
|||
title: "代码片段管理工具" |
|||
}, |
|||
}), |
|||
], |
|||
}) |
@ -0,0 +1,34 @@ |
|||
### 渲染层 |
|||
|
|||
* 必须使用hash模式,方便 |
|||
|
|||
全局替换字符 |
|||
|
|||
* `__static`: 静态资源字符串,注意没有用引号包裹,不能直接赋值 |
|||
* `__staticVar`: 静态资源字符串变量,可以赋值 |
|||
|
|||
|
|||
* 静态资源目录为根目录:`resource/electron`, 里面的东西会原封不动的复制到`dist/electron`中 |
|||
|
|||
预加载资源 |
|||
* 预加载的脚本会自动从`src/preload`直接复制到`dist/src/preload`; |
|||
|
|||
### mian层 |
|||
|
|||
全局变量 |
|||
|
|||
* `__static`: `resource/electron/static`的资源目录字符串变量,生产环境则是在`entry.js`根据此文件定位的`./static`中 |
|||
|
|||
|
|||
## 开发环境变量 |
|||
* `process.env.PORT` 开发环境时启动服务的端口,可以修改 |
|||
* `process.env.NODE_ENV` 区分是开发环境 |
|||
|
|||
## 生产环境 |
|||
* `process.env.PORT` 不需要 |
|||
* `process.env.NODE_ENV` 区分是生产环境 |
|||
|
|||
|
|||
### TODO |
|||
|
|||
- [ ] 完善终端的信息输出 |
Loading…
Reference in new issue