npmrun 4 years ago
commit
2555b99a5d
  1. 14
      .babelrc
  2. 14
      .editorconfig
  3. 2
      .env
  4. 5
      .gitignore
  5. 11
      .prettierrc
  6. 15
      a.md
  7. 5
      dist/package-lock.json
  8. 12
      dist/package.json
  9. 26
      main.js
  10. 10776
      package-lock.json
  11. 140
      package.json
  12. 12
      resource/electron/about.html
  13. BIN
      resource/electron/static/icon.png
  14. 5
      script/build.js
  15. 36
      script/code/runCommon.ts
  16. 47
      script/code/runElectron.ts
  17. 54
      script/code/runVite.ts
  18. 82
      script/dev-runner.ts
  19. 58
      script/log.ts
  20. 41
      script/rollup.config.ts
  21. 19
      script/run.js
  22. 25
      script/utils.ts
  23. 146
      script/webpack/runMain.js
  24. 65
      script/webpack/webpack.main.config.js
  25. 7
      src/common/patch.ts
  26. 63
      src/main/disk.ts
  27. 20
      src/main/facilities/index.ts
  28. 119
      src/main/float.ts
  29. 18
      src/main/index.dev.ts
  30. 133
      src/main/index.ts
  31. 66
      src/main/menu copy.ts
  32. 189
      src/main/menu.ts
  33. 12
      src/main/share/index.ts
  34. 12
      src/main/util/index.ts
  35. 125
      src/preload/index.js
  36. 91
      src/preload/loading.html
  37. 58
      src/render/AppRouter.tsx
  38. BIN
      src/render/assets/images/0.jpg
  39. BIN
      src/render/assets/images/0.png
  40. 10
      src/render/assets/locales/en-us.json
  41. 10
      src/render/assets/locales/zh-cn.json
  42. 112
      src/render/assets/style/_font.scss
  43. 127
      src/render/assets/style/_reset.scss
  44. 28
      src/render/assets/style/common.scss
  45. 14
      src/render/assets/style/global.scss
  46. 11
      src/render/components/Header/index.tsx
  47. 120
      src/render/components/Loading/Bubbles.module.scss
  48. 18
      src/render/components/Loading/Bubbles.tsx
  49. 36
      src/render/components/Loading/Rainbow.module.scss
  50. 14
      src/render/components/Loading/Rainbow.tsx
  51. 16
      src/render/index.html
  52. 24
      src/render/main.tsx
  53. 3
      src/render/plugins/electron/index.ts
  54. 28
      src/render/plugins/i18n/index.tsx
  55. 27
      src/render/plugins/pageHoc/index.tsx
  56. 4
      src/render/plugins/pageHoc/interface.ts
  57. 78
      src/render/route.tsx
  58. 1
      src/render/store/action/index.ts
  59. 17
      src/render/store/action/todo.ts
  60. 4
      src/render/store/constant/todo.ts
  61. 13
      src/render/store/index.ts
  62. 5
      src/render/store/reducer/index.ts
  63. 26
      src/render/store/reducer/todo.ts
  64. 52
      src/render/store/saga/index.ts
  65. 4
      src/render/store/types/todo.d.ts
  66. 37
      src/render/ui/Auth.tsx
  67. 27
      src/render/views/About/index.tsx
  68. 10
      src/render/views/Auth/Page404.tsx
  69. 10
      src/render/views/Float/index.module.scss
  70. 43
      src/render/views/Float/index.tsx
  71. 49
      src/render/views/Home/index.tsx
  72. 16
      src/render/views/Layout/index.tsx
  73. 9
      src/render/views/Login/Test.tsx
  74. 107
      src/render/views/Login/index.module.scss
  75. 53
      src/render/views/Login/index.tsx
  76. 1
      src/render/vite-env.d.ts
  77. 18
      test.js
  78. 35
      tsconfig.json
  79. 12
      types/global.d.ts
  80. 65
      vite.config.ts
  81. 34
      说明.md

14
.babelrc

@ -0,0 +1,14 @@
{
"comments": false,
"env": {
"main": {
"presets": [
["@babel/preset-env", {
"targets": { "node": 7 }
}],
"@babel/preset-typescript"
]
}
},
"plugins": ["@babel/plugin-transform-runtime"]
}

14
.editorconfig

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

2
.env

@ -0,0 +1,2 @@
# vite 服务器端口
PORT=3344

5
.gitignore

@ -0,0 +1,5 @@
node_modules
out
.idea
.vscode
dist/electron

11
.prettierrc

@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": false,
"TrailingCooma": "all",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 140
}

15
a.md

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

5
dist/package-lock.json

@ -0,0 +1,5 @@
{
"name": "my-electron-app",
"version": "1.0.0",
"lockfileVersion": 1
}

12
dist/package.json

@ -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": {}
}

26
main.js

@ -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()
})

10776
package-lock.json

File diff suppressed because it is too large

140
package.json

@ -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"
}
}
}

12
resource/electron/about.html

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

BIN
resource/electron/static/icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

5
script/build.js

@ -0,0 +1,5 @@
// @ts-nocheck
const {buildMain} = require("./webpack/runMain")
buildMain()

36
script/code/runCommon.ts

@ -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")
}
}

47
script/code/runElectron.ts

@ -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()
}
})
}

54
script/code/runVite.ts

@ -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()
}
})
}

82
script/dev-runner.ts

@ -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)
// }
// })

58
script/log.ts

@ -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")
}

41
script/rollup.config.ts

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

19
script/run.js

@ -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"
])
}

25
script/utils.ts

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

146
script/webpack/runMain.js

@ -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()

65
script/webpack/webpack.main.config.js

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

7
src/common/patch.ts

@ -0,0 +1,7 @@
/**
* !!! ensure process.cwd() correct
*/
process.chdir(__dirname.slice(0, __dirname.lastIndexOf('dist')))
export {};

63
src/main/disk.ts

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

20
src/main/facilities/index.ts

@ -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 => {}
// )
})

119
src/main/float.ts

@ -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&param=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()
}
})

18
src/main/index.dev.ts

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

133
src/main/index.ts

@ -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()
}
})
}

66
src/main/menu copy.ts

@ -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
})

189
src/main/menu.ts

@ -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
})

12
src/main/share/index.ts

@ -0,0 +1,12 @@
interface IPayload{
data: {
[propName:string]: any;
};
[propName:string]: any;
}
const payload:IPayload = {
data: {}
}
export default payload

12
src/main/util/index.ts

@ -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 ""
}
}

125
src/preload/index.js

@ -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();
})();

91
src/preload/loading.html

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

58
src/render/AppRouter.tsx

@ -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>
)
}

BIN
src/render/assets/images/0.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
src/render/assets/images/0.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

10
src/render/assets/locales/en-us.json

@ -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"
}

10
src/render/assets/locales/zh-cn.json

@ -0,0 +1,10 @@
{
"login":{
"title": "青铜时代2022",
"username_placeholder": "用户名",
"password_placeholder": "密码",
"btnText": "登录"
},
"home":"青铜时代2021",
"welcome":"欢迎来首页"
}

112
src/render/assets/style/_font.scss

@ -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;
}

127
src/render/assets/style/_reset.scss

@ -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;
}

28
src/render/assets/style/common.scss

@ -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;
}

14
src/render/assets/style/global.scss

@ -0,0 +1,14 @@
$color: green;
@mixin clearfix {
&:after {
clear: both;
content: ".";
display: block;
height: 0;
line-height: 0;
overflow: hidden;
}
*height: 1%;
}

11
src/render/components/Header/index.tsx

@ -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>
);
}

120
src/render/components/Loading/Bubbles.module.scss

@ -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);
}
}

18
src/render/components/Loading/Bubbles.tsx

@ -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>
)
}

36
src/render/components/Loading/Rainbow.module.scss

@ -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);
}
}

14
src/render/components/Loading/Rainbow.tsx

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

16
src/render/index.html

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

24
src/render/main.tsx

@ -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")
);

3
src/render/plugins/electron/index.ts

@ -0,0 +1,3 @@
const electron = require("electron") // 只能用require
export default electron

28
src/render/plugins/i18n/index.tsx

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

27
src/render/plugins/pageHoc/index.tsx

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

4
src/render/plugins/pageHoc/interface.ts

@ -0,0 +1,4 @@
export interface IOption {
page: string; // 页面路径
title: string; // 页面标题
}

78
src/render/route.tsx

@ -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,
},
]

1
src/render/store/action/index.ts

@ -0,0 +1 @@
export * from "./todo"

17
src/render/store/action/todo.ts

@ -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
})

4
src/render/store/constant/todo.ts

@ -0,0 +1,4 @@
export const ADD = "ADD_TODO"
export const ADD_ASYNC = "ADD_ASYNC"
export const REMOVE = "REMOVE_TODO"

13
src/render/store/index.ts

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

5
src/render/store/reducer/index.ts

@ -0,0 +1,5 @@
import todo from "./todo"
export default {
todo
}

26
src/render/store/reducer/todo.ts

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

52
src/render/store/saga/index.ts

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

4
src/render/store/types/todo.d.ts

@ -0,0 +1,4 @@
interface ITodo {
id: number;
text: string;
}

37
src/render/ui/Auth.tsx

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

27
src/render/views/About/index.tsx

@ -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>
)
}

10
src/render/views/Auth/Page404.tsx

@ -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>
)
}

10
src/render/views/Float/index.module.scss

@ -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;
}

43
src/render/views/Float/index.tsx

@ -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>
)
}

49
src/render/views/Home/index.tsx

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

16
src/render/views/Layout/index.tsx

@ -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>
)
}

9
src/render/views/Login/Test.tsx

@ -0,0 +1,9 @@
import React from "react";
export default function(){
return (
<div>
saadsadsaddddddddddddddddd
</div>
)
}

107
src/render/views/Login/index.module.scss

@ -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);
}
}
}
}

53
src/render/views/Login/index.tsx

@ -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>
)
}

1
src/render/vite-env.d.ts

@ -0,0 +1 @@
/// <reference types="vite/client" />

18
test.js

@ -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")

35
tsconfig.json

@ -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"
}
}
}

12
types/global.d.ts

@ -0,0 +1,12 @@
interface IAny{
[props: string]: any;
}
interface IAction extends IAny{
type: string
}
declare const __static: string;
declare const __staticVar: string;

65
vite.config.ts

@ -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: "代码片段管理工具"
},
}),
],
})

34
说明.md

@ -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…
Cancel
Save