Compare commits

...

8 Commits

Author SHA1 Message Date
3657364146 feat: 优化模板 2024-04-09 18:06:18 +08:00
ae3c4abac2 fix: 修改模板 2024-04-09 17:34:58 +08:00
4cf5a6d2f6 fix: 重构模板 2024-04-09 11:23:19 +08:00
f146d3dca7 fix: zhst版本 2024-04-09 09:42:50 +08:00
79128529bf fix: 注入webview功能 2024-04-09 09:36:15 +08:00
b65cb471e3 fix: 修改上传方案 2024-04-06 23:37:12 +08:00
ef444ad9e8 fix: 修改配置 2024-04-03 18:06:42 +08:00
d8342fe580 feat: 新增electron模板 2024-04-02 19:23:59 +08:00
32 changed files with 13263 additions and 36 deletions

16
.eslintrc.json Normal file
View File

@ -0,0 +1,16 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/recommended",
"plugin:import/electron",
"plugin:import/typescript"
],
"parser": "@typescript-eslint/parser"
}

14
.gitignore vendored
View File

@ -1,3 +1,13 @@
/node_modules
/.next
src/.umi
/.env.local
/.umirc.local.ts
/config/config.local.ts
/src/.umi
/src/.umi-production
/src/.umi-test
/dist
.swc
.electron
.electron-production
yarn-error.log
pnpm-lock.yaml

4
.npmrc Normal file
View File

@ -0,0 +1,4 @@
registry=https://registry.npmmirror.com
strict-peer-dependencies=false
electron-mirror=https://registry.npmmirror.com/-/binary/electron/
electron-builder-binaries-mirror=https://registry.npmmirror.com/binary.html?path=electron-builder-binaries/

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
.umi
.umi-production

8
.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }],
"plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"]
}

30
.umirc.ts Normal file
View File

@ -0,0 +1,30 @@
import { defineConfig } from "umi";
import { Platform, createTargets } from "@umijs/plugin-electron";
// example: mac & windows
const targets = createTargets([Platform.WINDOWS]);
// example: mac m1
// const targets = Platform.MAC.createTarget(['dmg'], Arch.arm64);
export default defineConfig({
npmClient: "pnpm",
plugins: ["@umijs/plugin-electron", "@umijs/plugins/dist/dva"],
// metas: [
// {
// 'http-equiv': 'Content-Security-Policy',
// content: "default-src 'none'"
// }
// ],
electron: {
builder: {
targets,
config: {}
},
extraDevFiles: {}
},
define: {
APP_ENV: "dev"
},
dva: {},
});

21
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Electron Main",
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.sh",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.cmd"
},
// runtimeArgs will be passed directly to your Electron application
"runtimeArgs": [
"foo",
"bar"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal"
}
]
}

View File

@ -1,34 +1 @@
## 目前已有脚手架列表
| 名称 | 说明 | 技术栈 |
| ------- | --------------------------- | --------------------- |
| nextJs | 基于 React 的服务端渲染方案 | nextJs + axios + antd |
| ReactJs | 基于 React 的业务型脚手架 | umiJs + axios + antd |
| TaroJs | 基于 React 的多端适配方案 | TaroJs 全家桶 |
| Vue | 基于 Vue 的业务型脚手架 | vueJs 全家桶 |
| Gulp | 基于 Gulp 的清凉型脚手架 | -- |
## 快速上手
### 1. 安装相关依赖
```js
// 推荐
yarn global add @nicecode/cli
// or
// npm install @nicecode/cli -g
```
### 2. 运行命令
```js
// 查看脚手架版本号,是否安装成功
nice - V
```
### 3. 创建项目
```js
nice create ${项目名称}
```
notice: run `pnpm install clear-module lodash chokidar` while using pnpm.

9
global.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
interface IBaseAPI {
uploadFile: (path: File[]) => void
setTitle: (value: string) => void
onUploadProgress: (progress: unknown) => void
}
interface Window {
baseAPI: IBaseAPI
}

2
jest-setup.ts Normal file
View File

@ -0,0 +1,2 @@
import '@testing-library/jest-dom';
import 'umi/test-setup'

27
jest.config.ts Normal file
View File

@ -0,0 +1,27 @@
import { Config, configUmiAlias, createConfig } from 'umi/test';
export default async () => {
try{
return (await configUmiAlias({
...createConfig({
target: 'browser',
jsTransformer: 'esbuild',
// config opts for esbuild , it will pass to esbuild directly
jsTransformerOpts: { jsx: 'automatic' },
}),
setupFilesAfterEnv: ['<rootDir>/jest-setup.ts'],
collectCoverageFrom: [
'src/**/*.{ts,js,tsx,jsx}',
'!src/.umi/**',
'!src/.umi-test/**',
'!src/.umi-production/**'
],
// if you require some es-module npm package, please uncomment below line and insert your package name
// transformIgnorePatterns: ['node_modules/(?!.*(lodash-es|your-es-pkg-name)/)']
})) as Config.InitialOptions;
} catch (e) {
console.log(e);
throw e;
}
};

62
package.json Normal file
View File

@ -0,0 +1,62 @@
{
"name": "electron-template",
"version": "1.0.2",
"author": {
"name": "dev"
},
"description": "eggKnife-electron的客户端模板",
"scripts": {
"dev": "cross-env umi dev",
"build": "cross-env NODE_ENV=production umi build",
"postinstall": "cross-env umi setup",
"setup": "cross-env umi setup",
"start": "cross-env npm run dev",
"test": "cross-env TS_NODE_TRANSPILE_ONLY=yes jest --passWithNoTests"
},
"build": {
"productName": "测试",
"appId": "nicecode"
},
"dependencies": {
"antd": "^5.16.1",
"chokidar": "^3.6.0",
"clear-module": "^4.1.2",
"lodash": "^4.17.21",
"normalize.css": "^8.0.1",
"umi": "^4.0.42"
},
"devDependencies": {
"@testing-library/jest-dom": "^5",
"@testing-library/react": "^14",
"@tsconfig/node21": "^21.0.3",
"@types/jest": "^29",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/testing-library__jest-dom": "^5.14.5",
"@umijs/plugin-electron": "^0.2.0",
"@umijs/plugins": "^4.1.8",
"cross-env": "^7",
"electron": "29.1.6",
"jest": "^29",
"jest-environment-jsdom": "^29",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.2",
"prettier-plugin-packagejson": "^2.4.3",
"ts-node": "^10",
"typescript": "^5"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"files": [
"src/**/*",
"tsconfig.json",
".npmrc",
".gitignore",
"README.md",
".umirc.ts",
"package.json",
"typings.d.ts"
]
}

12647
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

1
src/app.ts Normal file
View File

@ -0,0 +1 @@
import 'normalize.css'

BIN
src/assets/avatar.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

5
src/global.scss Normal file
View File

@ -0,0 +1,5 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
Arial, sans-serif;
margin: auto;
}

10
src/layouts/index.less Normal file
View File

@ -0,0 +1,10 @@
.navs {
ul {
padding: 0;
list-style: none;
display: flex;
}
li {
margin-right: 1em;
}
}

61
src/layouts/index.tsx Normal file
View File

@ -0,0 +1,61 @@
import { Outlet, history } from 'umi';
import { Layout, Menu, theme, Image, Affix } from 'antd'
import avatar from '../assets/avatar.jpg';
import styles from './index.less';
const { Header, Content, Footer } = Layout;
const items = [
{
key: '/',
label: '首页'
},
{
key: '/docs',
label: '上传页'
}
]
export default function BaseLayout() {
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<div className={styles.navs}>
<Layout>
<Affix>
<Header style={{ height: '32px', display: 'flex', alignItems: 'center' }}>
<div style={{ marginRight: '12px', height: '32px' }}>
<Image style={{ fontSize: 0 }} src={avatar} alt="nicecode" width={'24px'} preview={false} />
</div>
<Menu
style={{ height: '32px', lineHeight: '32px' }}
theme="dark"
mode="horizontal"
defaultSelectedKeys={['2']}
items={items}
onClick={item => history.push(item.key)}
/>
</Header>
</Affix>
<Content style={{ padding: '0 24px' }}>
<div
style={{
background: colorBgContainer,
minHeight: 280,
padding: 24,
borderRadius: borderRadiusLG,
}}
>
<Outlet />
</div>
</Content>
<Footer style={{ textAlign: 'center' }}>
nicecode ©{new Date().getFullYear()} Created by dev
</Footer>
</Layout>
</div>
);
}

17
src/main/config.ts Normal file
View File

@ -0,0 +1,17 @@
import { BrowserWindowConstructorOptions } from 'electron';
export default {
browserWindow: {
title: 'nicecode 客户端模板',
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#2f3241',
symbolColor: '#74b1be',
height: 32
},
maximizable: false,
webPreferences: {
enableRemoteModule: true,
}
} as BrowserWindowConstructorOptions
};

1
src/main/forks/init.ts Normal file
View File

@ -0,0 +1 @@
console.log('123', 123)

114
src/main/index.ts Normal file
View File

@ -0,0 +1,114 @@
import { app, Menu } from 'electron';
if (process.env.NODE_ENV === 'development') {
getBrowserWindowRuntime().webContents.openDevTools();
}
const isMac = process.platform === 'darwin'
const template = [
// { role: 'appMenu' }
...(isMac
? [{
label: app.name,
submenu: [
{ label: '关于', role: 'about' },
{ type: 'separator' },
{ role: 'services' },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
}]
: []),
// { role: 'fileMenu' }
{
label: '文件',
submenu: [
isMac ? { label: '退出', role: 'close' } : { label: '退出', role: 'quit' }
]
},
// { role: 'editMenu' }
{
label: '编辑',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
...(isMac
? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
]
: [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
])
]
},
// { role: 'viewMenu' }
{
label: '视图',
submenu: [
{ label: '重新加载', role: 'reload' },
{ label: '强制重新加载', role: 'forceReload' },
{ type: 'separator' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
// { role: 'windowMenu' }
{
label: '窗口',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac
? [
{ type: 'separator' },
{ role: 'front' },
{ type: 'separator' },
{ role: 'window' }
]
: [
{ role: 'close' }
])
]
},
{
role: 'help',
submenu: [
{ label: '控制台', role: 'toggleDevTools' },
{
label: '更多',
click: async () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { shell } = require('electron')
await shell.openExternal('https://electronjs.org')
}
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

9
src/main/ipc/platform.ts Normal file
View File

@ -0,0 +1,9 @@
import { ipcMain, app } from 'electron';
ipcMain.handle('getPlatform', () => {
return `hi, i'm from ${process.platform}`;
});
// node: () => process.versions.node,
// chrome: () => process.versions.chrome,
// electron: () => process.versions.electron,

67
src/main/ipc/upload.ts Normal file
View File

@ -0,0 +1,67 @@
import { app, ipcMain } from 'electron';
// import fs from 'fs';
// import path from 'node:path';
ipcMain.on('uploadFile', (event, filePaths: string[]) => {
// 获取用户当前文件夹路径
console.log('app', app.getGPUInfo('basic'))
// const saveDirectoryPath = await app.getPath('downloads');
// for (let i = 0; i < filePaths.length; i++) {
// const fileName = path.basename(filePaths[i]);
// const targetFilePath = path.join(__dirname, fileName);
// const fileStream = fs.createWriteStream(targetFilePath);
// fileStream.write(fs.readFileSync(filePaths[i]));
// fileStream.end();
// console.log('Uploaded file saved at:', targetFilePath);
// }
// const fileSize = fs.statSync(filePath).size;
// let uploadedSize = 0;
// const readStream = fs.createReadStream(filePath);
// readStream.on('data', (chunk) => {
// uploadedSize += chunk.length;
// const progress = Math.round((uploadedSize / fileSize) * 100);
// sender.send('upload-progress', progress);
// });
// readStream.on('end', () => {
// sender.send('upload-success');
// });
// readStream.on('error', (err) => {
// sender.send('upload-error', err.message);
// });
});
export const singleUpload = (file: File) => {
const path = file.path; //文件本地路径
const stats = fs.statSync(path); //读取文件信息
const chunkSize = 3 * 1024 * 1024; //每片分块的大小3M
const size = stats.size; //文件大小
const pieces = Math.ceil(size / chunkSize); //总共的分片数
function uploadPiece (i: number) {
//计算每块的结束位置
const endData = Math.min(size, (i + 1) * chunkSize);
const arr: any[] = [];
//创建一个readStream对象根据文件起始位置和结束位置读取固定的分片
const readStream = fs.createReadStream(path, { start: i * chunkSize, end: endData - 1 });
//on data读取数据
readStream.on('data', (data)=>{
arr.push(data)
})
//on end在该分片读取完成时触发
readStream.on('end', ()=>{
//这里服务端只接受blob对象需要把原始的数据流转成blob对象这块为了配合后端才转
const blob = new Blob(arr)
//新建formdata数据对象
const formdata = new FormData();
formdata.append("file", blob);
console.log('blob.size',blob.size)
formdata.append("size", size + ''); // 数字30被转换成字符串"30"
formdata.append("chunk", i + '');//第几个分片从0开始
formdata.append("chunks", pieces + '');//分片数
})
}
}

8
src/main/preload.ts Normal file
View File

@ -0,0 +1,8 @@
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('$api', {
getPlatform: async () => {
return await ipcRenderer.invoke('getPlatform');
},
uploadFile: (filePaths: string[]) => ipcRenderer.send('uploadFile', filePaths),
});

3
src/main/tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "@tsconfig/node21"
}

6
src/main/typing.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import { BrowserWindow } from 'electron';
declare global {
export function getBrowserWindowRuntime(): BrowserWindow;
const APP_ENV: string
}

21
src/models/count.ts Normal file
View File

@ -0,0 +1,21 @@
const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
export default {
namespace: 'count',
state: {
num: 0,
},
reducers: {
add(state: any) {
state.num += 1;
},
},
effects: {
*addAsync(_action: any, { put }: any) {
yield delay(1000);
yield put({ type: 'add' });
},
},
};

65
src/pages/docs.tsx Normal file
View File

@ -0,0 +1,65 @@
import { Button, message, Space, Upload } from 'antd'
import { useEffect, useState } from 'react';
import type { GetProp, UploadFile, UploadProps } from 'antd';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
const { Dragger } = Upload;
export default () => {
const [progress, setProgress] = useState(0)
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
const handleUpload = () => {
const formData = new FormData();
fileList.forEach((file) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
formData.append('files[]', file as FileType);
});
setUploading(true);
const filePaths = fileList.map((_file: any) => _file.path)
console.log('filePaths', filePaths)
window.$api.uploadFile(filePaths)
setUploading(false)
};
const uploadProps: UploadProps = {
name: 'file',
multiple: true,
capture: 'user',
hasControlInside: false,
onRemove: (file) => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
},
beforeUpload: (file) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
setFileList([...fileList, file]);
return false;
},
onDrop(e) {
console.log('Dropped files', e.dataTransfer.files);
},
fileList,
};
return (
<Space direction='vertical' style={{ width: '600px' }}>
<Dragger {...uploadProps}>
<p className="ant-upload-drag-icon">
{progress}
</p>
<p className="ant-upload-text"></p>
</Dragger>
<Button type='primary' block onClick={() => handleUpload()} ></Button>
</Space>
)
}

23
src/pages/index.tsx Normal file
View File

@ -0,0 +1,23 @@
import { Button, message } from 'antd';
import avatar from '../assets/avatar.jpg';
export default function HomePage() {
return (
<div>
<h2>Welcome to nicecode electron!</h2>
<p>
<img src={avatar} width="388" />
</p>
<p>
To get started, edit <code>pages/index.tsx</code> and save to reload.
</p>
<Button
onClick={async () => {
message.info(await window.$api.getPlatform());
}}
>
what is my platform?
</Button>
</div>
);
}

0
src/utils/index.ts Normal file
View File

3
tsconfig.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "./src/.umi/tsconfig.json"
}

7
typings.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import 'umi/typings';
declare global {
interface Window {
$api: any;
}
}