From 594b7ce573ce7ae6accac34aab8fb3ae90d9aa7c Mon Sep 17 00:00:00 2001 From: Wuruipeng Date: Fri, 12 Apr 2024 08:59:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat=EF=BC=9A=E4=B8=BB=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E5=92=8C=E6=B8=B2=E6=9F=93=E8=BF=9B=E7=A8=8B=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E3=80=81=E6=89=93=E5=8C=85=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .babelrc | 15 ++++ .gitignore | 3 +- LICENSE | 15 ++++ app/general/common.js | 39 ++++++++ app/general/ipcEvent.js | 15 ++++ app/main.js | 123 ++++++++++++++++++++++++++ app/preload.js | 29 ++++++ build/electron-builder.json | 45 ---------- node-folder.js => config-structure.js | 18 +++- index.html | 11 --- jest-preset.json | 6 ++ main.js | 15 ---- package.json | 109 +++++++++++++++++++++-- public/favicon.ico | Bin 0 -> 3870 bytes public/index.html | 19 ++++ public/logo192.png | Bin 0 -> 5347 bytes public/logo256.png | Bin 0 -> 8670 bytes public/manifest.json | 25 ++++++ public/robots.txt | 3 + server/index.js | 46 ++++++++++ server/package.json | 36 ++++++++ src/index.js | 11 +++ src/setupProxy.js | 13 +++ src/setupTests.js | 5 ++ 24 files changed, 517 insertions(+), 84 deletions(-) create mode 100644 .babelrc create mode 100644 LICENSE create mode 100644 app/general/common.js create mode 100644 app/general/ipcEvent.js create mode 100644 app/main.js create mode 100644 app/preload.js delete mode 100644 build/electron-builder.json rename node-folder.js => config-structure.js (82%) delete mode 100644 index.html create mode 100644 jest-preset.json delete mode 100644 main.js create mode 100644 public/favicon.ico create mode 100644 public/index.html create mode 100644 public/logo192.png create mode 100644 public/logo256.png create mode 100644 public/manifest.json create mode 100644 public/robots.txt create mode 100644 server/index.js create mode 100644 server/package.json create mode 100644 src/index.js create mode 100644 src/setupProxy.js create mode 100644 src/setupTests.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..b5195cc --- /dev/null +++ b/.babelrc @@ -0,0 +1,15 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "useBuiltIns": "usage", + "targets": { + "chrome": "49", + "ie": "11" + } + } + ], + "@babel/preset-react" + ] +} diff --git a/.gitignore b/.gitignore index bca833d..7f7cc51 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /coverage # production +/build /dist # misc @@ -24,4 +25,4 @@ yarn-error.log* node_modules .eslintcache package-lock.json -folder.txt +structure.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9dd2a5a --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2024, wuruipeng + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/app/general/common.js b/app/general/common.js new file mode 100644 index 0000000..4881978 --- /dev/null +++ b/app/general/common.js @@ -0,0 +1,39 @@ +/* + * @Author: wuruipeng + * @Date: 2024-04-08 09:57:24 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-10 17:01:17 + * @description: 全局公共方法 + */ +const { app, dialog } = require('electron') + +// 退出程序确定方法 +let willQuitApp = false // 前置判断条件,是否确认退出程序 +const confirmExit = event => { + if (!willQuitApp) { + event.preventDefault() // 取消默认行为 + dialog + .showMessageBox({ + type: 'warning', + title: 'Tips', + message: 'Are you sure quit this App ?', + buttons: ['OK', 'Cancel'] + }) + .then(result => { + const response = result.response + if (response === 0) { + willQuitApp = true + app.quit() + } else { + willQuitApp = false + } + }) + .catch(() => { + willQuitApp = false + }) + } +} + +module.exports = { + confirmExit +} diff --git a/app/general/ipcEvent.js b/app/general/ipcEvent.js new file mode 100644 index 0000000..a2a88e2 --- /dev/null +++ b/app/general/ipcEvent.js @@ -0,0 +1,15 @@ +/* + * @Author: wuruipeng + * @Date: 2024-04-08 09:57:33 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-08 10:02:41 + * @description: 监听渲染进程交互主进程方法 + */ + +const { app, ipcMain } = require('electron') + +// 关闭应用方法 +ipcMain.on('close-app', () => { + // 调用 app.quit() 方法来关闭应用程序 + app.quit() +}) diff --git a/app/main.js b/app/main.js new file mode 100644 index 0000000..6abb4b6 --- /dev/null +++ b/app/main.js @@ -0,0 +1,123 @@ +/* + * @Author: wuruipeng + * @Date: 2024-03-06 17:39:06 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-11 14:47:50 + * @description: 主进程配置 + */ + +const { app, screen, BrowserWindow, Menu, dialog } = require('electron') +const path = require('path') +const { confirmExit } = require('./general/common.js') +require('./general/ipcEvent.js') // 配置监听ipc信息事件 + +// 初始化主进程 +function initApp(rootPath) { + // 菜单模板 + const template = [ + { + label: 'Setting', + submenu: [ + { label: 'Change Serve/Local', accelerator: 'CmdOrCtrl+S' }, + { label: 'Toggle DevTools', role: 'toggleDevTools' }, + { type: 'separator' }, + { label: 'Quit', role: 'quit' } + ] + }, + { + label: 'Window', + submenu: [ + { label: 'Reload', role: 'reload' }, + { label: 'Zoom In', role: 'zoomIn' }, + { label: 'Zoom Out', role: 'zoomOut' }, + { label: 'Toggle Fullscreen', role: 'togglefullscreen' } + ] + }, + { + label: 'Help', + submenu: [{ label: `About ${app.name}`, role: 'about' }] + } + ] + // 基础窗口信息配置 + const createWindow = function () { + const { width, height } = screen.getPrimaryDisplay().workAreaSize // 自适应默认尺寸 + const mainWindow = new BrowserWindow({ + width, + height, + x: 0, + y: 0, + webPreferences: { + devTools: true, // 打开开发者工具 + nodeIntegration: true, // 使用nodejs整合模块 + contextIsolation: true, // 上下文隔离 + preload: path.join(__dirname, 'preload.js') // 设定html内预加载script文件 + } + }) + // 切换应用功能方法 + const changePage = (type = null) => { + switch (type) { + case 'web': + mainWindow.loadURL('http://localhost:3000') // 在线功能加载 React 服务的 URL + // 需要启动服务的项目,同时监听加载失败事件 + mainWindow.webContents.on( + 'did-fail-load', + (event, errorCode, errorDescription) => { + // 如果errorCode为-105(net::ERR_NAME_NOT_RESOLVED),则表示服务不可用 + if (errorCode === -105) { + dialog.showErrorBox('Can not use this server') + } else { + dialog.showErrorBox(`Load Failed:${errorDescription}`) + } + } + ) + break + default: + mainWindow.loadFile( + path.join(__dirname, '..', rootPath, 'index.html') + ) // 使用相对路径加载 HTML 文件 + break + } + } + // 自定义应用切换事件 + template[0].submenu[0].submenu = [ + { + label: 'Online Web', + click: () => { + changePage('web') + } + }, + { + label: 'Local Page', + click: () => { + changePage() + } + } + ] + // 定义并执行菜单配置 + const menu = Menu.buildFromTemplate(template) + Menu.setApplicationMenu(menu) + changePage('web') + // 窗口关闭前进行确认验证 + mainWindow.on('close', event => { + confirmExit(event) + }) + } + // 应用加载完成配置 + app.on('ready', createWindow) + app.on('activate', () => { + createWindow() + }) + // 退出程序提示 + app.on('before-quit', event => { + confirmExit(event) + }) +} + +try { + // 开发模式 配置electron主进程热更新,可支持css,js,html热更 + require('electron-reloader')(module, {}) + initApp('public') +} catch (_) { + // 生产模式 electron-reloader依赖在打包后运行引用会造成读取不到的错误,从而定位到catch事件中,在catch中应去掉对该依赖的加载 + initApp('build') +} diff --git a/app/preload.js b/app/preload.js new file mode 100644 index 0000000..8da3fdf --- /dev/null +++ b/app/preload.js @@ -0,0 +1,29 @@ +/* + * @Author: wuruipeng + * @Date: 2024-04-08 09:55:51 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-08 16:06:34 + * @description: 渲染进程预加载脚本(用于搭建传递主进程与渲染进程的属性使用和方法) + */ + +const { contextBridge, ipcRenderer } = require('electron') +const common = require('./general/common.js') + +// 可发送通信消息至主进程控制调用 +contextBridge.exposeInMainWorld('ipcRenderer', ipcRenderer) +// 将需要暴露给渲染进程的 API 注册到一个受信任的预定义对象中(可返回更新值至渲染进程) +contextBridge.exposeInMainWorld( + 'resultAPI', // 对象名称 + { + // 定义一个 invoke 方法,用于从渲染进程向主进程发送消息,并等待主进程返回结果 + invoke: (channel, data) => { + // 在这里使用 ipcRenderer 发送消息给主进程,并等待主进程返回结果 + return ipcRenderer.invoke(channel, data) + }, + // 监听主进程发送的消息 + on: (channel, listener) => { + ipcRenderer.on(channel, listener) + } + } +) +contextBridge.exposeInMainWorld('common', common) diff --git a/build/electron-builder.json b/build/electron-builder.json deleted file mode 100644 index 3f9fab8..0000000 --- a/build/electron-builder.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "appId": "com.example.myapp", - "productName": "ElectronProject", - "copyright": "Copyright © 2020-2024 WRP", - "directories": { - "output": "dist" - }, - "files": [ - { - "from": "build/", - "to": "build/" - }, - "main.js", - "index.html", - "package.json" - ], - "win": { - "target": ["nsis"] - }, - "mac": { - "target": ["dmg"] - }, - "linux": { - "target": ["AppImage"] - }, - "nsis": { - "oneClick": false, - "perMachine": true, - "allowElevation": true, - "allowToChangeInstallationDirectory": true, - "contents": [ - { - "x": 130, - "y": 220, - "type": "link", - "path": "/Applications" - }, - { - "x": 410, - "y": 220, - "type": "file" - } - ] - } -} diff --git a/node-folder.js b/config-structure.js similarity index 82% rename from node-folder.js rename to config-structure.js index 54c653e..5b61072 100644 --- a/node-folder.js +++ b/config-structure.js @@ -1,3 +1,10 @@ +/* + * @Author: wuruipeng + * @Date: 2024-04-03 10:32:46 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-08 11:13:13 + * @description:项目目录结构生成配置 + */ const fs = require('fs') const path = require('path') @@ -56,13 +63,18 @@ function generateDirectoryText( const folderPath = './' // 指定要排除的文件夹列表 -const excludedFolders = ['.git', 'node_modules', '.eslintcache', 'folder.txt'] +const excludedFolders = [ + '.git', + 'node_modules', + '.eslintcache', + 'structure.txt' +] const directoryText = generateDirectoryText(folderPath, '', excludedFolders) // 指定要保存目录文本的文件路径 -const outputPath = 'folder.txt' +const outputPath = 'structure.txt' fs.writeFileSync(outputPath, directoryText) -console.log(`目录信息已保存到 ${outputPath} 文件中。`) +console.log(`Structure information has saved to ${outputPath}`) diff --git a/index.html b/index.html deleted file mode 100644 index 3d65494..0000000 --- a/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - Electron - - - Electron - - diff --git a/jest-preset.json b/jest-preset.json new file mode 100644 index 0000000..afddcf4 --- /dev/null +++ b/jest-preset.json @@ -0,0 +1,6 @@ +{ + "preset": "react", + "transformIgnorePatterns": [ + "node_modules/(?!(@history/index|@api.*|@utils/index|@style.*)/)" + ] +} diff --git a/main.js b/main.js deleted file mode 100644 index 76074d5..0000000 --- a/main.js +++ /dev/null @@ -1,15 +0,0 @@ -const { app, BrowserWindow } = require('electron') - -const createWindow = () => { - const win = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - win.loadFile('index.html') -} - -app.whenReady().then(createWindow) diff --git a/package.json b/package.json index dbfef70..2dfee39 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,114 @@ { - "name": "electron-project", + "author": "WuRuipeng", + "license": "ISC", + "name": "mtlh-application", "version": "1.0.0", "description": "electron-project", - "main": "main.js", + "main": "app/main.js", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.5", + "fs-extra": "^11.1.1", + "http-proxy-middleware": "^2.0.6", + "lodash": "^4.17.21", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "^9.0.4", + "react-router-dom": "^6.21.1", + "react-scripts": "5.0.1", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0" + }, "scripts": { - "start": "electron .", - "pack": "electron-builder", - "structure": "node node-folder.js", + "app": "set NODE_ENV=development && electron .", + "serve": "set BROWSER=none && react-scripts start --no-open", + "serve:build": "react-scripts build", + "server": "serve build", + "pack": "set NODE_ENV=production && electron-builder", + "pack:all": "npm run serve:build && npm run pack && serve build", + "jest": "jest", + "structure": "node config-structure.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "https://gitee.com/wuruipeng_admin/my-application-development.git" }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, "devDependencies": { + "@babel/core": "^7.22.9", + "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@babel/preset-env": "^7.23.8", + "@babel/preset-react": "^7.23.3", + "css-loader": "^3.6.0", "electron": "^25.5.0", - "electron-builder": "^24.6.3" + "electron-builder": "^24.6.3", + "electron-reloader": "^1.2.3", + "jest": "26.6.0" + }, + "build": { + "asar": false, + "appId": "com.example.mtlh", + "productName": "MTLH", + "copyright": "Copyright © 2020-2024 WRP", + "icon": "build/logo256.png", + "directories": { + "output": "dist" + }, + "extraResources": [ + { + "from": "server/", + "to": "server/", + "filter": [ + "**/*", + "!**/node_modules/**", + "!package-lock/*.json" + ] + } + ], + "files": [ + "app/**/*", + "build/**/*", + "package.json" + ], + "win": { + "target": [ + "nsis" + ] + }, + "mac": { + "target": [ + "dmg" + ] + }, + "linux": { + "target": [ + "AppImage" + ] + }, + "nsis": { + "oneClick": false, + "perMachine": true, + "allowElevation": true, + "allowToChangeInstallationDirectory": true + }, + "extends": null }, - "keywords": [], - "author": "", - "license": "ISC" + "engines": { + "node": ">=16" + } } diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a GIT binary patch literal 3870 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 baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ literal 0 HcmV?d00001 diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..cb18895 --- /dev/null +++ b/public/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + + Application + + + +
+ diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 GIT binary patch literal 5347 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 X@eDJUQo;Ye2mwlRsI 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 literal 0 HcmV?d00001 diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..ed99a00 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo256.png", + "type": "image/png", + "sizes": "256x256" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..3962804 --- /dev/null +++ b/server/index.js @@ -0,0 +1,46 @@ +/* + * @Author: wuruipeng + * @Date: 2024-04-08 10:47:57 + * @LastEditors: wuruipeng + * @LastEditTime: 2024-04-11 11:36:29 + * @description:本地koa服务配置入口文件 + */ + +const Koa = require('koa') +const app = new Koa() +// 引入post请求中间件 +const bodyparser = require('koa-bodyparser') +// 引入解决跨域组件 +const cors = require('koa2-cors') +// 引入表单校验模块 +const koaBouncer = require('koa-bouncer') +// 引入session模块 +const session = require('koa-session') + +// 使用post请求中间件 +app.use(bodyparser()) +// 使用解决跨域组件 +app.use(cors()) +// 使用表单校验模块 +app.use(koaBouncer.middleware()) +// 设置session签名,防止客户端修改(加密) +app.keys = ['session secret', 'anthor secret'] +// 设置session配置项 +const SESSION_CONFIG = { + key: 'sid', // 设置cookie的key名 + maxAge: 86400000, // 有效期,默认一天 + httpOnly: true, // 仅服务端修改 + signed: true // 签名cookie +} +// 使用session模块注册配置 +app.use(session(SESSION_CONFIG, app)) + +// 中间件配置 +app.use(async ctx => { + // 获取访问次数 + let n = ctx.session.count || 0 + ctx.session.count = ++n // 存储对应session的信息 + ctx.body += `第${n}次访问该网站` +}) + +app.listen('3001') diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..6669ed7 --- /dev/null +++ b/server/package.json @@ -0,0 +1,36 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "Local NodeJs Server", + "main": "index.js", + "scripts": { + "serve": "node index.js", + "serve:nodemon": "nodemon index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "koa", + "mongodb" + ], + "author": "WuRuipeng", + "license": "ISC", + "dependencies": { + "@koa/router": "^10.1.1", + "jsonwebtoken": "^8.5.1", + "koa": "^2.13.4", + "koa-bodyparser": "^4.3.0", + "koa-bouncer": "^6.0.0", + "koa-jwt": "^4.0.3", + "koa-logger": "^3.2.1", + "koa-multer": "^1.0.2", + "koa-onerror": "^4.2.0", + "koa-session": "^6.2.0", + "koa-static": "^5.0.0", + "koa2-cors": "^2.0.6", + "mongodb": "^4.4.0", + "mongoose": "^6.2.4", + "nodemon": "^3.1.0", + "sequelize": "^6.17.0", + "trek-captcha": "^0.4.0" + } +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..5f18e10 --- /dev/null +++ b/src/index.js @@ -0,0 +1,11 @@ +import React from 'react' +import { createRoot } from 'react-dom/client' +const root = createRoot(document.getElementById('root')) + +root.render( + +
+ 主页面 +
+
+) diff --git a/src/setupProxy.js b/src/setupProxy.js new file mode 100644 index 0000000..354917d --- /dev/null +++ b/src/setupProxy.js @@ -0,0 +1,13 @@ +const { createProxyMiddleware } = require('http-proxy-middleware') + +module.exports = function (app) { + app.use( + createProxyMiddleware('/api', { + target: 'http://localhost:3001', + changeOrigin: true, + pathRewrite: { + '^/api': '' + } + }) + ) +} diff --git a/src/setupTests.js b/src/setupTests.js new file mode 100644 index 0000000..8f2609b --- /dev/null +++ b/src/setupTests.js @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; -- Gitee From 41ffa64a8ac354a01d4696d7738aa8b6094a15fa Mon Sep 17 00:00:00 2001 From: Wuruipeng Date: Fri, 19 Apr 2024 16:26:54 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat=EF=BC=9Ats=E3=80=81less=E7=9A=84?= =?UTF-8?q?=E6=8E=A5=E5=85=A5=EF=BC=8Cwebpack=E9=87=8D=E6=9E=84=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=89=93=E5=8C=85=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 27 ++++++++++---- src/index.js | 11 ------ src/index.tsx | 15 ++++++++ src/setupProxy.js | 13 ------- src/styles/index.less | 51 +++++++++++++++++++++++++ tsconfig.json | 36 ++++++++++++++++++ webpack.config.js | 86 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 207 insertions(+), 32 deletions(-) delete mode 100644 src/index.js create mode 100644 src/index.tsx delete mode 100644 src/setupProxy.js create mode 100644 src/styles/index.less create mode 100644 tsconfig.json create mode 100644 webpack.config.js diff --git a/package.json b/package.json index 2dfee39..c82ec47 100644 --- a/package.json +++ b/package.json @@ -12,22 +12,20 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.5", "fs-extra": "^11.1.1", - "http-proxy-middleware": "^2.0.6", "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^9.0.4", "react-router-dom": "^6.21.1", - "react-scripts": "5.0.1", "redux": "^5.0.1", "redux-thunk": "^3.1.0" }, "scripts": { - "app": "set NODE_ENV=development && electron .", - "serve": "set BROWSER=none && react-scripts start --no-open", - "serve:build": "react-scripts build", - "server": "serve build", - "pack": "set NODE_ENV=production && electron-builder", + "app": "electron .", + "serve": "webpack serve --mode development", + "build": "webpack --mode development", + "serve:build": "serve build", + "pack": "electron-builder", "pack:all": "npm run serve:build && npm run pack && serve build", "jest": "jest", "structure": "node config-structure.js", @@ -54,11 +52,24 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/preset-env": "^7.23.8", "@babel/preset-react": "^7.23.3", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "babel-loader": "^9.1.3", + "copy-webpack-plugin": "^12.0.2", "css-loader": "^3.6.0", "electron": "^25.5.0", "electron-builder": "^24.6.3", "electron-reloader": "^1.2.3", - "jest": "26.6.0" + "html-webpack-plugin": "^5.6.0", + "jest": "26.6.0", + "less": "^4.2.0", + "less-loader": "^12.2.0", + "style-loader": "^4.0.0", + "ts-loader": "^9.5.1", + "typescript": "^5.4.5", + "webpack": "^5.91.0", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" }, "build": { "asar": false, diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 5f18e10..0000000 --- a/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import { createRoot } from 'react-dom/client' -const root = createRoot(document.getElementById('root')) - -root.render( - -
- 主页面 -
-
-) diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..21d0124 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import '@/styles/index.less' +import { Root, createRoot } from 'react-dom/client' + +const rootElement = document.getElementById('root') +if (rootElement) { + const root: Root = createRoot(rootElement) + root.render( + + Main Theme + + ) +} else { + console.error('Cannot find the rootElement!') +} diff --git a/src/setupProxy.js b/src/setupProxy.js deleted file mode 100644 index 354917d..0000000 --- a/src/setupProxy.js +++ /dev/null @@ -1,13 +0,0 @@ -const { createProxyMiddleware } = require('http-proxy-middleware') - -module.exports = function (app) { - app.use( - createProxyMiddleware('/api', { - target: 'http://localhost:3001', - changeOrigin: true, - pathRewrite: { - '^/api': '' - } - }) - ) -} diff --git a/src/styles/index.less b/src/styles/index.less new file mode 100644 index 0000000..bf6d0e8 --- /dev/null +++ b/src/styles/index.less @@ -0,0 +1,51 @@ +/* Typography */ +html, +body, +#root { + width: 100%; + height: 100%; + padding: 0; + margin: 0; + font: inherit; + font-family: Arial, sans-serif; +} + +/* Box sizing */ +*, +*:before, +*:after { + box-sizing: border-box; +} + +/* Links */ +a { + color: #007bff; + text-decoration: none; + transition: color 0.3s ease; +} + +a:hover { + color: #0056b3; +} + +/* Lists */ +ul, +ol { + margin: 0; + padding: 0; +} + +/* Form elements */ +input, +button, +textarea, +select { + font-family: inherit; + font-size: inherit; +} + +/* Images */ +img { + max-width: 100%; + height: auto; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..f0ccea1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "esnext", + "lib": ["dom", "dom.iterable", "es2015", "es2016", "es2017", "esnext"], + "strict": true, + "declaration": true, + "sourceMap": true, + "noEmit": false, + "outDir": "./build", + "rootDir": "./src", + "baseUrl": "./src", + "importHelpers": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strictNullChecks": true, + "preserveConstEnums": true, + "resolveJsonModule": true, + "typeRoots": ["./node_modules/@types"], + "allowJs": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "isolatedModules": true, + "jsx": "react" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["app", "node_modules", "build", "dist", "public", "server"], + "paths": { + "@/*": ["src/*"] + } +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..fadbdb6 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,86 @@ +const path = require('path') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin') + +module.exports = { + mode: 'development', + entry: './src/index.tsx', + devServer: { + static: { + directory: path.join(__dirname, 'public') + }, + compress: true, + port: 3000, + open: false, + allowedHosts: 'auto', + client: { + logging: 'info', + overlay: true, + progress: true + }, + proxy: [ + { + context: ['/api'], + target: 'http://localhost:3001', + secure: false, + changeOrigin: true, + pathRewrite: { '^/api': '' } + } + ] + }, + resolve: { + alias: { + '@': path.resolve(__dirname, 'src/') + }, + extensions: ['.tsx', '.ts', '.jsx', '.js'] + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader' + } + }, + { + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: 'ts-loader', + options: { + transpileOnly: false + } + } + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.less$/, + use: ['style-loader', 'css-loader', 'less-loader'] + } + ] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './public/index.html' + }), + new CopyWebpackPlugin({ + patterns: [ + { + from: 'public', + to: '', + globOptions: { + ignore: ['**/index.html'] + } + } + ] + }) + ], + output: { + path: path.resolve(__dirname, 'build'), + filename: 'bundle.js' + } +} -- Gitee