npmrun 4 years ago
commit
7f88caf415
  1. 14
      .editorconfig
  2. 2
      .env
  3. 5
      .gitignore
  4. 11
      .prettierrc
  5. 26
      main.js
  6. 7060
      package-lock.json
  7. 95
      package.json
  8. 70
      script/build-main.ts
  9. 158
      script/dev-runner.js
  10. 39
      script/rollup.config.ts
  11. 24
      script/utils.ts
  12. 4
      src/common/patch.ts
  13. 36
      src/main/index.ts
  14. 125
      src/preload/index.js
  15. 91
      src/preload/loading.html
  16. 60
      src/render/AppRouter.tsx
  17. BIN
      src/render/assets/images/0.jpg
  18. BIN
      src/render/assets/images/0.png
  19. 10
      src/render/assets/locales/en-us.json
  20. 10
      src/render/assets/locales/zh-cn.json
  21. 112
      src/render/assets/style/_font.scss
  22. 127
      src/render/assets/style/_reset.scss
  23. 28
      src/render/assets/style/common.scss
  24. 14
      src/render/assets/style/global.scss
  25. 11
      src/render/components/Header/index.tsx
  26. 120
      src/render/components/Loading/Bubbles.module.scss
  27. 18
      src/render/components/Loading/Bubbles.tsx
  28. 36
      src/render/components/Loading/Rainbow.module.scss
  29. 14
      src/render/components/Loading/Rainbow.tsx
  30. 13
      src/render/index.html
  31. 19
      src/render/main.tsx
  32. 28
      src/render/plugins/i18n/index.tsx
  33. 27
      src/render/plugins/pageHoc/index.tsx
  34. 4
      src/render/plugins/pageHoc/interface.ts
  35. 69
      src/render/route.tsx
  36. 1
      src/render/store/action/index.ts
  37. 17
      src/render/store/action/todo.ts
  38. 4
      src/render/store/constant/todo.ts
  39. 13
      src/render/store/index.ts
  40. 5
      src/render/store/reducer/index.ts
  41. 26
      src/render/store/reducer/todo.ts
  42. 52
      src/render/store/saga/index.ts
  43. 4
      src/render/store/types/todo.d.ts
  44. 37
      src/render/ui/Auth.tsx
  45. 21
      src/render/views/About/index.tsx
  46. 10
      src/render/views/Auth/Page404.tsx
  47. 86
      src/render/views/Home/Header.tsx
  48. 71
      src/render/views/Home/index.tsx
  49. 16
      src/render/views/Layout/index.tsx
  50. 9
      src/render/views/Login/Test.tsx
  51. 134
      src/render/views/Login/index.module.scss
  52. 53
      src/render/views/Login/index.tsx
  53. 1
      src/render/vite-env.d.ts
  54. 20
      test.js
  55. 35
      tsconfig.json
  56. 8
      types/global.d.ts
  57. 43
      vite.config.ts
  58. 15
      windi.config.ts

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

11
.prettierrc

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

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

7060
package-lock.json

File diff suppressed because it is too large

95
package.json

@ -0,0 +1,95 @@
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "description",
"main": "dist/electron/entry.js",
"scripts": {
"deva": "electron-forge start --inspect-electron --app-path dist/electron/entry.js",
"dev": "npm run dev:all",
"dev:all": "concurrently -n=vue,ele -c=green,blue \"npm run dev:vue\" \"npm run dev:ele\"",
"dev:vue": "vite",
"dev:ele": "node -r ts-node/register script/build-main --env=development --watch",
"dev:test": "electron-forge start --inspect-electron",
"package": "electron-forge package",
"make": "electron-forge make",
"build": "tsc && vite build",
"serve": "vite preview"
},
"keywords": [],
"author": "TopOne",
"license": "ISC",
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.58",
"@electron-forge/maker-deb": "^6.0.0-beta.58",
"@electron-forge/maker-rpm": "^6.0.0-beta.58",
"@electron-forge/maker-squirrel": "^6.0.0-beta.58",
"@electron-forge/maker-zip": "^6.0.0-beta.58",
"@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-typescript": "^8.2.3",
"@types/minimist": "^1.2.2",
"cfonts": "^2.9.3",
"chalk": "^4.1.1",
"concurrently": "^6.2.0",
"dotenv": "^10.0.0",
"electron": "^13.1.7",
"execa": "^5.1.1",
"ts-node": "^10.1.0",
"vitejs-plugin-electron": "^0.1.3"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.0",
"axios": "^0.21.1",
"classnames": "^2.3.1",
"i18next": "^20.3.4",
"i18next-browser-languagedetector": "^6.1.2",
"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-saga": "^1.1.3",
"styled-jsx": "^3.4.4",
"@types/node": "^15.12.5",
"@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",
"redux-devtools": "^3.7.0",
"sass": "^1.35.1",
"typescript": "^4.3.2",
"vite": "^2.3.8",
"vite-plugin-windicss": "^1.2.0",
"windicss": "^3.1.3"
},
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "my_electron_app"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
}
}

70
script/build-main.ts

@ -0,0 +1,70 @@
/**
* electron
*/
import { join } from "path"
import { exec, spawn, ChildProcess } from "child_process"
import { watch, rollup, OutputOptions } from "rollup"
import electron from "electron"
import { waitOn } from "./utils"
import options from "./rollup.config"
import { main } from "../package.json"
const ora = require("ora")
const dotenv = require("dotenv")
const chalk = require("chalk")
const minimist = require("minimist")
dotenv.config({ path: join(__dirname, "../.env") })
const argv = minimist(process.argv.slice(2))
const opts = options(argv.env)
const TAG = "[build-main.ts]"
const spinner = ora(`${TAG} Electron build...`)
if (argv.watch) {
waitOn({ port: process.env.PORT as string }).then(msg => {
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", ev => {
if (ev.code === "END") {
if (child && child.kill) {
console.log(child.pid)
manualRestart = true
// <number>child.pid
// child.kill()
process.kill( <number>child.pid)
child = null
setTimeout(() => {
manualRestart = false
}, 5000)
}
child = exec(`electron-forge start --inspect-electron --app-path ${join(__dirname, `../${main}`)}`, {
env: Object.assign(process.env, { NODE_ENV: argv.env }),
})
child.on("close", () => {
if (!manualRestart) process.exit()
})
} else if (ev.code === "ERROR") {
console.log(ev.error)
}
})
})
} else {
spinner.start()
rollup(opts)
.then(build => {
spinner.stop()
console.log(TAG, chalk.green("Electron build successed."))
build.write(opts.output as OutputOptions)
})
.catch(error => {
spinner.stop()
console.log(`\n${TAG} ${chalk.red("构建报错")}\n`, error, "\n")
})
}

158
script/dev-runner.js

@ -0,0 +1,158 @@
// @ts-nocheck
const electron = require("electron")
const execa = require("execa")
const vite = require("vite")
const path = require("path")
const fs = require("fs")
const os = require("os")
const esbuild = require("esbuild")
const { say } = require("cfonts")
const { spawn } = require("child_process")
const chalk = require("chalk")
function getEnvScript () {
let script = `process.env={...process.env};`
script += `process.env.RES_DIR = require("path").join(require("path").dirname(process.execPath),"resources/resource/release")`
return script
}
async function buildRender() {
const subprocess = execa("vite",['-c','vite.config.ts']).stdout.pipe(process.stdout);;
setTimeout(() => {
subprocess.killed();
}, 1000);
try {
await subprocess;
} catch (error) {
console.log(subprocess.killed); // true
console.log(error.isCanceled); // true
}
return
let options = {
root: path.join(__dirname, "../src/render"),
enableEsbuild: true,
minify: false,
resolve:{
alias: [
{ find: "@", replacement: path.resolve(__dirname, "src/render") },
{ find: "@render", replacement: path.resolve(__dirname, "src/render") },
{ find: "@main", replacement: path.resolve(__dirname, "src/main") },
{ find: "@src", replacement: path.resolve(__dirname, "src/src") },
{ find: "@root", replacement: __dirname },
],
},
optimizeDeps: {
exclude: ["process"],
},
}
await vite.build(options)
// let htmlPath = path.join(__dirname, "../dist/electron", "index.html")
// let html = fs.readFileSync(htmlPath, { encoding: "utf8" })
// html = html.replace("<head>", `<head><script>${getEnvScript()};</script>`)
// fs.writeFileSync(htmlPath, html)
}
function buildMain() {
let outfile = path.join(process.cwd(), "dist/electron/entry.js")
let entryFilePath = path.join(process.cwd(), "src/main/index.js")
esbuild.buildSync({
entryPoints: [entryFilePath],
outfile,
minify: false,
bundle: true,
platform: "node",
sourcemap: false,
external: ["electron"],
})
let js = `${getEnvScript()}${os.EOL}${fs.readFileSync(outfile)}`
fs.writeFileSync(outfile, js)
}
function startElectron() {
var args = ["--inspect=5858", path.join(__dirname, "../dist/electron/main.js")]
// detect yarn or npm and process commandline args accordingly
if (process.env.npm_execpath.endsWith("yarn.js")) {
args = args.concat(process.argv.slice(3))
} else if (process.env.npm_execpath.endsWith("npm-cli.js")) {
args = args.concat(process.argv.slice(2))
}
// electronProcess = spawn(electron, args)
// electronProcess.stdout.on("data", data => {
// electronLog(data, "blue")
// })
// electronProcess.stderr.on("data", data => {
// electronLog(data, "red")
// })
// electronProcess.on("close", () => {
// if (!manualRestart) process.exit()
// })
}
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 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 = ''
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()
// buildMain()
buildRender()
}
init()

39
script/rollup.config.ts

@ -0,0 +1,39 @@
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") => {
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
}

24
script/utils.ts

@ -0,0 +1,24 @@
import { builtinModules } from "module"
import { get } from "http"
import { green } from "chalk"
/** 轮询监听 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))

4
src/common/patch.ts

@ -0,0 +1,4 @@
/**
* !!! ensure process.cwd() correct
*/
process.chdir(__dirname.slice(0, __dirname.lastIndexOf('dist')))

36
src/main/index.ts

@ -0,0 +1,36 @@
/**
* electron
*/
console.log('2222');
import '@src/common/patch'
const {join} = require("path")
const {app,BrowserWindow} = require("electron")
const dotenv = require("dotenv")
dotenv.config({ path: join(__dirname, '../../../.env') })
let win
console.log('1232asd啊2aasdaaaa');
function createWin() {
console.log('萨达');
// 创建浏览器窗口
win = new BrowserWindow({
width: 1024,
height: 768,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: join(__dirname, '../../src/preload/index.js'),
},
})
const URL = app.isPackaged
? `file://${join(__dirname, '../render/index.html')}` // vite 构建后的静态文件地址
: `http://localhost:${process.env.PORT}/login` // vite 启动的服务器地址
win?.loadURL(URL)
}
app.whenReady().then(createWin)

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>

60
src/render/AppRouter.tsx

@ -0,0 +1,60 @@
import { BrowserRouter 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 () {
console.log(pageList)
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>
)

13
src/render/index.html

@ -0,0 +1,13 @@
<!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" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
</html>

19
src/render/main.tsx

@ -0,0 +1,19 @@
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 "virtual:windi-devtools";
import "virtual:windi.css";
import Router from "./AppRouter";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<Router></Router>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);

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; // 页面标题
}

69
src/render/route.tsx

@ -0,0 +1,69 @@
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: "/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

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

@ -0,0 +1,21 @@
import React from "react"
import { useLocation, Route, Switch } from "react-router-dom"
function Test() {
return <div>test</div>
}
export default function About(props: any) {
let location = useLocation()
console.log(location)
console.log(props)
return (
<div className="container mx-auto">
<div> asdasd</div>
<div>22{props.children}</div>
<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>
)
}

86
src/render/views/Home/Header.tsx

@ -0,0 +1,86 @@
import React, { useState } from "react"
export default () => {
const title = "TESAaT"
const [leftMenuList, setLeftMenuList] = useState<any[]>([
{ title: "首页", path: "/" },
{ title: "角色", children: [{ title: "月儿", path: "/about" }] },
])
const [rightMenuList, setRightMenuList] = useState<any[]>([
{
title: "登录/注册",
click: true,
},
])
function onLeftClick(e: any, menu: any, allMenu: any) {
if (menu.click || !menu.path) {
e.preventDefault()
}
}
function onRightClick(e: any, menu: any, allMenu: any) {
if (menu.click || !menu.path) {
e.preventDefault()
}
}
return (
<div className="">
<div className="shadow bg-white h-12 leading-12 fixed top-0 left-0 right-0">
<div className="container h-full clearfix mx-auto">
<div className="h-full float-left cursor-pointer text-size-25px flex items-center">{title}</div>
<ul className="h-full float-left ml-10">
{leftMenuList.map((menu, index) => {
return (
<li className="h-full float-left group relative" key={index}>
<a
href={menu.path ? menu.path : "#"}
onClick={e => onLeftClick(e, menu, leftMenuList)}
className="h-full px-5 hover:(bg-cool-gray-200 text-black) text-gray-400 text-size-14px flex items-center"
>
{menu.title}
</a>
{menu.children && menu.children.length && (
<ul className="absolute overflow-hidden transition-all duration-150 max-h-0 group-hover:max-h-500px left-0 top-full shadow">
{menu.children.map((subMenu: any, jndex: number) => {
return (
<li key={jndex} className="float-left relative">
<a
href={subMenu.path ? subMenu.path : "#"}
onClick={e => onLeftClick(e, subMenu, leftMenuList)}
className="h-12 px-5 hover:(bg-cool-gray-200 text-black) text-gray-400 text-size-14px flex items-center"
>
{subMenu.title}
</a>
</li>
)
})}
</ul>
)}
</li>
)
})}
</ul>
<ul className="float-right h-full">
{rightMenuList.map((menu, index) => {
return (
<li key={index} className="h-full float-left relative cursor-pointer">
<a
href={menu.path ? menu.path : "#"}
className="h-full px-5 text-size-14px flex items-center"
onClick={e => onRightClick(e, menu, rightMenuList)}
>
{menu.title}
</a>
</li>
)
})}
</ul>
</div>
</div>
<div className="h-12"></div>
</div>
)
}

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

@ -0,0 +1,71 @@
import { addTodo, removeTodo } from "@/store/action/todo"
import React, { FormEvent, useRef, useContext } from "react"
import { connect } from "react-redux"
import Header from "./Header"
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>
<Header></Header>
<div>
<div className="bg-white min-h-100vh">
<div className="container clearfix mx-auto h-500px">
<form onSubmit={e => addOne(e)}>
<input ref={inputRef} type="text" />
<button type="submit">222aaaasaadd</button>
</form>
{todo.map((v: ITodo) => {
return (
<p onClick={() => remove(v.id)} key={v.id}>
{v.text}
{v.id}
</p>
)
})}
</div>
</div>
<div className="min-h-100vh"></div>
<div className="bg-white min-h-100vh">
<div className="container clearfix mx-auto">
{[...Array(100)]
.map((v, i) => i)
.map(v => {
return <p key={v}>v</p>
})}
</div>
</div>
</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>
)
}

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

@ -0,0 +1,134 @@
@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;
::-webkit-input-placeholder {
/* WebKit browsers */
font-family: "Source Sans Pro", sans-serif;
color: white;
font-weight: 300;
}
:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
font-family: "Source Sans Pro", sans-serif;
color: white;
opacity: 1;
font-weight: 300;
}
::-moz-placeholder {
/* Mozilla Firefox 19+ */
font-family: "Source Sans Pro", sans-serif;
color: white;
opacity: 1;
font-weight: 300;
}
:-ms-input-placeholder {
/* Internet Explorer 10+ */
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" />

20
test.js

@ -0,0 +1,20 @@
// // http://localhost:3344
// get("http://baidu.com",res=>{
// console.log(res.statusCode);
// })
// import { spawn, ChildProcess } from 'child_process'
// spawn(
// electronForge,
// [
// "start",
// "--inspect-electron",
// "--app-path",
// join(__dirname, "dist/electron/entry.js"),//join(__dirname, `../${main}`),
// ] ,
// {
// stdio: 'inherit',
// env: Object.assign(process.env, { NODE_ENV: "development" }),
// })
process.kill(19656)

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": true,
"jsx": "react",
"rootDir": ".",
"baseUrl": ".",
"paths": {
"@/*": ["src/render/*"],
"@render/*": ["src/render/*"],
"@main/*": ["src/main/*"],
"@src/*": ["src/*"],
"@root/*": ["./*"]
},
},
"include": ["src/render", "types"],
"exclude": ["node_modules"],
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}

8
types/global.d.ts

@ -0,0 +1,8 @@
interface IAny{
[props: string]: any;
}
interface IAction extends IAny{
type: string
}

43
vite.config.ts

@ -0,0 +1,43 @@
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"
require('dotenv').config({ path: join(__dirname, '.env') })
// https://vitejs.dev/config/
export default defineConfig({
root: resolve(__dirname, "src/render"),
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: [WindiCSS(), reactRefresh(), electron()],
})

15
windi.config.ts

@ -0,0 +1,15 @@
import { defineConfig } from "vite-plugin-windicss";
function range(size, startAt = 1) {
return Array.from(Array(size).keys()).map((i) => i + startAt);
}
export default defineConfig({
preflight: false,
safelist: [
range(30).map((i) => `p-${i}`), // p-1 to p-3
range(10).map((i) => `mt-${i}`), // mt-1 to mt-10
],
extract: {
include: ['src/render/**/*.{vue,html,jsx,tsx}'],
exclude: ['node_modules', '.git'],
},
});
Loading…
Cancel
Save