├── .nvmrc
├── docs
├── PRINT.md
├── CHANGE_LOG.md
├── CHANGE_LOG_EN.md
├── Copy.md
├── PART_UPDATE.md
└── PART_UPDATE_EN.md
├── server
├── src
│ ├── static
│ │ └── index.ts
│ ├── router
│ │ ├── Example
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── config
│ │ ├── prod.ts
│ │ ├── dev.ts
│ │ └── index.ts
│ ├── controllers
│ │ └── example
│ │ │ └── index.ts
│ ├── services
│ │ └── example
│ │ │ └── content.ts
│ ├── middleware
│ │ ├── log.ts
│ │ └── cors.ts
│ ├── utils
│ │ ├── redis
│ │ │ ├── redisConnection.ts
│ │ │ └── redisOperate.ts
│ │ ├── logger
│ │ │ └── globalLog.ts
│ │ └── fileOperation
│ │ │ └── index.ts
│ └── server.ts
├── dockerfile
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── src
├── main
│ ├── README.md
│ ├── public
│ │ ├── icon.ico
│ │ ├── icon.png
│ │ └── tray.png
│ ├── config
│ │ ├── config.js
│ │ └── webpack.config.js
│ ├── preload.js
│ ├── dev-app-update.yml
│ ├── app-update.yml
│ ├── controls
│ │ ├── AppTray.js
│ │ ├── AppMainWindow.js
│ │ └── AppAutoUpdater.js
│ ├── script
│ │ └── build.js
│ ├── util
│ │ └── utils.js
│ ├── index.js
│ └── loading.html
└── render
│ ├── README.md
│ ├── components
│ ├── FormCps
│ │ ├── index.tsx
│ │ └── readme.md
│ ├── TableCps
│ │ ├── index.tsx
│ │ └── readme.md
│ ├── readme.md
│ └── AutoUpdate
│ │ ├── style.less
│ │ └── index.tsx
│ ├── assets
│ ├── image
│ │ └── yay.jpg
│ └── style
│ │ ├── common.less
│ │ └── bootstrap-part.less
│ ├── .env
│ ├── layouts
│ ├── menu
│ │ ├── index.less
│ │ └── index.tsx
│ ├── loading
│ │ ├── Style.module.less
│ │ └── index.tsx
│ ├── index.less
│ ├── header
│ │ ├── index.tsx
│ │ └── index.less
│ ├── error
│ │ └── index.tsx
│ └── index.tsx
│ ├── api
│ └── api.list.ts
│ ├── common
│ ├── global.ts
│ └── enum.ts
│ ├── pages
│ ├── Foo
│ │ ├── services
│ │ │ └── foo.ts
│ │ ├── models
│ │ │ └── foo.ts
│ │ ├── components
│ │ │ └── TableList.tsx
│ │ └── index.tsx
│ ├── home.normal.less
│ ├── Home
│ │ ├── Edge
│ │ │ └── index.tsx
│ │ └── WebSocket
│ │ │ └── index.tsx
│ └── index.tsx
│ ├── utils
│ ├── polyfill
│ │ ├── electron-store.js
│ │ ├── electron.js
│ │ └── index.js
│ ├── store
│ │ └── index.ts
│ ├── rightClickMenuFuc.tsx
│ ├── axiosRequest.tsx
│ ├── common.ts
│ └── autoUpdate
│ │ └── partUpdate.js
│ ├── models
│ └── xxStore.ts
│ ├── global.less
│ ├── app.ts
│ ├── mock
│ └── foo.ts
│ ├── config
│ ├── menus.tsx
│ └── iconfont.ts
│ └── .umirc.ts
├── .prettierignore
├── .gitlab-ci.yml
├── .editorconfig
├── .gitignore
├── typings.d.ts
├── tsconfig.json
├── .prettierrc.js
├── scripts
├── webpack.main.js
└── main-build.js
├── package.json
├── README.md
├── README_EN.md
└── .eslintrc.js
/.nvmrc:
--------------------------------------------------------------------------------
1 | v14.18.3
2 |
--------------------------------------------------------------------------------
/docs/PRINT.md:
--------------------------------------------------------------------------------
1 | ## 打印相关使用文档
--------------------------------------------------------------------------------
/server/src/static/index.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/README.md:
--------------------------------------------------------------------------------
1 | # electron主进程
2 |
--------------------------------------------------------------------------------
/src/render/README.md:
--------------------------------------------------------------------------------
1 | # electron渲染进程
2 |
--------------------------------------------------------------------------------
/src/render/components/FormCps/index.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/render/components/TableCps/index.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/render/components/readme.md:
--------------------------------------------------------------------------------
1 | # 组件库写这儿
--------------------------------------------------------------------------------
/src/render/components/FormCps/readme.md:
--------------------------------------------------------------------------------
1 | # form组件
--------------------------------------------------------------------------------
/src/render/components/TableCps/readme.md:
--------------------------------------------------------------------------------
1 | # table组件
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/*.md
2 | **/*.svg
3 | **/*.ejs
4 | **/*.html
5 | package.json
6 | .umi
7 | .umi-production
8 |
--------------------------------------------------------------------------------
/src/main/public/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qld-cf/electron-react-tpl/HEAD/src/main/public/icon.ico
--------------------------------------------------------------------------------
/src/main/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qld-cf/electron-react-tpl/HEAD/src/main/public/icon.png
--------------------------------------------------------------------------------
/src/main/public/tray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qld-cf/electron-react-tpl/HEAD/src/main/public/tray.png
--------------------------------------------------------------------------------
/src/main/config/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 主进程全局配置
3 | */
4 |
5 | export const DEV_ADDRESS = 'http://localhost:9090'
6 |
--------------------------------------------------------------------------------
/src/render/assets/image/yay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qld-cf/electron-react-tpl/HEAD/src/render/assets/image/yay.jpg
--------------------------------------------------------------------------------
/src/render/.env:
--------------------------------------------------------------------------------
1 | # 环境变量配置
2 | # https://umijs.org/zh-CN/docs/env-variables#host
3 | PORT=9090
4 | ESLINT=none
5 | BROWSER=none
--------------------------------------------------------------------------------
/src/render/layouts/menu/index.less:
--------------------------------------------------------------------------------
1 | .menu {
2 | height: calc(100vh - var(--header-height));
3 | overflow-y: auto;
4 | }
5 |
--------------------------------------------------------------------------------
/src/render/api/api.list.ts:
--------------------------------------------------------------------------------
1 |
2 | // api 网关地址
3 | const apiList = {
4 | list: 'xxx',
5 |
6 |
7 | };
8 | export default apiList;
9 |
--------------------------------------------------------------------------------
/src/render/common/global.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局通用常量
3 | */
4 | export const pageSizeOptions = ['10', '20', '30', '40', '50', '100']
5 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - project: "arch-foundation/cicd"
3 | ref: electron
4 | file: "/templates/Electron.gitlab-ci.yml"
--------------------------------------------------------------------------------
/src/render/components/AutoUpdate/style.less:
--------------------------------------------------------------------------------
1 | // :global{
2 | // .ant-btn-primary{
3 | // margin-right: 10px;
4 | // }
5 | // }
--------------------------------------------------------------------------------
/src/render/pages/Foo/services/foo.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export function getFoo(params: any) {
4 | return axios('/api/foo', { method: 'POST', params })
5 | }
6 |
--------------------------------------------------------------------------------
/src/render/layouts/loading/Style.module.less:
--------------------------------------------------------------------------------
1 | .main {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | width: 100%;
6 | height: 100%;
7 | }
8 |
--------------------------------------------------------------------------------
/src/render/utils/polyfill/electron-store.js:
--------------------------------------------------------------------------------
1 | module.exports = class Store {
2 | get(name) {
3 | return localStorage.getItem(name)
4 | }
5 | set(name, val) {
6 | localStorage.setItem(name, val)
7 | }
8 | }
--------------------------------------------------------------------------------
/server/src/router/Example/index.ts:
--------------------------------------------------------------------------------
1 | import example from '../../controllers/example'
2 | import * as Router from 'koa-router';
3 | const router = new Router();
4 |
5 | router.get('*',example)
6 |
7 | module.exports = router
--------------------------------------------------------------------------------
/src/render/utils/polyfill/electron.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ipcRenderer: {
3 | send: () => {},
4 | on: () => {},
5 | },
6 | ipcMain: {
7 | send: () => {},
8 | on: () => {},
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/preload.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 为window挂载方法
3 | */
4 | const { ipcRenderer } = require('electron')
5 |
6 | /** 挂载停止启动loading方法 */
7 | window.stopLoading = function () {
8 | ipcRenderer.send('stop-loading-main')
9 | }
10 |
--------------------------------------------------------------------------------
/docs/CHANGE_LOG.md:
--------------------------------------------------------------------------------
1 | # electron-react-umi-tpl change log
2 |
3 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/CHANGE_LOG_EN.md)
4 |
5 | ### 2020-06-14
6 |
7 | 1. 添加增量更新, 支持远程更新,无需下载新包覆盖安装
8 | 2. 操作: 文档待补充
--------------------------------------------------------------------------------
/src/render/layouts/index.less:
--------------------------------------------------------------------------------
1 | .content {
2 | // height: calc(100vh - var(--header-height) - 32px); // 32px margin
3 | height: 0; // 父元素 flex 布局,这里直接设置 0
4 | margin: 16px;
5 | background-color: #eff0fa;
6 | overflow: auto;
7 | }
--------------------------------------------------------------------------------
/server/src/config/prod.ts:
--------------------------------------------------------------------------------
1 | const prodConfig = {
2 | baseUrl: '',
3 | baseHost: '',
4 | basePort: 3025,
5 | baseSocketPort: 8025,
6 | host_redis: '127.0.0.1',
7 | port_redis: 6379,
8 | }
9 |
10 | export default prodConfig
--------------------------------------------------------------------------------
/server/src/config/dev.ts:
--------------------------------------------------------------------------------
1 | const devConfig = {
2 | baseUrl: '',
3 | baseHost: '127.0.0.1',
4 | basePort: 3002,
5 | baseSocketPort: 8024,
6 | host_redis: '127.0.0.1',
7 | port_redis: 6379,
8 | }
9 |
10 | export default devConfig
--------------------------------------------------------------------------------
/src/main/dev-app-update.yml:
--------------------------------------------------------------------------------
1 | # 本地开发测试使用
2 |
3 | # 在dev-app-update.yml中配置检查更新的地址(全量更新) electron-builder会根据本地version和服务器yml文件version匹配
4 | # 如果远程包更新,则拉取后自动安装
5 | # 这个地址为开发环境下的地址
6 | # 需要放入根目录才能打包运行
7 | provider: 'generic'
8 | url: 'http://127.0.0.1:4000/download/'
9 | channel: 'latest'
--------------------------------------------------------------------------------
/server/src/controllers/example/index.ts:
--------------------------------------------------------------------------------
1 | import * as koa from 'koa'
2 | import {getContent} from '../../services/example/content'
3 |
4 | const index = async(ctx:koa.Context,next:Function)=>{
5 | ctx.body = getContent('koa')
6 | await next()
7 | }
8 |
9 | export default index
--------------------------------------------------------------------------------
/src/render/layouts/loading/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Spin } from 'antd'
3 | import styles from './Style.module.less'
4 |
5 | export default () => {
6 | return (
7 |
8 |
9 |
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/server/src/services/example/content.ts:
--------------------------------------------------------------------------------
1 | const getContent = (code:T):string=>{
2 | let res:any;
3 | if(typeof(code)!=='string'){
4 | res = `${String(code)} is not string`
5 | }else{
6 | res = `${code} is string`
7 | }
8 | return res
9 | }
10 |
11 | export {getContent}
--------------------------------------------------------------------------------
/docs/CHANGE_LOG_EN.md:
--------------------------------------------------------------------------------
1 | # electron-react-umi-tpl change log
2 |
3 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/CHANGE_LOG_EN.md)
4 |
5 | ### 2020-06-14
6 |
7 | 1. ADD electron Incremental update ;
8 | 2. jUST update app.zip for update online;
9 | 3. NOT need to download exe or dmg
10 |
--------------------------------------------------------------------------------
/src/render/common/enum.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局通用枚举
3 | */
4 |
5 | export enum FormTypes { // 表单类型
6 | INPUT = 'COMMON_INPUT',
7 | NUMBER = 'COMMON_INPUT_NUMBER',
8 | DATE = 'COMMON_DATE',
9 | RANGE_DATE = 'COMMON_RANGE_DATE',
10 | SELECT = 'COMMON_SELECT',
11 | BUSINESS_SELECT='BUSINESS_SELECT'
12 | };
13 |
--------------------------------------------------------------------------------
/server/dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | RUN mkdir -p /home/Service
4 |
5 |
6 | WORKDIR /home/Service
7 |
8 | COPY . /home/Service
9 |
10 | RUN npm install -g typescript
11 | RUN npm install
12 |
13 | ENV NODE_ENV production
14 |
15 | #PORT
16 | EXPOSE 8025
17 | EXPOSE 3025
18 |
19 | CMD [ "npm","run","build" ]
20 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/src/main/app-update.yml:
--------------------------------------------------------------------------------
1 | # 正式更新
2 |
3 | # 在dev-update.yml中配置检查更新的地址(全量更新) electron-builder会根据本地version和服务器yml文件version匹配
4 | # 如果远程包更新,则拉取后自动安装
5 | # 这个地址为正式环境下的更新地址
6 | # 需要放入根目录才能打包运行
7 | # channel: 检查更新的渠道 beta | latest",
8 | # url: 服务器地址
9 | # provider: 默认参数
10 | provider: 'generic'
11 | url: 'your server exe/dmg url'
12 | channel: 'latest'
--------------------------------------------------------------------------------
/src/render/utils/polyfill/index.js:
--------------------------------------------------------------------------------
1 | const dict = process.env.TARGET === 'web'
2 | ? {
3 | electron: require('./electron'),
4 | 'electron-store': require('./electron-store'),
5 | }
6 | : {
7 | electron: require('electron'),
8 | 'electron-store': require('electron-store'),
9 | }
10 |
11 | module.exports = function (name) {
12 | return dict[name]
13 | }
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | **/*.log
8 |
9 | test/unit/coverage
10 | test/e2e/reports
11 | selenium-debug.log
12 |
13 | # Editor directories and files
14 | .idea
15 | .vscode
16 | *.suo
17 | *.ntvs*
18 | *.njsproj
19 | *.sln
20 |
21 | package-lock.json
22 |
23 | /config/
24 | config.rar
25 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2017",
4 | "module": "commonjs",
5 | // "noImplicitAny": true,
6 | "outDir": "dist",
7 | "sourceMap": true,
8 | "noImplicitAny": false
9 | },
10 | "include": [
11 | "src/**/*"
12 | ],
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
--------------------------------------------------------------------------------
/src/render/pages/home.normal.less:
--------------------------------------------------------------------------------
1 | .container {
2 | background-repeat: no-repeat;
3 | background-size: cover;
4 | background-position: center center;
5 | }
6 | .homewrap {
7 | display: flex;
8 | flex-direction: column;
9 | margin: '30px 0 ';
10 | align-items: center;
11 | width: 100%;
12 | height: 100%;
13 | .bigFt {
14 | font-size: 36px;
15 | line-height: 50px;
16 | color: #474245;
17 | margin-top: 35px;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/server/src/middleware/log.ts:
--------------------------------------------------------------------------------
1 | import * as koa from 'koa'
2 | const log = () => {
3 | return async (ctx:koa.Context, next:Function) => {
4 | // 记录请求开始的时间
5 | const start = Date.now();
6 | await next();
7 | // 记录完成的时间 作差 计算响应时间
8 | const responseTime = Date.now() - start;
9 | if((global as any).log){
10 | (global as any).log.info(`响应时间为${responseTime / 1000}s`);
11 | }
12 | }
13 | };
14 |
15 | export default log
--------------------------------------------------------------------------------
/server/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as koa from 'koa';
3 | import * as Route from 'koa-router'
4 |
5 | const routerMount = (app:koa) => {
6 | fs.readdirSync(__dirname).forEach((file:string) => {
7 | if(file === 'index.js'||file === 'index.js.map'){
8 | return
9 | };
10 | const router:Route = require(`./${file}/index.js`);
11 | app.use(router.routes()).use(router.allowedMethods());
12 | })
13 | };
14 |
15 | export default routerMount
--------------------------------------------------------------------------------
/src/render/pages/Home/Edge/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Card, Button } from 'antd'
3 |
4 | interface IProps {
5 | loading: boolean
6 | }
7 |
8 | const Edge = (props: IProps | any) => {
9 |
10 | return (
11 |
12 |
13 | Edge
14 |
15 |
20 |
21 | )
22 | }
23 |
24 | export default Edge
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /npm-debug.log*
6 | /yarn-error.log
7 | /yarn.lock
8 | package-lock.json
9 |
10 | # production
11 | **/dist
12 |
13 | # misc
14 | .DS_Store
15 | .idea
16 | .vscode
17 | .svn
18 | .npmrc
19 | .yarnrc
20 |
21 | # umi
22 | .umi
23 | .umi-production
24 |
25 | # 一些临时文件
26 | **/.tmp
27 | **/.umi
28 |
29 | # Electron 构建文件
30 | release
31 | src/main/bundle.js
32 | src/main/bundle.js.map
33 |
--------------------------------------------------------------------------------
/src/render/models/xxStore.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局store dva+redux+redux-saga
3 | */
4 | export default {
5 | namespace: 'xxStore',
6 | state: {
7 | xxList: []
8 | },
9 | effects: {
10 | *updateList({ payload }: any, { put }: any) {
11 | // 开放使用方法
12 | yield put({
13 | type: 'save',
14 | payload: { xxList: payload }
15 | })
16 | }
17 | },
18 | reducers: {
19 | save(state: any, { payload }: any) {
20 | return { ...state, ...payload }
21 | }
22 | },
23 | subscriptions: {}
24 | }
25 |
--------------------------------------------------------------------------------
/src/render/layouts/header/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Layout } from 'antd'
3 | import './index.less'
4 |
5 |
6 | const { Header } = Layout
7 |
8 | const headerProps = {
9 | style: {
10 | background: '#A14EFF'
11 | }
12 | }
13 |
14 | const HeaderComponent = (props: any) => {
15 |
16 | return (
17 |
22 | )
23 | }
24 |
25 | export default HeaderComponent
26 |
--------------------------------------------------------------------------------
/server/src/config/index.ts:
--------------------------------------------------------------------------------
1 | import devConfig from './dev';
2 | import prodConfig from './prod';
3 |
4 | interface configDefault {
5 | baseUrl: string,
6 | baseHost: string,
7 | basePort: number
8 | baseSocketPort: number,
9 | host_redis: string,
10 | port_redis: number,
11 | }
12 |
13 | const getConfig = (type: string): configDefault => {
14 | let config: configDefault;
15 | if (typeof (type) !== 'string') {
16 | type = String(type)
17 | }
18 | if (type === 'production') {
19 | config = prodConfig
20 | }
21 | if (type === 'development') {
22 | config = devConfig
23 | }
24 | return config
25 | }
26 |
27 | export default getConfig
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | import { History } from 'umi'
2 |
3 | declare module '*.css'
4 | declare module '*.less'
5 |
6 | // declare module '*.module.less' {
7 | // const styles: { [className: string]: string }
8 | // export default styles
9 | // }
10 |
11 | declare module '*.png'
12 | declare module '*.jpg'
13 | declare module '*.jpeg'
14 |
15 | declare module 'umi' {
16 | /** react-router history 对象 */
17 | export const history: History
18 | }
19 |
20 | declare global {
21 | /** 启动时候的 ENV */
22 | const REACT_APP_MY_ENV: string
23 |
24 | interface Window {
25 | /** 浏览器下开发,关闭 electron 载入动画 */
26 | stopLoading: () => void
27 | /** 是否开启自动更新 */
28 | isOpenAutoUpdate: boolean
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/config/webpack.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * electron web端编译配置
3 | */
4 | const path = require('path');
5 |
6 | const pathResolve = (dir = '') => path.join(__dirname, '..', dir) // 指向 src/main
7 |
8 | module.exports = {
9 | mode: 'production',
10 | devtool: 'cheap-module-source-map',
11 | target: 'electron-main',
12 | entry: pathResolve('index.js'),
13 | output: {
14 | path: pathResolve(),
15 | filename: 'bundle.js',
16 | },
17 | node: {
18 | __dirname: false,
19 | __filename: false
20 | },
21 | resolve: {
22 | extensions: ['.js', '.ts', '.tsx', '.jsx', '.json']
23 | },
24 | module: {
25 | rules: [
26 | {
27 | test: /\.js$/,
28 | exclude: /node-modules/
29 | },
30 | ]
31 | }
32 | };
--------------------------------------------------------------------------------
/src/render/pages/Foo/models/foo.ts:
--------------------------------------------------------------------------------
1 | import * as api from '../services/foo'
2 |
3 | export interface DataItem {
4 | age: number
5 | name: string
6 | job: string
7 | id: number | string
8 | }
9 |
10 | export interface FooState {
11 | list: Array
12 | }
13 |
14 | export default {
15 | namespace: 'foo',
16 | state: {
17 | list: []
18 | },
19 | effects: {
20 | *fetch({ payload }: any, { call, put }: any) {
21 | const { data } = yield call(api.getFoo, payload)
22 | yield put({
23 | type: 'save',
24 | payload: { list: data.list }
25 | })
26 | }
27 | },
28 | reducers: {
29 | save(state: FooState, { payload }: any) {
30 | return { ...state, ...payload }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/render/layouts/header/index.less:
--------------------------------------------------------------------------------
1 | .layout-top-eader {
2 | background-size: 100% 100%;
3 | background-repeat: no-repeat;
4 | padding: 0;
5 | color: white;
6 |
7 | .logo {
8 | width: 200px;
9 | display: flex;
10 | flex-direction: row;
11 | align-items: center;
12 |
13 | img {
14 | width: 44px;
15 | }
16 |
17 | h1 {
18 | color: white;
19 | font-size: 24px;
20 | margin: 0 0 0 15px;
21 | }
22 | }
23 |
24 | .header-shop-box {
25 | margin: 0 10px;
26 | .shop-name {
27 | width: 200px;
28 | padding: 0 9px;
29 | line-height: 34px;
30 | border-radius: 4px;
31 | box-shadow: 0 0 1px #ccc;
32 | }
33 | .ant-select {
34 | width: 200px;
35 | }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/render/utils/store/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Q: electron无法正常保存COOKIE STORAGE
3 | * 方案: electron客户端保存本地数据Store
4 | */
5 | const Store = require('@utils/polyfill')('electron-store')
6 |
7 | const store = new Store()
8 |
9 | class GlobalStore {
10 | public get(name: string | string[]): any {
11 | if (Array.isArray(name)) {
12 | return name.map(n => store.get(n))
13 | } else {
14 | return store.get(name)
15 | }
16 | }
17 | public save(key: string, val: any): void {
18 | store.set(key, val)
19 | }
20 | public delete(key: string | string[]): void {
21 | if (Array.isArray(key)) {
22 | key.forEach(k => store.delete(k))
23 | } else {
24 | store.delete(key)
25 | }
26 | }
27 | }
28 |
29 | export default new GlobalStore()
30 |
--------------------------------------------------------------------------------
/src/render/pages/Foo/components/TableList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { TableProps, ColumnType } from 'antd/lib/table'
3 | import { Table } from 'antd'
4 | import { DataItem } from '../models/foo'
5 |
6 | const columns: Array> = [
7 | {
8 | dataIndex: 'name',
9 | title: 'Name'
10 | },
11 | {
12 | dataIndex: 'age',
13 | title: 'Age'
14 | },
15 | {
16 | dataIndex: 'job',
17 | title: 'Job'
18 | }
19 | ]
20 |
21 | export interface IProps extends TableProps {
22 | loading: boolean
23 | }
24 |
25 | export default (props: IProps) => {
26 | const tableProps: TableProps = {
27 | ...props,
28 | columns,
29 | rowKey: (recode: DataItem) => recode.id
30 | }
31 |
32 | return
33 | }
34 |
--------------------------------------------------------------------------------
/src/render/global.less:
--------------------------------------------------------------------------------
1 | /**
2 | * Umi 中约定 src/global.less 为全局样式
3 | */
4 | @import './assets/style/bootstrap-part.less';
5 | @import './assets/style/common.less';
6 |
7 | :root {
8 | --header-height: 64px;
9 | }
10 |
11 | html,
12 | body,
13 | #root {
14 | height: 100%;
15 | }
16 |
17 | ul,
18 | li {
19 | list-style: none;
20 | margin: 0;
21 | padding: 0;
22 | }
23 |
24 | body {
25 | margin: 0;
26 | }
27 |
28 | // 滚动条美化
29 | * {
30 | &::-webkit-scrollbar {
31 | width: 7px;
32 | height: 7px;
33 | }
34 |
35 | &::-webkit-scrollbar-thumb {
36 | border-radius: 2px;
37 | // box-shadow: inset 0 0 5px rgba(99, 99, 99, .4);
38 | background: rgba(161, 78, 255, 0.24); // 滑动条
39 | }
40 |
41 | &::-webkit-scrollbar-track {
42 | box-shadow: inset 0 0 5px rgba(99, 99, 99, 0.24);
43 | border-radius: 2px;
44 | background: #ededed; // 条背景
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/render/pages/Home/WebSocket/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { Card } from 'antd'
3 | import socketIOClient from "socket.io-client";
4 |
5 | const ENDPOINT = "http://127.0.0.1:3002";
6 |
7 | interface IProps {
8 | loading: boolean
9 | }
10 |
11 | const WebSocket = (props: IProps) => {
12 | const [response, setResponse] = useState("");
13 | useEffect(() => {
14 | const socket = socketIOClient(ENDPOINT, { transport: ['websocket'] });
15 | console.log('socket', socket)
16 | if (socket) {
17 | socket.on("FromAPI", (data: React.SetStateAction) => {
18 | setResponse(data);
19 | });
20 | }
21 | }, []);
22 | return (
23 |
24 | Settings
25 |
26 | WebSocket检测: 当前服务器返回时间:
27 |
28 |
29 | )
30 | }
31 |
32 | export default WebSocket
33 |
--------------------------------------------------------------------------------
/src/render/app.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 自定义运行时配置
3 | * https://umijs.org/zh-CN/docs/runtime-config
4 | */
5 | import React from 'react'
6 | import { ConfigProvider } from 'antd'
7 | import zhCN from 'antd/es/locale/zh_CN'
8 | import RightClickMenuFuc from './utils/rightClickMenuFuc'
9 | import ErrorBoundary from '@/layouts/error'
10 |
11 | // 注入右键复制黏贴
12 | RightClickMenuFuc()
13 |
14 | // 是否开启app自动更新, 默认关闭
15 | window.isOpenAutoUpdate = false
16 |
17 | // 定时器: 关闭electron的loading
18 | setTimeout(() => window.stopLoading(), 1000)
19 |
20 | // 覆写 render
21 | export function render(oldRender: () => void) {
22 | oldRender()
23 | }
24 |
25 | // 修改交给 react-dom 渲染时的根组件
26 | // https://umijs.org/zh-CN/docs/runtime-config
27 | export function rootContainer(container: React.ReactDOM) {
28 | return React.createElement(
29 | ErrorBoundary,
30 | {},
31 | React.createElement(ConfigProvider, { locale: zhCN }, container)
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": "./",
11 | "strict": true,
12 | "allowJs": true,
13 | "outDir": "./dist",
14 | "paths": {
15 | "@/*": ["src/render/*"],
16 | "@@/*": ["src/render/.umi/*"],
17 | "@config/*": ["src/render/config/*"],
18 | "@components/*": ["src/render/components/*"],
19 | "@utils/*": ["src/render/utils/*"],
20 | "@typings/*": ["typings/*"],
21 | "@pages/*": ["src/render/pages/*"]
22 | },
23 | "allowSyntheticDefaultImports": true
24 | },
25 | // "include": ["./typings.d.ts"],
26 | // "typings": "./typings.d.ts",
27 | // "exclude": [
28 | // "node_modules",
29 | // "dist",
30 | // "scripts",
31 | // "build",
32 | // ]
33 | }
34 |
--------------------------------------------------------------------------------
/src/render/pages/Foo/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * redux实例
3 | */
4 | import React from 'react'
5 | import { Card } from 'antd'
6 | import { connect } from 'umi'
7 | import { ConnectProps } from 'umi/types'
8 | import TableList from './components/TableList'
9 | import { FooState } from './models/foo'
10 |
11 | interface IProps extends ConnectProps {
12 | foo: FooState
13 | loading: boolean
14 | }
15 |
16 | const Foo = (props: IProps) => {
17 | const { dispatch = () => {}, foo } = props
18 | React.useEffect(() => {
19 | dispatch({
20 | type: 'foo/fetch',
21 | payload: {}
22 | })
23 | }, [])
24 |
25 | const data = React.useMemo(() => {
26 | return foo.list
27 | }, [foo.list])
28 |
29 | return (
30 |
31 |
32 |
33 | )
34 | }
35 |
36 | export default connect(({ foo, loading }: any) => ({
37 | foo,
38 | loading: loading.effects['foo/fetch']
39 | }))(Foo)
40 |
--------------------------------------------------------------------------------
/src/render/mock/foo.ts:
--------------------------------------------------------------------------------
1 | // 使用 Mock
2 | const Mock = require('mockjs')
3 |
4 | const barData = Mock.mock({
5 | // 属性 list 的值是一个数组,其中含有 1 到 10 个元素
6 | 'list|1-10': [
7 | {
8 | // 属性 id 是一个自增数,起始值为 1,每次增 1
9 | 'id|+1': 1
10 | }
11 | ]
12 | })
13 |
14 | module.exports = {
15 | // 'GET /mock/api/goods/list': barData,
16 |
17 | 'POST /api/foo'(req: any, res: any) {
18 | res.json({
19 | list: [
20 | { id: 1, age: 29, name: 'anan', job: '前端组长' },
21 | { id: 2, age: 36, name: 'andy', job: 'BOSS' },
22 | { id: 3, age: 28, name: 'kevin', job: '前端开发' }
23 | ]
24 | })
25 | },
26 |
27 | 'GET /mock/api/data/goods/list': {
28 | success: true, // 网关层数据返回
29 | traceId: 'xxxxxxx',
30 | result: {
31 | success: true,
32 | result: {
33 | list: [
34 | {
35 | id: 1,
36 | name: 'goods-1'
37 | }
38 | ],
39 | size: '10',
40 | pageSize: '1',
41 | total: '100'
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /* const fabric = require('@umijs/fabric');
2 | module.exports = {
3 | ...fabric.prettier,
4 | };
5 | */
6 |
7 | // .prettierrc.js
8 | module.exports = {
9 | // 一行最多 100 字符
10 | printWidth: 100,
11 | // 使用 2 个空格缩进
12 | tabWidth: 2,
13 | // 不使用缩进符,而使用空格
14 | useTabs: false,
15 | // 行尾需要有分号
16 | semi: false,
17 | // 使用单引号
18 | singleQuote: true,
19 | // 对象的 key 仅在必要时用引号
20 | quoteProps: 'as-needed',
21 | // jsx 使用单引号
22 | jsxSingleQuote: true,
23 | // 末尾不需要逗号
24 | trailingComma: 'none',
25 | // 大括号内的首尾需要空格
26 | bracketSpacing: true,
27 | // jsx 标签的反尖括号需要换行
28 | jsxBracketSameLine: false,
29 | // 箭头函数,只有一个参数的时候,也需要括号
30 | // arrowParens: 'always',
31 | // 每个文件格式化的范围是文件的全部内容
32 | rangeStart: 0,
33 | rangeEnd: Infinity,
34 | // 不需要写文件开头的 @prettier
35 | requirePragma: false,
36 | // 不需要自动在文件开头插入 @prettier
37 | insertPragma: false,
38 | // 使用默认的折行标准
39 | proseWrap: 'preserve',
40 | // 根据显示样式决定 html 要不要折行
41 | htmlWhitespaceSensitivity: 'css',
42 | // 换行符使用 lf
43 | endOfLine: 'lf'
44 | }
45 |
--------------------------------------------------------------------------------
/server/src/utils/redis/redisConnection.ts:
--------------------------------------------------------------------------------
1 | import * as ioredis from 'ioredis';
2 | import { resolve } from 'dns';
3 |
4 | interface redisConfig {
5 | port:number,
6 | host:string,
7 | password?:string
8 | db?:number //连接的数据库id,默认为0,即第一个数据库
9 | family?:number //可填参数:4、6 (ipv4\ipv6)
10 | }
11 |
12 | let clientCreate = (config:redisConfig,callback_:Function) => {
13 | let redis:ioredis.Redis = new ioredis(config);
14 | redis.on('connect',()=>{ //根据 connect 事件判断连接成功
15 | callback_(null,redis) //链接成功, 返回 redis 连接对象
16 | })
17 | redis.on('error',(err)=>{ //根据 error 事件判断连接失败
18 | callback_(err,null) //捕捉异常, 返回 error
19 | })
20 | }
21 |
22 | let redisConn = (options?:redisConfig) => {
23 | let config = options
24 | return new Promise((resolve,reject) => { //返回API调用方 一个 promise 对象
25 | clientCreate(config,(err:any,conn:ioredis.Redis) => {
26 | if(err) {
27 | reject(err)
28 | }
29 | resolve(conn) //返回连接的redis对象
30 | })
31 | })
32 | }
33 |
34 | export default redisConn
--------------------------------------------------------------------------------
/src/render/layouts/error/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { ErrorInfo } from 'react'
2 | import { Spin } from 'antd'
3 | const log = require('electron-log');
4 |
5 | export default class ErrorBoundary extends React.PureComponent {
6 | static getDerivedStateFromError() {
7 | return { hasError: true }
8 | }
9 |
10 | state = { hasError: false }
11 | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
12 | const errorData = `${error.stack}\n${errorInfo.componentStack}`
13 | log.error(`ErrorBoundary-${new Date()}: `, errorData)
14 | if (process.env.NODE_ENV !== 'development') { // 生产环境才开启
15 | location.href = location.href.includes('index.html') ? `${location.origin}/index.html` : location.origin as string
16 | }
17 | }
18 |
19 | render() {
20 | const { children } = this.props
21 | const { hasError } = this.state
22 |
23 | if (hasError) {
24 | return (
25 |
26 |
27 |
28 | )
29 | } else {
30 | return children
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/render/config/menus.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 左侧菜单配置
3 | */
4 | import React from 'react'
5 | import { AreaChartOutlined, createFromIconfontCN } from '@ant-design/icons'
6 | import { iconFonts, icons } from './iconfont'
7 |
8 | const isDev = process.env.APP_ENV === 'development'
9 |
10 | // iconfont.cn
11 | const IconFont = createFromIconfontCN({
12 | // scriptUrl: '//at.alicdn.com/t/font_1492696_4ai9rbngxhe.js'
13 | scriptUrl: 'http://at.alicdn.com/t/font_1492696_4ai9rbngxhe.js'
14 | })
15 |
16 | export interface IMenu {
17 | title: string
18 | path: string
19 | fullPath?: string
20 | icon?: React.ReactNode
21 | subs?: Array
22 | electron?: boolean
23 | }
24 |
25 | export default [
26 | {
27 | title: 'Home',
28 | path: '/Home',
29 | icon: ,
30 | subs: [
31 | {
32 | title: 'WebSocket',
33 | path: '/WebSocket',
34 | fullPath: '/Home/WebSocket'
35 | },
36 | {
37 | title: 'Edge',
38 | path: '/Edge',
39 | fullPath: '/Home/Edge'
40 | }
41 | ]
42 | }
43 | ] as Array
44 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "koa4typescript",
3 | "version": "1.0.0",
4 | "description": "use ts in koa2",
5 | "main": "dist/index.js",
6 | "scripts": {
7 | "dev": "nodemon --watch 'src/**/*' -e ts,tsx --exec npm run start ",
8 | "start": "tsc && cross-env NODE_ENV=development node dist/server.js",
9 | "build": "tsc",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "keywords": [
13 | "koa",
14 | "typescript"
15 | ],
16 | "author": "chrislee0211",
17 | "license": "ISC",
18 | "dependencies": {
19 | "@types/ioredis": "^4.0.18",
20 | "@types/koa-bodyparser": "^4.3.0",
21 | "@types/log4js": "^2.3.5",
22 | "@types/socket.io": "^2.1.4",
23 | "@types/vfile-message": "^2.0.0",
24 | "cross-env": "^6.0.3",
25 | "ioredis": "^4.14.1",
26 | "koa": "^2.10.0",
27 | "koa-bodyparser": "^3.2.0",
28 | "koa-router": "^7.4.0",
29 | "log4js": "^5.3.0",
30 | "socket.io": "^2.3.0"
31 | },
32 | "devDependencies": {
33 | "@types/koa": "^2.0.51",
34 | "@types/koa-router": "^7.0.42",
35 | "nodemon": "^1.19.4",
36 | "ts-node": "^8.4.1",
37 | "typescript": "^3.6.4"
38 | }
39 | }
--------------------------------------------------------------------------------
/src/main/controls/AppTray.js:
--------------------------------------------------------------------------------
1 | /**
2 | * APP托盘
3 | */
4 | 'use strict'
5 |
6 | const { app, Menu, Tray } = require('electron')
7 | const path = require('path')
8 | const trayIcon = `${path.join(__dirname, '/public/tray.png')}`
9 |
10 | module.exports = class AppTray {
11 | constructor(mainWindow) {
12 | this.mainWindow = mainWindow || null
13 | this.initTray()
14 | }
15 |
16 | initTray() {
17 | this.tray = new Tray(trayIcon)
18 | this.tray.setToolTip('我的托盘图标')
19 |
20 | const trayMenuTemplate = [
21 | {
22 | label: '显示主页面',
23 | click: () => {
24 | this.mainWindow.show()
25 | }
26 | },
27 | {
28 | label: '退出',
29 | click: () => {
30 | app.quit()
31 | }
32 | }
33 | ]
34 | const contextMenu = Menu.buildFromTemplate(trayMenuTemplate)
35 | // 设置此图标的上下文菜单
36 | this.tray.setContextMenu(contextMenu)
37 | // 单击右下角小图标显示应用
38 | this.tray.on('click', () => {
39 | this.mainWindow.isVisible() ? this.mainWindow.hide() : this.mainWindow.show()
40 | this.mainWindow.isVisible()
41 | ? this.mainWindow.setSkipTaskbar(false)
42 | : this.mainWindow.setSkipTaskbar(true)
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | # koa2 + typescript + websocket + redis + log4js
2 |
3 | ## 一、说明
4 |
5 | > - cors 跨域处理
6 | > - redis
7 | > - websocket
8 | > - log4js: 日志中间件
9 |
10 | ## 二、使用
11 |
12 | ```
13 | npm install
14 |
15 | // 本地热加载开发
16 | npm run dev
17 | // 构建成js
18 | npm run build
19 | // 线上启动 pm2或run dev
20 | npm run pm2-start:watch
21 |
22 |
23 | ```
24 |
25 | #### 前端使用
26 |
27 | ```
28 | import React, { useEffect, useState } from 'react'
29 | import { Card } from 'antd'
30 |
31 | import socketIOClient from "socket.io-client";
32 |
33 |
34 | const ENDPOINT = "http://127.0.0.1:8002";
35 |
36 | interface IProps {
37 | loading: boolean
38 | }
39 |
40 | const WebSocket = (props: IProps) => {
41 | const [response, setResponse] = useState("");
42 | useEffect(() => {
43 | const socket = socketIOClient(ENDPOINT, { transport: ['websocket'] });
44 | console.log('socket', socket)
45 | if (socket) {
46 | socket.on("FromAPI", data => {
47 | setResponse(data);
48 | });
49 | }
50 | }, []);
51 | return (
52 |
53 | Settings
54 |
55 | WebSocket检测: 当前服务器返回时间:
56 |
57 |
58 | )
59 | }
60 |
61 | export default WebSocket
62 |
63 |
64 | ```
65 |
--------------------------------------------------------------------------------
/src/render/utils/rightClickMenuFuc.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 右键复制黏贴
3 | */
4 | const remote = require('electron').remote;
5 | const Menu = remote.Menu;
6 | const MenuItem = remote.MenuItem;
7 |
8 |
9 | const RightClickMenuFuc = () => {
10 | /**
11 | * 判断点击区域可编辑
12 | * @param {*} e
13 | */
14 | function isEleEditable(e: any): any {
15 | if (!e) {
16 | return false;
17 | }
18 | // 为input标签或者contenteditable属性为true
19 | if (e.tagName === 'INPUT' || e.contentEditable === 'true') {
20 | return true;
21 | } else {
22 | // 递归查询父节点
23 | return isEleEditable(e.parentNode)
24 | }
25 | }
26 |
27 | const menu = new Menu();
28 | menu.append(new MenuItem({ label: '粘贴', role: 'paste' }));
29 |
30 | const menu2 = new Menu();
31 | menu2.append(new MenuItem({ label: '复制', role: 'copy' }));
32 | window.addEventListener('contextmenu', (e) => { // 上下文监听事件
33 | e.preventDefault();
34 | if (isEleEditable(e.target)) {
35 | menu.popup(remote.getCurrentWindow());
36 | } else {
37 | // 判断有文本选中
38 | let selectText = window?.getSelection()?.toString();
39 | if (selectText) {
40 | menu2.popup(remote.getCurrentWindow());
41 | }
42 | }
43 |
44 | }, false)
45 | }
46 |
47 | export default RightClickMenuFuc
48 |
49 |
--------------------------------------------------------------------------------
/src/render/utils/axiosRequest.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * axios二次封装
3 | */
4 |
5 | import axios from 'axios'
6 |
7 | const instance = axios.create({
8 | baseURL: process.env.REACT_APP_URL,
9 | timeout: 10000
10 | })
11 |
12 | // 请求拦截
13 | instance.interceptors.request.use(
14 | config => {
15 | // config.headers['X-Token'] = ''
16 | return config
17 | },
18 | error => {
19 | console.error(error)
20 | return Promise.reject(error)
21 | }
22 | )
23 | // 响应拦截
24 | instance.interceptors.response.use(
25 | response => {
26 | const res = response.data
27 | if (res.code === 200) {
28 | return res.data
29 | } else {
30 | console.error('INSTANCE_ERROR')
31 | }
32 | },
33 | error => {
34 | return Promise.reject(error)
35 | }
36 | )
37 |
38 | export const getRequest = (url: any) => {
39 | return instance({
40 | method: 'get',
41 | url: url
42 | });
43 | }
44 |
45 | export const postRequest = (url: any, params: any) => {
46 | return instance({
47 | method: 'post',
48 | url: url,
49 | data: params,
50 | transformRequest: [(data) => {
51 | let ret = ''
52 | for (const it in data) {
53 | ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
54 | }
55 | return ret
56 | }]
57 | });
58 | }
--------------------------------------------------------------------------------
/server/src/utils/logger/globalLog.ts:
--------------------------------------------------------------------------------
1 | import * as log4js from 'log4js';
2 | import default_redis from '../redis/redisOperate';
3 |
4 | const methods = ["trace", "debug", "info", "warn", "error", "fatal", "mark"];
5 | const contextLogger:any = {};
6 | log4js.configure({
7 | appenders: {
8 | console: {
9 | type: 'stdout',
10 | },
11 | cheese: {
12 | type: 'dateFile',
13 | encoding: 'utf-8',
14 | filename: 'logs/globalLog',
15 | layout: {
16 | type: "pattern",
17 | pattern: '{"date":"%d","level":"%p","data":\'%m\'}'
18 | },
19 | pattern: "-yyyy-MM-dd.log",
20 | alwaysIncludePattern: true,
21 | },
22 | },
23 | categories: {default: {appenders: ['cheese','console'], level: 'info'}}
24 | });
25 |
26 | const bindLog = function () {
27 |
28 | const logger:any = log4js.getLogger('cheese');
29 | // 循环 methods 将所有方法挂载到ctx 上
30 | methods.forEach((method:any) => {
31 | contextLogger[method] = (message:any) => {
32 | logger[method](message)
33 | }
34 | });
35 | // 为 ctx 增加 log 方法
36 | Object.defineProperty(global, 'log', {
37 | value: contextLogger,
38 | writable: false,
39 | enumerable: true,
40 | configurable: false
41 | });
42 |
43 | };
44 |
45 | export default bindLog
--------------------------------------------------------------------------------
/scripts/webpack.main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 主进程 webpack 配置
3 | */
4 | const path = require('path');
5 |
6 | const resolve = (dir = '') => path.join(__dirname, '../src/main', dir) // 指向 src/main
7 |
8 | module.exports = function (env) {
9 | process.env.NODE_ENV = env;
10 | const isDev = env === 'development';
11 |
12 | return {
13 | mode: isDev ? 'development' : 'production',
14 | devtool: isDev ? undefined : 'cheap-module-source-map',
15 | target: 'electron-main',
16 | entry: resolve('index.js'),
17 | output: {
18 | path: resolve(),
19 | filename: 'bundle.js',
20 | },
21 | node: {
22 | __dirname: false,
23 | __filename: false
24 | },
25 | resolve: {
26 | extensions: ['.js', '.json', '.ts'],
27 | alias: {
28 | '@root': path.join(__dirname, '..'),
29 | },
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /\.(js|ts)$/,
35 | exclude: /node-modules/,
36 | loader: 'babel-loader',
37 | options: {
38 | plugins: ["@babel/plugin-transform-typescript"],
39 | },
40 | },
41 | {
42 | test: /\.node$/,
43 | loader: isDev ? 'node-loader' : 'relative-loader',
44 | options: isDev ? undefined : {
45 | relativePath: '/aclas-addons/'
46 | }
47 | },
48 | ]
49 | }
50 | };
51 | };
52 |
--------------------------------------------------------------------------------
/server/src/utils/fileOperation/index.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 |
4 | /**
5 | * 写入文件工具,所有静态文件m默认存在static文件夹内
6 | * @param data 写入的内容
7 | * @param fileName 文件名称
8 | * @returns {string} 若写入成功则返回文件路径,否则返回undefined
9 | * @author chrislee
10 | * @Time 2020/4/11
11 | */
12 | export const writeFile = async (data:any,fileName:string):Promise => {
13 | let fileAddr:string = ``;
14 | fileAddr = path.resolve(__dirname,`../../static/${fileName}`);
15 | return new Promise((resolve,reject) => {
16 | fs.writeFile(fileAddr,data,(err:NodeJS.ErrnoException) => {
17 | if(err){
18 | reject(undefined)
19 | }else{
20 | resolve(fileAddr)
21 | }
22 | })
23 | })
24 | }
25 |
26 |
27 | /**
28 | * 删除指定的文件名
29 | * @param fileName 文件名称
30 | * @returns {string} 若删除成功,返回文件名称,否则返回undefined
31 | * @author chrislee
32 | * @Time 2020/4/11
33 | */
34 | export const removeFile = async (fileName:string):Promise => {
35 | let fileAddr:string = ``;
36 | fileAddr = path.resolve(__dirname,`../../static/${fileName}`);
37 | return new Promise((resolve,reject) => {
38 | fs.unlink(fileAddr,(err:NodeJS.ErrnoException)=>{
39 | if(err){
40 | reject(undefined)
41 | }else{
42 | resolve(fileName)
43 | }
44 | })
45 | })
46 | }
--------------------------------------------------------------------------------
/src/render/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './home.normal.less'
3 | import AutoUpdate from '@components/AutoUpdate'
4 | import { Button, message, Input } from 'antd'
5 | const Store = require('electron-store')
6 | const store = new Store()
7 |
8 | export default function () {
9 | const getLocalStoreData = () => {
10 | message.info(store.get('LOCAL_ELECTRON_STORE'))
11 | }
12 | return (
13 |
14 |
Welcome to MapleChain Electron App
15 |
16 |
electron-store
17 |
18 |
26 |
29 |
30 |
RightClickMenuFuc
31 |
32 | Here is the copy msg | 选中任意文字后右键复制
33 |
34 |
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/src/render/layouts/menu/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 左侧菜单
3 | */
4 | import React from 'react'
5 | import { Menu } from 'antd'
6 | import { useHistory, useLocation } from 'umi'
7 | import menus, { IMenu } from '@config/menus'
8 | import styles from './index.less'
9 |
10 | const { SubMenu, Item } = Menu
11 |
12 | export default (props: any) => {
13 | const history = useHistory()
14 | const location = useLocation()
15 |
16 | const clickMenu = (path: string) => {
17 | history.push(path)
18 | }
19 |
20 | const generate = (menus: Array, deep = [] as Array) => {
21 | return menus.map(menu => {
22 | const paths = [...deep, menu.path]
23 | const path = paths.join('')
24 |
25 | return Array.isArray(menu.subs) && menu.subs.length ? (
26 |
31 | {generate(menu.subs, paths)}
32 |
33 | ) : (
34 | - clickMenu(path)}
37 | title={menu.title}
38 | icon={menu.icon}
39 | >
40 | {menu.title}
41 |
42 | )
43 | })
44 | }
45 |
46 | return (
47 |
54 | )
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/script/build.js:
--------------------------------------------------------------------------------
1 | /**
2 | * webpack 构建
3 | */
4 | const webpack = require('webpack');
5 | const chalk = require('chalk');
6 | const config = require('../config/webpack.config');
7 |
8 | const compiler = webpack(config);
9 | const TAG = '[src/main/script/build.js]';
10 |
11 | compiler.hooks.beforeCompile.tap(TAG, arg0 => {
12 | console.log(TAG, chalk.yellow('Electron webpack 开始构建'));
13 | });
14 |
15 | compiler.run((err, stats) => {
16 | if (err) {
17 | // err 对象将只包含与webpack相关的问题,例如错误配置等
18 | console.log(TAG, chalk.red('💥 Electron webpack 相关报错'));
19 | } else if (stats.hasErrors()) {
20 | // webpack 编译报错
21 | const json = stats.toJson('errors-only');
22 | console.log(TAG, filterLogs(json.errors)().join('\n'));
23 | console.log(TAG, chalk.red('💥 Electron 构建报错'));
24 | } else {
25 | console.log(TAG, chalk.green('Electron webpack 构建完成'));
26 | }
27 | });
28 |
29 | /**
30 | * webpack 日志过滤
31 | */
32 | function filterLogs(errors) {
33 | let tmp = [];
34 | return function (filter = true) {
35 | if (filter) {
36 | errors.forEach(err => {
37 | if (err.includes('Error: Child compilation failed:')) {
38 | // 忽略 webpack 内部调用错误栈
39 | return;
40 | }
41 | if (!tmp.find(_ => _.split('\n')[1] === err.split('\n')[1])) {
42 | // 一个错误,可能会被爆出多次,做下报错去重
43 | // 比如一个 loader 报错,那么 n 个文件经过 loader 就会报出 n 个错误
44 | tmp.push(err);
45 | }
46 | });
47 | } else {
48 | tmp = errors;
49 | }
50 | return tmp;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/server/src/server.ts:
--------------------------------------------------------------------------------
1 | import * as Koa from 'koa'; // koa框架
2 | import getConfig from './config';
3 | import * as http from 'http';
4 | import * as socketIO from 'socket.io'
5 | import globalLogger from './utils/logger/globalLog'
6 | import log from './middleware/log'
7 | // 路由分发
8 | import routerMount from './router/index';
9 |
10 |
11 | // 中间件
12 | import cors from './middleware/cors';
13 | import * as bodyParser from 'koa-bodyparser';
14 |
15 | const app = new Koa(); // 新建一个koa应用
16 | const env = process.env.NODE_ENV
17 | const PORT: number | string = getConfig(env).basePort;
18 | const server = http.createServer(app.callback());
19 | // const io = socketIO(server, { pingInterval: 20000 })
20 | const io = socketIO(server)
21 |
22 |
23 | let interval;
24 |
25 | io.on("connection", (socket) => {
26 | console.log("New client connected");
27 | if (interval) {
28 | clearInterval(interval);
29 | }
30 | // getApiAndEmit(socket)
31 | interval = setInterval(() => getApiAndEmit(socket), 3000);
32 | socket.on("disconnect", () => {
33 | console.log("Client disconnected");
34 | clearInterval(interval);
35 | });
36 | });
37 |
38 | const getApiAndEmit = socket => {
39 | const response = new Date();
40 | // 向客户端发送事件
41 | socket.emit("FromAPI", response);
42 | };
43 |
44 |
45 | app.use(cors)
46 | app.use(log())
47 | app.use(bodyParser())
48 | routerMount(app)
49 |
50 | globalLogger()
51 |
52 |
53 | const _server = app.listen(PORT); // 监听应用端口
54 |
55 | server.listen(_server) // 监听socket端口 必须要监听该端口服务 不然405
56 | // server.listen(getConfig(env).baseSocketPort) // 监听socket端口
57 |
58 |
59 | console.log(`Server running on port ${PORT}`);
--------------------------------------------------------------------------------
/src/main/util/utils.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 工具集
4 | */
5 | const fs = require('fs')
6 | const path = require('path')
7 | const log = require('electron-log');
8 | const axios = require('axios').default;
9 |
10 | /**
11 | * 删除文件或文件夹
12 | * @param {string} dir 文件夹位置
13 | */
14 | function deleteDirSync(dir) {
15 | let files = fs.readdirSync(dir)
16 | // eslint-disable-next-line @typescript-eslint/prefer-for-of
17 | for (let i = 0;i < files.length;i++) {
18 | let newPath = path.join(dir, files[i]);
19 | let stat = fs.statSync(newPath)
20 | if (stat.isDirectory()) {
21 | // 如果是文件夹就递归下去
22 | deleteDirSync(newPath);
23 | } else {
24 | // 删除文件
25 | fs.unlinkSync(newPath);
26 | }
27 | }
28 | fs.rmdirSync(dir)// 如果文件夹是空的,删除自身
29 | }
30 |
31 |
32 | /**
33 | * 通过main进程发送事件给renderer进程,提示更新信息
34 | * @param {string} text 信息
35 | * @param {object} mainWindow 实例
36 | */
37 | function sendUpdateMessage(text, mainWindow) {
38 | log.info('enter sendUpdateMessage', text);
39 | mainWindow.webContents.send('message', text)
40 | }
41 |
42 | /**
43 | * 下载远程压缩包并写入指定文件
44 | * @param {string} 远程地址
45 | * @param {string} 文件名
46 | */
47 | function downloadFile(uri, filename) {
48 | const writer = fs.createWriteStream(filename)
49 | axios.get(uri, { responseType: 'stream' }).then(res => {
50 | res.data.pipe(writer);
51 | });
52 | return new Promise((resolve, reject) => {
53 | writer.on('finish', resolve);
54 | writer.on('error', reject);
55 | });
56 | }
57 |
58 | module.exports = {
59 | deleteDirSync,
60 | downloadFile,
61 | sendUpdateMessage
62 | }
--------------------------------------------------------------------------------
/src/render/layouts/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react'
2 | import { Layout } from 'antd'
3 | import { useHistory, useLocation, useStore } from 'umi'
4 | import Header from './header'
5 | import SideMenu from './menu'
6 | import store from '@utils/store/'
7 | import styles from './index.less'
8 |
9 | const { Content, Sider } = Layout
10 |
11 |
12 | const BasicLayout: React.FC = (props: any) => {
13 | const location = useLocation()
14 | const history = useHistory()
15 | const [collapsed, setCollapsed] = useState(false)
16 | const [shopCurrent, setShopCurrent] = useState(store.get(''))
17 |
18 | const clickCollapse = useCallback(() => {
19 | setCollapsed(!collapsed)
20 | }, [collapsed])
21 | const changeShop = useCallback(shop => {
22 | setShopCurrent(shop)
23 | }, [shopCurrent])
24 |
25 | const LayoutMain = (
26 |
27 |
32 |
33 | {/* 左侧菜单 */}
34 |
35 |
36 |
37 |
38 | {/* 右侧 content */}
39 |
40 | {props.children}
41 |
42 |
43 |
44 | )
45 |
46 | const LayoutLogin = props.children
47 |
48 | const LayoutDict: any = {
49 | '/User/login': LayoutLogin
50 | }
51 |
52 | return LayoutDict[location.pathname] ? LayoutDict[location.pathname] : LayoutMain
53 | }
54 |
55 | export default BasicLayout
56 |
--------------------------------------------------------------------------------
/src/render/utils/common.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * url参数查询
3 | * @param {string} [url=location.search] - url地址
4 | * @param {string} [query] - 查询参数
5 | * @param {boolean} [decode=true] - 返回的查询值是否需要解码
6 | * @returns {object|string}
7 | */
8 | export const getParams = ({
9 | url = window.location.search,
10 | query,
11 | decode = true
12 | }: { url?: string; query?: string; decode?: boolean } = {}) => {
13 | const paramStr = url.split('?')[1]
14 | const paramArr = (paramStr && paramStr.split('&')) || []
15 | const params: any = {}
16 | paramArr.forEach((param, i) => {
17 | const paramData = param.split('=')
18 | params[paramData[0]] = decode ? decodeURIComponent(paramData[1]) : paramData[1]
19 | })
20 | return query ? params[query] : params
21 | }
22 |
23 | /**
24 | * url添加参数
25 | * @param {string} url - 需要添加参数的url
26 | * @param {object} params - 添加的参数,参数是'key:value'形式
27 | * @param {boolean} [encode=false] - 返回的url是否需要编码
28 | * @returns {string}
29 | */
30 | export function addParams({
31 | url = '',
32 | params = {},
33 | encode = false
34 | }: {
35 | url?: string
36 | params: object
37 | encode?: boolean
38 | }) {
39 | if (!Object.keys(params).length) {
40 | return url
41 | }
42 | url = decodeURIComponent(url)
43 | const [hostStr, searchStr] = url.split('?')
44 | if (url.includes('?')) {
45 | const oldParams: any = {}
46 | searchStr.split('&').forEach(val => {
47 | const newVal = val.split('=')
48 | oldParams[newVal[0]] = newVal[1]
49 | })
50 | // 合并、去重参数
51 | params = { ...oldParams, ...params }
52 | }
53 | let [paramsStr, i] = ['', 1]
54 | for (const [key, val] of Object.entries(params)) {
55 | paramsStr += i > 1 ? `&${key}=${val}` : `${key}=${val}`
56 | i++
57 | }
58 | const baseUrl = `${hostStr}?${paramsStr}`
59 | return encode ? encodeURIComponent(baseUrl) : baseUrl
60 | }
61 |
--------------------------------------------------------------------------------
/src/render/.umirc.ts:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const isWeb = process.env.TARGET === 'web'
4 | const isDev = process.env.APP_ENV === 'development'
5 | const isProd = process.env.APP_ENV === 'production'
6 | const resolvePath = dir => path.join(__dirname, dir)
7 |
8 | const webpack_dev = config => {
9 | return (
10 | config
11 | // .devtool('eval-cheap-source-map') // 是否开启sourceMap
12 | .target('electron-renderer')
13 | )
14 | }
15 | const webpack_dev_web = config => {
16 | // web端运行localhost:9090 一般不用
17 | return config
18 | .devtool('eval-cheap-source-map')
19 | .node.set('fs', 'empty')
20 | .set('worker_threads', 'empty')
21 | .set('electron', 'empty')
22 | .set('electron-is-dev', 'empty')
23 | .set('electron-store', 'empty')
24 | .set('electron-updater', 'empty')
25 | }
26 | const webpack_prod = config => {
27 | return config.target('electron-renderer')
28 | }
29 |
30 | const chainWebpack = config => {
31 | return (
32 | config
33 | // .devtool('eval-cheap-source-map') // 是否开启sourceMap
34 | .target('electron-renderer')
35 | )
36 | }
37 |
38 | export default {
39 | chainWebpack, // 热加载需要
40 | // 是否编译 node_modules
41 | nodeModulesTransform: {
42 | // 打包加速
43 | type: 'none'
44 | },
45 | // 生成资源带 hash 尾缀
46 | // 开发模式下 umi 会忽略此选项,不然热重载会出问题(很贴心)
47 | hash: true,
48 | // url 格式
49 | history: {
50 | type: 'hash'
51 | },
52 | // script、link 标签资源引入路径
53 | publicPath: './',
54 | // antd 主题配置
55 | theme: {
56 | '@primary-color': '#A14EFF',
57 | '@link-color': '#A14EFF',
58 | '@font-family': '"futura-pt", sans-serif',
59 | '@line-height-base': '1.3',
60 | '@border-radius-base': '6px'
61 | },
62 | // 路径别名
63 | alias: {
64 | '@': resolvePath(''),
65 | '@components': resolvePath('components'),
66 | '@config': resolvePath('config'),
67 | '@utils': resolvePath('utils'),
68 | '@pages': resolvePath('pages')
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/server/src/middleware/cors.ts:
--------------------------------------------------------------------------------
1 | import * as Koa from 'koa';
2 | import * as URL from 'url';
3 |
4 | const cors = async (ctx: Koa.Context, next: Function) => {
5 | const origin = URL.parse(ctx.get('origin') || ctx.get('referer') || '');
6 | // 允许来自所有域名请求
7 | ctx.set("Access-Control-Allow-Origin", `${origin.protocol}//${origin.host}`);
8 | // ctx.set("Access-Control-Allow-Origin", `*`);
9 | // 这样就能只允许 http://localhost:8080 这个域名的请求了
10 | // ctx.set("Access-Control-Allow-Origin", "http://localhost:9090");
11 |
12 | // 设置所允许的HTTP请求方法
13 | ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
14 |
15 | // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段.
16 | ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
17 |
18 | // 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
19 |
20 | // Content-Type表示具体请求中的媒体类型信息
21 | ctx.set("Content-Type", "application/json;charset=utf-8");
22 |
23 | // 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。
24 | // 当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*";
25 | ctx.set("Access-Control-Allow-Credentials", 'true');
26 |
27 | // 该字段可选,用来指定本次预检请求的有效期,单位为秒。
28 | // 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
29 | // 下面的的设置只本次验证的有效时间,即在该时间段内服务端可以不用进行验证
30 | ctx.set("Access-Control-Max-Age", '300');
31 |
32 | /*
33 | CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
34 | Cache-Control、
35 | Content-Language、
36 | Content-Type、
37 | Expires、
38 | Last-Modified、
39 | Pragma。
40 | */
41 | // 需要获取其他字段时,使用Access-Control-Expose-Headers,
42 | // getResponseHeader('myData')可以返回我们所需的值
43 | //https://www.rails365.net/articles/cors-jin-jie-expose-headers-wu
44 | ctx.set("Access-Control-Expose-Headers", "myData");
45 |
46 | if (ctx.method !== 'OPTIONS') {
47 | // 如果请求类型为非预检请求,则进入下一个中间件(包括路由中间件等)
48 | await next();
49 | } else {
50 | // 当为预检时,直接返回204,代表空响应体
51 | ctx.body = '';
52 | ctx.status = 204;
53 | }
54 | }
55 |
56 | export default cors
--------------------------------------------------------------------------------
/src/main/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * electron 主进程入口
3 | */
4 | 'use strict'
5 |
6 | const { app, ipcMain } = require('electron')
7 | const AppMainWindow = require('./controls/AppMainWindow')
8 | const AppTray = require('./controls/AppTray')
9 | const Store = require('electron-store')
10 | const store = new Store()
11 | const os = require('os')
12 | const electronDev = require('electron-is-dev')
13 |
14 | // 测试用
15 | store.set('LOCAL_ELECTRON_STORE', 'STORE-MSG: WELCOME TO MY TPL')
16 | // 渲染进程保证node api可用,9.0版本已经默认开启
17 | app.allowRendererProcessReuse = true
18 | // win7部分系统白屏优化: 下关闭硬件加速
19 | const isWin7 = os.release().startsWith('6.1')
20 | if (isWin7) app.disableHardwareAcceleration()
21 |
22 | class MainApp {
23 | constructor() {
24 | this.mainWindow = null
25 | this.tray = null
26 | }
27 | init() {
28 | this.initAppLife()
29 | this.initIPC()
30 | }
31 |
32 | // 创建右键托盘
33 | createTray() {
34 | this.tray = new AppTray(this.mainWindow)
35 | }
36 |
37 | // 创建主进程窗口
38 | createMainWindow() {
39 | this.mainWindow = new AppMainWindow()
40 | this.createTray()
41 | electronDev || this.handleKeepSingleApp()
42 | }
43 |
44 | handleKeepSingleApp() {
45 | // 限制只可以打开一个应用
46 | const gotTheLock = app.requestSingleInstanceLock()
47 | console.log('gotTheLock', gotTheLock)
48 | if (!gotTheLock) {
49 | app.quit()
50 | } else {
51 | app.on('second-instance', (event, commandLine, workingDirectory) => {
52 | // 当运行第二个实例时,将会聚焦到mainWindow这个窗口
53 | if (this.mainWindow) {
54 | if (this.mainWindow.isMinimized()) this.mainWindow.restore()
55 | this.mainWindow.focus()
56 | this.mainWindow.show()
57 | }
58 | })
59 | }
60 | }
61 |
62 | // 生命周期
63 | initAppLife() {
64 | app.whenReady().then(() => {
65 | this.createMainWindow()
66 | })
67 |
68 | app.on('window-all-closed', () => {
69 | console.log('window-all-closed')
70 | // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,否则绝大部分应用及其菜单栏会保持激活。
71 | console.log('process.platform', process.platform)
72 | if (process.platform !== 'darwin') {
73 | console.log('quit')
74 | app.quit()
75 | }
76 | })
77 |
78 | app.on('before-quit', () => {
79 | console.log('before-quit')
80 | this.mainWindow.destoryMainWindow()
81 | })
82 | }
83 |
84 | // 可作为IPC通讯集合
85 | initIPC() {
86 | // 关闭启动loading层, 移除loading view视图
87 | ipcMain.on('stop-loading-main', () => {
88 | this.mainWindow.removeView()
89 | })
90 | }
91 | }
92 |
93 | // 初始化
94 | new MainApp().init()
95 |
--------------------------------------------------------------------------------
/scripts/main-build.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/camelcase */
2 | /**
3 | * 主进程 webpack 构建
4 | * electron 启动交给 electron-connect
5 | */
6 | const path = require('path');
7 | const child_process = require('child_process');
8 | const electron = require('electron');
9 | const webpack = require('webpack');
10 | const chalk = require('chalk');
11 | const ora = require('ora');
12 | const wait_on = require('wait-on');
13 | const argv = require('minimist')(process.argv.slice(2));
14 | const config_factory = require('./webpack.main');
15 | require('dotenv').config({ path: path.join(__dirname, '../src/render/.env') });
16 |
17 | const root = path.join(__dirname, '..');
18 | const main = process.env['npm_package_main'];
19 | const { env, port = process.env.PORT, watch } = argv;
20 | const spinner = ora('Electron webpack build...');
21 | const TAG = 'scripts/main-build.js';
22 | let watching = null;
23 | let child = null;
24 |
25 | const compiler = webpack(config_factory(env));
26 | compiler.hooks.afterCompile.tap(TAG, () => {
27 | spinner.stop();
28 | if (watch) { // 开发模式
29 | if (child) { process.kill(child.pid, 'SIGINT'); }
30 | child = child_process.spawn(
31 | electron,
32 | [path.join(root, main)],
33 | { stdio: 'inherit', }
34 | );
35 | }
36 | });
37 | compiler.hooks.beforeCompile.tap(TAG, () => {
38 | spinner.start();
39 | });
40 |
41 | function compileHandle(err, stats) {
42 | if (err) {
43 | // err 对象将只包含与webpack相关的问题,例如错误配置等
44 | console.log(TAG, chalk.red('💥 Electron webpack 相关报错'));
45 | console.log(err);
46 |
47 | // 关闭 wepback 监听
48 | watching && watching.close(() => console.log(TAG, 'Watching Ended.'));
49 | process.exit(1);
50 | } else if (stats.hasErrors()) {
51 | // webpack 编译报错
52 | const json = stats.toJson('errors-only');
53 | console.log(TAG, json.errors);
54 | console.log(TAG, chalk.red('💥 Electron 构建报错'));
55 | } else {
56 | console.log(TAG, chalk.green('Electron webpack 构建完成'));
57 | }
58 | }
59 |
60 | if (watch) { // 开发模式
61 | const opts = {
62 | resources: [`http://localhost:${port}`], // PORT === port
63 | interval: 900, // poll interval in ms, default 250ms
64 | log: false,
65 | };
66 |
67 | // 等待 webpack-dev-server 启动后启动 electron
68 | wait_on(opts, function (err) {
69 | if (err) {
70 | console.log(err);
71 | process.exit(1);
72 | }
73 | // once here, all resources are available
74 | watching = compiler.watch({
75 | ignored: /bundle\.js(\.map)?/,
76 | }, compileHandle);
77 | });
78 | } else { // 构建模式
79 | compiler.run(compileHandle);
80 | }
--------------------------------------------------------------------------------
/src/render/components/AutoUpdate/index.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * 组件控制app更新
3 | */
4 |
5 | import React, { useEffect } from 'react'
6 | import { Modal, message, Button } from 'antd'
7 | import app, { ipcRenderer as ipc, remote } from 'electron'
8 | import styles from './style.less'
9 | import { checkForPartUpdates } from '@/utils/autoUpdate/partUpdate'
10 | const { confirm } = Modal
11 | interface IProps {
12 | loading?: boolean
13 | }
14 |
15 | const appPkgVersion = remote.app.getVersion()
16 |
17 | const Update = (props: IProps) => {
18 | console.log('mainApp', app)
19 | console.log('ipc', ipc)
20 | // 模拟 自动更新
21 | useEffect(() => {
22 | // 检测app自动更新
23 | // if (window.isOpenAutoUpdate) {
24 | const container = document.getElementById('container') // TODO: 自动更新进度条
25 | // console.log('ipc', ipc)
26 | if (ipc) {
27 |
28 | ipc.on('message', (event, text) => {
29 | console.log('text', text)
30 | if (text.indexOf('ERROR') !== -1) {
31 | if (text.indexOf('ERR_CONNECTION_REFUSED') !== -1) {
32 | message.info('获取服务器地址异常')
33 | } else {
34 | message.info(text)
35 | }
36 | return
37 | }
38 |
39 | message.info(text)
40 | const msg = document.createElement('div')
41 | msg.innerText = text
42 | console.log('message', msg)
43 | if (msg && container) {
44 | container.appendChild(msg)
45 | }
46 | })
47 |
48 | // 因为如果安装文件过小的话,很快就下载完成,导致没能达到触发条件。
49 | ipc.on('downloadProgress', (event, { percent }) => {
50 | console.log(percent)
51 | })
52 |
53 | // 接收到主进程有新的版本已经下载完成,询问是否更新。
54 | ipc.on('isUpdateNow', () => {
55 | confirm({
56 | title: '确定要现在升级吗?',
57 | content: '更新内容:...',
58 | onOk() {
59 | ipc.send('updateNow')
60 | },
61 | onCancel() {
62 | console.log('update canceled')
63 | }
64 | })
65 | })
66 | }
67 | // }
68 | }, [window.isOpenAutoUpdate])
69 |
70 | const updateNewestVersion = () => {
71 | ipc && ipc.send('checkForUpdates')
72 | }
73 |
74 | const updateSpecifiedVersion = (v: string) => {
75 | const updateInfo = { // TODO: 接口返回
76 | isUpdate: true,
77 | // updateUrl: 'http://127.0.0.1:4002/download/'
78 | updateUrl: 'your server exe/dmg url' + v
79 | }
80 | ipc && updateInfo.isUpdate && ipc.send('checkForUpdates', updateInfo) // 如果强更才发起通知
81 | }
82 | return (
83 |
84 |
Version: {appPkgVersion}
85 | {window.isOpenAutoUpdate &&
}
86 | {!window.isOpenAutoUpdate &&
87 |
88 |
91 |
94 |
}
95 |
96 | )
97 | }
98 |
99 | export default Update
--------------------------------------------------------------------------------
/src/render/utils/autoUpdate/partUpdate.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * app更新验证
4 | */
5 | const app = require('@utils/polyfill')('electron')
6 | const isElectronDev = require('electron-is-dev')
7 | const axios = require('axios').default;
8 | import { message, Modal } from 'antd'
9 | import { ExclamationCircleOutlined } from '@ant-design/icons';
10 | const { confirm } = Modal;
11 | const { ipcRenderer: ipc, remote } = app
12 |
13 | let localYmlUrl = ``
14 | const remoteYmlURL = 'http://yourFileServer/app-update.yml' // 这里配置你的远程文件服务器
15 |
16 | if (!isElectronDev && process.platform === 'win32') { // win平台
17 | localYmlUrl = `./resources/app-update.yml`
18 | }
19 | if (!isElectronDev && process.platform === 'darwin') { // mac平台
20 | localYmlUrl = `/Applications/${remote.app.getName()}.app/Contents/Resources/resources/app-update.yml`
21 | }
22 |
23 | // 下载远程压缩包并写入指定文件
24 | function downloadFile(uri) {
25 | return new Promise((resolve, reject) => {
26 | axios.get(uri).then(res => {
27 | return resolve(res)
28 | }).catch(e => {
29 | console.error(e)
30 | message.info('获取远程版本失败')
31 | // eslint-disable-next-line prefer-promise-reject-errors
32 | return reject('获取远程版本失败')
33 | });
34 | });
35 | }
36 |
37 | function checkVersion(params) {
38 | return new Promise((resolve, reject) => {
39 | const currentVersion = remote.app.getVersion()
40 | // 获取最新版本号
41 | downloadFile(remoteYmlURL, localYmlUrl).then(res => {
42 | const remoteVersion = JSON.stringify(res.data).split('\\n')[0].split(' ')[1]
43 | const remoteVersionArr = remoteVersion.split('.')
44 | const currentVersionArr = currentVersion.split('.')
45 | // 0.1.1 Y和Z比较来开启增量更新 1.1.1 X比较来开启全量更新
46 | if (Number(remoteVersionArr[0]) > Number(currentVersionArr[0])) {
47 | // 开启全量更新
48 | return resolve('OPEN_ALL_UPDATE')
49 | } else if (Number(remoteVersionArr[2]) > Number(currentVersionArr[2]) || Number(remoteVersionArr[1]) > Number(currentVersionArr[1])) {
50 | // 开启增量更新
51 | return resolve('OPEN_PART_UPDATE')
52 | } else {
53 | console.log('无版本变动,不更新')
54 | }
55 | }).catch(e => {
56 | console.error(e)
57 | })
58 | })
59 | }
60 |
61 | /** 检查更新 */
62 | export async function checkForPartUpdates() {
63 | try {
64 | // check version 检查版本
65 | const res = await checkVersion()
66 | if (res && res === 'OPEN_PART_UPDATE') {
67 | // 增量更新
68 | console.log('OPEN_PART_UPDATE')
69 | confirm({
70 | title: '检测到更新',
71 | icon: ,
72 | content: (
73 |
76 | ),
77 | okText: '确认',
78 | cancelText: '取消',
79 | onOk() {
80 | ipc && ipc.send('checkForPartUpdates')
81 | message.info('请耐心等待几秒..')
82 | },
83 | onCancel() {
84 | console.log('Cancel');
85 | },
86 | });
87 | // partUpdates()
88 | }
89 | if (res && res === 'OPEN_ALL_UPDATE') {
90 | console.log('OPEN_ALL_UPDATE')
91 | // 全量更新
92 | }
93 | } catch (error) {
94 | console.error('checkVersionERROR', error)
95 | }
96 | }
--------------------------------------------------------------------------------
/docs/Copy.md:
--------------------------------------------------------------------------------
1 | # electron-react-umi-tpl change log
2 |
3 | ### 选中右键复制黏贴
4 |
5 | ```
6 | // app.ts
7 |
8 | // 注入右键复制黏贴
9 | import RightClickMenuFuc from './utils/rightClickMenuFuc'
10 |
11 | RightClickMenuFuc()
12 |
13 | ```
14 |
15 |
16 | ```
17 | // RightClickMenuFuc.ts
18 |
19 | /**
20 | * 右键复制黏贴
21 | */
22 | const remote = require('electron').remote;
23 | const Menu = remote.Menu;
24 | const MenuItem = remote.MenuItem;
25 |
26 |
27 | const RightClickMenuFuc = () => {
28 | /**
29 | * 判断点击区域可编辑
30 | * @param {*} e
31 | */
32 | function isEleEditable(e: any): any {
33 | if (!e) {
34 | return false;
35 | }
36 | // 为input标签或者contenteditable属性为true
37 | if (e.tagName == 'INPUT' || e.contentEditable == 'true') {
38 | return true;
39 | } else {
40 | // 递归查询父节点
41 | return isEleEditable(e.parentNode)
42 | }
43 | }
44 |
45 | const menu = new Menu();
46 | menu.append(new MenuItem({ label: '粘贴', role: 'paste' }));
47 |
48 | const menu2 = new Menu();
49 | menu2.append(new MenuItem({ label: '复制', role: 'copy' }));
50 | window.addEventListener('contextmenu', (e) => { // 上下文监听事件
51 | e.preventDefault();
52 | if (isEleEditable(e.target)) {
53 | menu.popup(remote.getCurrentWindow());
54 | } else {
55 | // 判断有文本选中
56 | let selectText = window.getSelection().toString();
57 | if (!!selectText) {
58 | menu2.popup(remote.getCurrentWindow());
59 | }
60 | }
61 |
62 | }, false)
63 | }
64 |
65 | export default RightClickMenuFuc
66 |
67 |
68 |
69 |
70 | ```
71 |
72 |
73 | # electron-react-umi-tpl change log
74 |
75 | ### Right click to copy and paste
76 |
77 | ```
78 | // app.ts
79 |
80 | // Inject right click to copy and paste
81 | import RightClickMenuFuc from'./utils/rightClickMenuFuc'
82 |
83 | RightClickMenuFuc()
84 |
85 | ```
86 |
87 |
88 | ```
89 | // RightClickMenuFuc.ts
90 |
91 | /**
92 | * Right click to copy and paste
93 | */
94 | const remote = require('electron').remote;
95 | const Menu = remote.Menu;
96 | const MenuItem = remote.MenuItem;
97 |
98 |
99 | const RightClickMenuFuc = () => {
100 | /**
101 | * Judging that the click area can be edited
102 | * @param {*} e
103 | */
104 | function isEleEditable(e: any): any {
105 | if (!e) {
106 | return false;
107 | }
108 | // is the input tag or the contenteditable attribute is true
109 | if (e.tagName =='INPUT' || e.contentEditable =='true') {
110 | return true;
111 | } else {
112 | // Query the parent node recursively
113 | return isEleEditable(e.parentNode)
114 | }
115 | }
116 |
117 | const menu = new Menu();
118 | menu.append(new MenuItem({ label:'paste', role:'paste' }));
119 |
120 | const menu2 = new Menu();
121 | menu2.append(new MenuItem({ label:'copy', role:'copy' }));
122 | window.addEventListener('contextmenu', (e) => {// Context listener event
123 | e.preventDefault();
124 | if (isEleEditable(e.target)) {
125 | menu.popup(remote.getCurrentWindow());
126 | } else {
127 | // Determine that there is text selected
128 | let selectText = window.getSelection().toString();
129 | if (!!selectText) {
130 | menu2.popup(remote.getCurrentWindow());
131 | }
132 | }
133 |
134 | }, false)
135 | }
136 |
137 | export default RightClickMenuFuc
138 |
139 |
140 |
141 |
142 | ```
--------------------------------------------------------------------------------
/src/main/loading.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | electron主进程的loading过渡
8 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/src/render/assets/style/common.less:
--------------------------------------------------------------------------------
1 | /**
2 | * 这里写全局公共样式
3 | */
4 | @commonColor: #a14eff;
5 | @commonBgColor: #eff0fa;
6 |
7 | // wrap
8 | .commonWrap {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | .layout-content {
14 | padding: 0 0 24px 0;
15 | margin: 0px;
16 | height: 100%;
17 | }
18 |
19 | .layout-content-withTab {
20 | padding: 24px 0;
21 | margin: 0px;
22 | height: 100%;
23 | }
24 |
25 | .ant-layout {
26 | background: @commonBgColor !important;
27 | }
28 |
29 | // settlement
30 | .settlement {
31 | background: @commonBgColor;
32 | }
33 |
34 | // other
35 | .mid {
36 | display: flex;
37 | justify-content: center;
38 | align-items: center;
39 | height: 100%;
40 | }
41 |
42 | .status2 {
43 | padding: 5px 10px;
44 | background-color: #1890ff;
45 | border-radius: 5px;
46 | color: #fff;
47 | }
48 |
49 | .bsp {
50 | background-color: #1890ff;
51 | color: #fff;
52 | padding: 4px;
53 | border-radius: 3px;
54 | cursor: pointer;
55 | }
56 |
57 | .totalNumber {
58 | background: #e6f2ff;
59 | margin-top: 20px;
60 | border: 1px solid #b3e4ff;
61 | border-radius: 5px;
62 | height: 50px;
63 | line-height: 50px;
64 | padding-left: 20px;
65 | }
66 |
67 | .mRight10 {
68 | margin-right: 10px;
69 | }
70 |
71 | .mRight5 {
72 | margin-right: 5px;
73 | }
74 |
75 | .mTop16 {
76 | margin-top: 16px;
77 | }
78 |
79 | .mBottom16 {
80 | margin-bottom: 16px;
81 | }
82 |
83 | .alignCenter {
84 | text-align: center;
85 | }
86 |
87 | .mRight16 {
88 | margin-right: 16px;
89 | }
90 |
91 | .mLeft16 {
92 | margin-left: 16px;
93 | }
94 |
95 | // 单行省略号
96 | .ellipsis {
97 | overflow: hidden;
98 | text-overflow: ellipsis;
99 | white-space: nowrap;
100 | }
101 |
102 | .wrapStyle {
103 | padding: 16px 16px 10px;
104 | background-color: #ffffff;
105 | border-radius: 10px;
106 | }
107 |
108 | // 组件通用
109 | .commonHandleBack {
110 | display: flex;
111 | align-items: center;
112 | justify-content: center;
113 | margin-top: 30px;
114 |
115 | Button:nth-of-type(1) {
116 | margin-right: 20px;
117 | }
118 |
119 | Button {
120 | width: 110px;
121 | height: 40px;
122 | }
123 | }
124 |
125 | .bg-white {
126 | background-color: white;
127 | }
128 |
129 | .cursor-pointer {
130 | cursor: pointer;
131 | }
132 |
133 | // 带左侧标识的 title
134 | @color-pirmary: #a14eff;
135 |
136 | .note-title {
137 | position: relative;
138 | padding-left: 12px;
139 | line-height: 44px;
140 | font-size: 16px;
141 | font-weight: bold;
142 | color: #474245;
143 |
144 | &::before {
145 | content: '';
146 | position: absolute;
147 | top: 50%;
148 | left: 0;
149 | width: 2px;
150 | height: 14px;
151 | background-color: @color-pirmary;
152 | transform: translateY(-50%);
153 | }
154 | }
155 |
156 | .note-title-border {
157 | .note-title;
158 |
159 | &::after {
160 | content: '';
161 | position: absolute;
162 | bottom: 0;
163 | left: 0;
164 | height: 1px;
165 | width: 100%;
166 | background-color: #f3f3f3;
167 | }
168 | }
169 |
170 | .optionBtnList {
171 | width: 100%;
172 | display: flex;
173 | justify-content: flex-start;
174 | align-items: center;
175 | margin: 10px 0;
176 |
177 | & > li > button {
178 | margin-right: 15px;
179 | }
180 | }
181 |
182 | .commonWrap{
183 | .wrapStyle:nth-of-type(2){
184 | min-height: 480px;
185 | }
186 | }
187 |
188 | .detailContent{
189 | padding: 0 20px 20px 20px;
190 | background-color: #fff;
191 | height: auto;
192 | }
193 |
--------------------------------------------------------------------------------
/server/src/utils/redis/redisOperate.ts:
--------------------------------------------------------------------------------
1 | import redisConnect from './redisConnection'
2 | import * as ioredis from 'ioredis'
3 | import getConfig from '../../config/index';
4 |
5 | export interface redisTool {
6 | setString(key:string,value:any):Promise
7 | getString(key:any):Promise
8 | delString(key:string):Promise
9 | getDbSize():Promise
10 | connToRedis():Promise
11 | }
12 |
13 | interface redisConfig {
14 | port:number,
15 | host:string,
16 | password?:string
17 | db?:number
18 | family?:number
19 | }
20 |
21 | const env = process.env.NODE_ENV;
22 | let redisConfig:redisConfig = {
23 | port: getConfig(env).port_redis,
24 | host: getConfig(env).host_redis
25 | }
26 |
27 | class RedisTool implements redisTool {
28 | redis:ioredis.Redis
29 | config:redisConfig
30 | constructor(opt?:any){
31 | this.redis = null;
32 | if(opt){
33 | this.config = Object.assign(redisConfig,opt)
34 | }else{
35 | this.config = redisConfig
36 | }
37 | // this.connToRedis()
38 | this.connToRedis().then(res=>{
39 | if(res){
40 | console.log('redis connet success')
41 | }
42 | }).catch(e=>{
43 | console.error('The Redis Can not Connect:'+e)
44 | })
45 | }
46 |
47 | /**连接redis */
48 | connToRedis(){
49 | return new Promise((resolve, reject) => {
50 | if(this.redis) {
51 | resolve(true) //已创建
52 | } else {
53 | redisConnect(this.config).then((resp:ioredis.Redis) => {
54 | this.redis = resp
55 | resolve(true) }
56 | ).catch(err => { reject(err) })
57 | }
58 | })
59 | }
60 |
61 | /**存储string类型的key-value */
62 | async setString(key:string,value:any){
63 | let val:string = typeof(value)!=='string'?JSON.stringify(value):value;
64 | let k:string = typeof(value)!=='string'?JSON.stringify(key):key;
65 | try {
66 | const res = await this.redis.set(k, val);
67 | return res;
68 | }
69 | catch (e) {
70 | console.error(e);
71 | }
72 | }
73 |
74 | /**获取string类型的key-value */
75 | async getString(key:any){
76 | let id:string = typeof(key)!=='string'?JSON.stringify(key):key;
77 | try{
78 | const res = await this.redis.get(id);
79 | return res
80 | }catch(e){
81 | console.error(e);
82 | return null
83 | }
84 | }
85 |
86 | /**删除string类型的key-value */
87 | async delString(key:string){
88 | let id:string = typeof(key)!=='string'?JSON.stringify(key):key;
89 | try{
90 | const res = await this.redis.del(id);
91 | return res
92 | }catch(e){
93 | console.error(e);
94 | return null
95 | }
96 | }
97 |
98 | /**获取当前数据库key的数量 */
99 | async getDbSize(){
100 | try{
101 | const res = await this.redis.dbsize();
102 | return res
103 | }catch(e){
104 | console.error(e);
105 | return null
106 | }
107 | }
108 |
109 | }
110 |
111 | export const default_redis = new RedisTool();
112 | export const redis_db1 = new RedisTool({db:1})
113 | // export const redis_db2 = new RedisTool({db:2})
114 | // export const redis_db3 = new RedisTool({db:3})
115 | // export const redis_db4 = new RedisTool({db:4})
116 |
117 | export default default_redis
--------------------------------------------------------------------------------
/src/main/controls/AppMainWindow.js:
--------------------------------------------------------------------------------
1 | /**
2 | * electron窗口初始化
3 | */
4 | 'use strict'
5 |
6 | const { BrowserView, BrowserWindow } = require('electron')
7 | const isDevEnv = require('electron-is-dev')
8 | const path = require('path')
9 | const {
10 | default: installExtension,
11 | REACT_DEVELOPER_TOOLS,
12 | REDUX_DEVTOOLS
13 | } = require('electron-devtools-installer')
14 | const AppAutoUpdater = require('../controls/AppAutoUpdater')
15 | const { DEV_ADDRESS } = require('../config/config')
16 | const log = require('electron-log')
17 |
18 | module.exports = class AppMainWindow extends BrowserWindow {
19 | constructor() {
20 | const config = {
21 | width: 1010,
22 | height: 716,
23 | minWidth: 800,
24 | minHeight: 600,
25 | autoHideMenuBar: false,
26 | fullscreen: false,
27 | webPreferences: {
28 | nodeIntegration: true,
29 | webviewTag: true,
30 | preload: path.join(__dirname, 'preload.js'),
31 | contextIsolation: false, // required flag
32 | enableRemoteModule: true
33 | }
34 | }
35 |
36 | super(config)
37 | this.mainWindow = this
38 | this.browserView = null
39 | this.initMainWindow()
40 | this.initEvents()
41 | }
42 |
43 | initMainWindow() {
44 | // 必须在主进程塞入文件前配置 loading
45 | this.windowLoading()
46 | this.loadURL(
47 | isDevEnv ? DEV_ADDRESS : `file://${path.join(__dirname, '../render/dist/index.html')}`
48 | )
49 | if (isDevEnv) {
50 | // 打开开发者工具
51 | this.mainWindow.openDevTools()
52 | }
53 | // 异步安装插件
54 | installExtension(REACT_DEVELOPER_TOOLS)
55 | .then(name => console.log(`Added Extension REDUX_DEVTOOLS: ${name}`))
56 | .catch(err => console.log('An error occurred: ', err))
57 | installExtension(REDUX_DEVTOOLS)
58 | .then(name => console.log(`Added Extension REDUX_DEVTOOLS: ${name}`))
59 | .catch(err => console.log('An error occurred: ', err))
60 | }
61 |
62 | // 主进程加载时的loading过渡,避免白屏
63 | windowLoading() {
64 | console.log(`file://${path.join(__dirname, 'loading.html')}`)
65 | this.browserView = new BrowserView()
66 | this.mainWindow.setBrowserView(this.browserView)
67 | this.browserView.setBounds({ x: 0, y: 0, width: 1010, height: 716 })
68 | this.browserView.webContents.loadURL(`file://${path.join(__dirname, 'loading.html')}`)
69 | this.browserView.webContents.on('dom-ready', () => {
70 | this.mainWindow.show()
71 | })
72 | }
73 |
74 | initEvents() {
75 | // 窗口关闭的监听
76 | this.mainWindow.on('closed', () => {
77 | console.log('closed')
78 | this.mainWindow = null
79 | })
80 |
81 | this.mainWindow.on('close', e => {
82 | console.log('close windows')
83 | // 兼容不同平台关闭
84 | if (process.platform === 'win32' && this.mainWindow['hide']) {
85 | this.mainWindow.hide()
86 | e.preventDefault()
87 | }
88 | })
89 | this.mainWindow.once('ready-to-show', () => {
90 | // 加入loading.html后, 此处updateHandle无效
91 | // 检查自动更新
92 | // log.info('enter ready-to-show')
93 | })
94 |
95 | this.mainWindow.once('show', () => {
96 | log.info('enter show')
97 | // 检查自动更新
98 | AppAutoUpdater.updateHandle(this.mainWindow)
99 | })
100 |
101 | // 隐藏默认菜单
102 | this.mainWindow.webContents.once('did-finish-load', () => {
103 | this.mainWindow.setMenuBarVisibility(false)
104 | })
105 | }
106 |
107 | destoryMainWindow() {
108 | this.mainWindow = null
109 | }
110 |
111 | removeView() {
112 | this.mainWindow.removeBrowserView(this.browserView)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-react-app",
3 | "description": "{{description}}",
4 | "author": "app",
5 | "version": "0.0.1",
6 | "scripts": {
7 | "start": "npm run dev",
8 | "dev": "concurrently -n=umi,electron -c=blue,green -p=[{name}] \"cross-env APP_ROOT=src/render umi dev\" \"npm run electron:dev\"",
9 | "electron:dev": "node scripts/main-build --env=development --watch",
10 | "---- webpack 构建 bundle.js electron-webpack ----": "---- webpack 构建 bundle.js ----",
11 | "electron:build": "node scripts/main-build --env=production",
12 | "---- 辅助命令 ----": "---- DIST=dist 指向根目录 dist ----",
13 | "build:umi": "cross-env DIST=dist APP_ENV=test APP_ROOT=src/render umi build",
14 | "---- close flex routes ----": "---- 开发关闭动态路由,可选 ----",
15 | "flex": "concurrently -n=umi,electron -c=blue,green \"cross-env closeFlexRoute=true APP_ENV=test APP_ROOT=src/render umi dev\" \"npm run electron:dev\"",
16 | "---- 自动根据操作系统平台构建包 electron进程环境变量:APP_ENV=production electron-package ----": "---- 自动根据操作系统平台构建包 ----",
17 | "pack": "npm run electron:build && cross-env APP_ENV=production APP_ROOT=src/render umi build && electron-builder",
18 | "---- 不同接口环境 APP_ENV=test ----": "---- 自动根据操作系统平台构建包 ----",
19 | "pack-env-test": "npm run electron:build && cross-env APP_ENV=test APP_ROOT=src/render umi build && electron-builder",
20 | "---- 构建三个操作系统平台包 electron-package-all ----": "---- 构建三个操作系统平台包 ----",
21 | "pack-all": "npm run electron:build && cross-env APP_ENV=production APP_ROOT=src/render umi build && electron-builder -mwl",
22 | "---- 构建 mac 操作系统平台包 electron-package-mac ----": "---- 构建 mac 操作系统平台包 ----",
23 | "pack-mac": "npm run electron:build && cross-env APP_ENV=production APP_ROOT=src/render umi build && electron-builder -m",
24 | "---- 构建 windows 操作系统平台包 electron-package-windows ----": "---- 构建 windows 操作系统平台包 ----",
25 | "pack-windows": "npm run electron:build && cross-env APP_ENV=production APP_ROOT=src/render umi build && electron-builder -w"
26 | },
27 | "main": "src/main/bundle.js",
28 | "build": {
29 | "extends": null,
30 | "productName": "APP",
31 | "appId": "yourAppId",
32 | "directories": {
33 | "output": "release/${version}_setup"
34 | },
35 | "files": [
36 | "!node_modules/**/*",
37 | "src/main/**/*",
38 | "src/main/public",
39 | "src/render/dist/**/*"
40 | ],
41 | "mac": {
42 | "target": [
43 | "dmg",
44 | "zip"
45 | ]
46 | },
47 | "win": {
48 | "target": [
49 | {
50 | "target": "nsis",
51 | "arch": [
52 | "x64",
53 | "ia32"
54 | ]
55 | }
56 | ],
57 | "artifactName": "${productName}_setup_${version}.${ext}",
58 | "icon": "src/main/public/icon.ico"
59 | },
60 | "publish": [
61 | {
62 | "provider": "generic",
63 | "url": ""
64 | }
65 | ],
66 | "nsis": {
67 | "oneClick": false,
68 | "perMachine": false,
69 | "allowToChangeInstallationDirectory": true,
70 | "deleteAppDataOnUninstall": false
71 | }
72 | },
73 | "dependencies": {
74 | "@ant-design/icons": "^4.0.6",
75 | "adm-zip": "^0.4.14",
76 | "antd": "^4.2.2",
77 | "axios": "^0.19.2",
78 | "classnames": "^2.2.6",
79 | "dayjs": "^1.8.25",
80 | "electron-is-dev": "^1.2.0",
81 | "electron-log": "^4.1.1",
82 | "js-cookie": "^2.2.1",
83 | "react": "^16.12.0",
84 | "react-dom": "^16.12.0",
85 | "socket.io-client": "^2.3.0",
86 | "umi": "^3.2.17"
87 | },
88 | "devDependencies": {
89 | "@types/classnames": "^2.2.10",
90 | "@types/js-cookie": "^2.2.6",
91 | "@typescript-eslint/eslint-plugin": "^2.27.0",
92 | "@typescript-eslint/parser": "^2.27.0",
93 | "@umijs/fabric": "^2.0.8",
94 | "@umijs/preset-react": "1.x",
95 | "@umijs/test": "^3.1.1",
96 | "babel-eslint": "^10.1.0",
97 | "babel-loader": "^8.2.2",
98 | "chalk": "^4.0.0",
99 | "concurrently": "^5.1.0",
100 | "cross-env": "^7.0.2",
101 | "dotenv": "^8.2.0",
102 | "electron": "^12.2.3",
103 | "electron-builder": "^22.5.1",
104 | "electron-devtools-installer": "^3.0.0",
105 | "electron-is-dev": "^1.2.0",
106 | "electron-log": "^4.1.1",
107 | "electron-store": "^5.1.1",
108 | "electron-updater": "^4.2.5",
109 | "eslint-plugin-html": "^6.0.2",
110 | "eslint-plugin-react": "^7.19.0",
111 | "minimist": "^1.2.5",
112 | "mockjs": "^1.1.0",
113 | "node-loader": "^0.6.0",
114 | "ora": "^4.0.4",
115 | "prettier": "^1.19.1",
116 | "wait-on": "^4.0.2",
117 | "webpack": "^5.28.0"
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/docs/PART_UPDATE.md:
--------------------------------------------------------------------------------
1 | ## 增量更新说明文档
2 |
3 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/PART_UPDATE_EN.md)
4 |
5 |
6 |
7 | #### 提前准备
8 |
9 |
10 | 1. 准备本地或者远程服务器或者远程静态文件url
11 | ```
12 | npm i -g http-server
13 | cd yourFileFolder // 进入任意文件夹
14 | http-server -p 4000 // 快速开启本地服务,用于存储更新文件
15 | ```
16 | 2. 配置和打包,拿到更新文件内容并压缩
17 | ```
18 | // package.json
19 | // 关闭asar模式
20 | "asar": false,
21 | // 打包
22 | npm run pack-windows
23 | // 进入打好的windows包
24 | cd release\0.x.x_setup\win-unpacked\resources
25 | // 压缩app文件夹 => app.zip, 拷贝app-update.yml和app.zip
26 |
27 | ```
28 |
29 | 3. app.zip app-update.yml 传到服务器
30 |
31 | 3. 本地流程,启动客户端,点击增量更新
32 |
33 | ```
34 | // 入口
35 | // src\render\components\AutoUpdate\index.tsx
36 |
39 |
40 | ```
41 |
42 | ```
43 | // 本地检查与服务器的version版本比较
44 | // 如果找到新版本,则向主进程通信,通知checkForPartUpdates开始更新
45 |
46 | // src\render\utils\autoUpdate\partUpdate.js
47 | /** 检查更新 */
48 | export async function checkForPartUpdates() {
49 | try {
50 | // check version 检查版本
51 | const res = await checkVersion()
52 | if (res && res === 'OPEN_PART_UPDATE') {
53 | // 增量更新
54 | console.log('OPEN_PART_UPDATE')
55 | confirm({
56 | title: '检测到更新',
57 | icon: ,
58 | content: (
59 |
62 | ),
63 | okText: '确认',
64 | cancelText: '取消',
65 | onOk() {
66 | ipc && ipc.send('checkForPartUpdates')
67 | message.info('请耐心等待几秒..')
68 | },
69 | onCancel() {
70 | console.log('Cancel');
71 | },
72 | });
73 | // partUpdates()
74 | }
75 | if (res && res === 'OPEN_ALL_UPDATE') {
76 | console.log('OPEN_ALL_UPDATE')
77 | // 全量更新
78 | }
79 | } catch (error) {
80 | console.error('checkVersionERROR', error)
81 | }
82 | }
83 |
84 | function checkVersion(params) {
85 | return new Promise((resolve, reject) => {
86 | const currentVersion = remote.app.getVersion()
87 | // 获取最新版本号
88 | downloadFile(remoteYmlURL, localYmlUrl).then(res => {
89 | const remoteVersion = JSON.stringify(res.data).split('\\n')[0].split(' ')[1]
90 | const remoteVersionArr = remoteVersion.split('.')
91 | const currentVersionArr = currentVersion.split('.')
92 | // 0.1.1 Y和Z比较来开启增量更新 1.1.1 X比较来开启全量更新
93 | if (Number(remoteVersionArr[0]) > Number(currentVersionArr[0])) {
94 | // 开启全量更新
95 | return resolve('OPEN_ALL_UPDATE')
96 | } else if (Number(remoteVersionArr[2]) > Number(currentVersionArr[2]) || Number(remoteVersionArr[1]) > Number(currentVersionArr[1])) {
97 | // 开启增量更新
98 | return resolve('OPEN_PART_UPDATE')
99 | } else {
100 | console.log('无版本变动,不更新')
101 | }
102 | }).catch(e => {
103 | console.error(e)
104 | })
105 | })
106 | }
107 |
108 | ```
109 |
110 |
111 | ```
112 | // src\main\controls\AppAutoUpdater.js
113 | // 下载服务器文件包
114 | // 本地解压和备份,替换,重启客户端即可完成更新
115 |
116 | // 增量更新
117 | ipcMain.on('checkForPartUpdates', async (e, msg) => {
118 | console.log('checkForPartUpdates', msg)
119 | // if (isElectronDev) {
120 | // console.log('开发模式不支持')
121 | // return
122 | // }
123 | try {
124 | if (fs.existsSync(`${localresourcePath}.back`)) { // 删除旧备份
125 | deleteDirSync(`${localresourcePath}.back`)
126 | }
127 | if (fs.existsSync(localresourcePath)) {
128 | fs.renameSync(localresourcePath, `${localresourcePath}.back`); // 备份目录
129 | }
130 | await downloadFile(remoteAppURL, appZipPath)
131 | console.log('app.asar.unpacked.zip 下载完成')
132 | fs.mkdirSync(localresourcePath) // 创建app来解压用
133 | try {
134 | // 同步解压缩
135 | const unzip = new AdmZip(appZipPath)
136 | unzip.extractAllTo(resourcePath, true)
137 | console.log('app.asar.unpacked.zip 解压缩完成')
138 | console.log('更新完成,正在重启...')
139 | mainWindow.webContents.send('partUpdateReady')
140 | setTimeout(() => {
141 | app.relaunch(); // 重启
142 | app.exit(0);
143 | }, 1800);
144 | } catch (error) {
145 | console.error(`extractAllToERROR: ${error}`);
146 | }
147 | // 更新窗口
148 | // BrowserWindow.getAllWindows().forEach((win: any) => {
149 | // win.webContents.reload()
150 | // // remote.app.relaunch(); // 重启
151 | // // remote.app.exit(0);
152 | // })
153 | console.log('webContents reload完成')
154 | } catch (error) {
155 | console.error(`checkForPartUpdatesERROR`, error)
156 | if (fs.existsSync(`${localresourcePath}.back`)) {
157 | fs.renameSync(`${localresourcePath}.back`, localresourcePath);
158 | }
159 | }
160 | })
161 |
162 | ```
163 |
164 |
--------------------------------------------------------------------------------
/docs/PART_UPDATE_EN.md:
--------------------------------------------------------------------------------
1 | ## Incremental update documentation
2 | #### Prepare ahead of time
3 | 1. Prepare local or remote server or remote static file URL
4 | ```
5 | npm i -g http-server
6 | // enter any folder of filer folder
7 | Http-server - P 4000 // quickly start the local service to store update files
8 | ```
9 | 2. Configure and package, get the updated file content and compress it
10 | ```
11 | // package.json
12 | //Turn off ASAR mode
13 | "asar": false,
14 | //Packing
15 | npm run pack
16 | //Enter the windows package
17 | cd release\0.x.x_ setup\win-unpacked\resources
18 | //Compressed app folder = > app.zip , copy app- update.yml and app.zip
19 | ```
20 | Three app.zip Pass to server
21 | 3. Local process, start the client and click incremental update
22 | ```
23 | //Entrance
24 | // src\render\components\AutoUpdate\ index.tsx
25 |
28 | ```
29 |
30 | ```
31 | //Version version comparison between local check and server
32 | //If a new version is found, it communicates with the main process and notifies checkforpartupdates to start updating
33 | // src\render\utils\autoUpdate\ partUpdate.js
34 | /**Check for updates*/
35 | export async function checkForPartUpdates() {
36 | try {
37 | //Check version
38 | const res = await checkVersion()
39 | if (res && res === 'OPEN_ PART_ UPDATE') {
40 | //Incremental update
41 | console.log ('OPEN_ PART_ UPDATE')
42 | confirm({
43 | Title: 'update detected',
44 | icon: ,
45 | content: (
46 |
49 | )
50 | Oktext: 'confirm',
51 | Canceltext: 'Cancel',
52 | onOk() {
53 | ipc && ipc.send ('checkForPartUpdates')
54 | message.info ('please wait a few seconds..')
55 | }
56 | onCancel() {
57 | console.log ('Cancel');
58 | }
59 | };
60 | // partUpdates()
61 | }
62 | if (res && res === 'OPEN_ ALL_ UPDATE') {
63 | console.log ('OPEN_ ALL_ UPDATE')
64 | //Full update
65 | }
66 | } catch (error) {
67 | console.error ('checkVersionERROR', error)
68 | }
69 | }
70 | function checkVersion(params) {
71 | return new Promise((resolve, reject) => {
72 | const currentVersion = remote.app.getVersion ()
73 | //Get the latest version number
74 | downloadFile(remoteYmlURL, localYmlUrl).then(res => {
75 | const remoteVersion = JSON.stringify ( res.data ).split('\\n')[0].split(' ')[1]
76 | const remoteVersionArr = remoteVersion.split ('.')
77 | const currentVersionArr = currentVersion.split ('.')
78 | //0.1.1 y and Z comparison to turn on incremental update 1.1.1 x comparison to turn on full update
79 | if (Number(remoteVersionArr[0]) > Number(currentVersionArr[0])) {
80 | //Open full update
81 | return resolve('OPEN_ ALL_ UPDATE')
82 | } else if (Number(remoteVersionArr[2]) > Number(currentVersionArr[2]) || Number(remoteVersionArr[1]) > Number(currentVersionArr[1])) {
83 | //Turn on incremental update
84 | return resolve('OPEN_ PART_ UPDATE')
85 | } else {
86 | console.log ('No version change, no update ')
87 | }
88 | }).catch(e => {
89 | console.error (E)
90 | }
91 | }
92 | }
93 | ```
94 |
95 |
96 |
97 | ```
98 | // src\main\controls\ AppAutoUpdater.js
99 | //Download Server package
100 | //Local decompression and backup, replacement, restart the client to complete the update
101 | //Incremental update
102 | ipcMain.on ('checkForPartUpdates', async (e, msg) => {
103 | console.log ('checkForPartUpdates', msg)
104 | // if (isElectronDev) {
105 | // console.log ('development mode not supported ')
106 | // return
107 | // }
108 | try {
109 | if ( fs.existsSync ('${localresourcepath}. Back') {// delete the old backup
110 | deleteDirSync(`${localresourcePath}.back`)
111 | }
112 | if ( fs.existsSync (localresourcePath)) {
113 | fs.renameSync (localresourcepath, '${localresourcepath}. Back'); // backup directory
114 | }
115 | await downloadFile(remoteAppURL, appZipPath)
116 | console.log () app.asar.unpacked . zip download complete ')
117 | fs.mkdirSync (localresourcepath) // create an app to unzip the
118 | try {
119 | //Synchronous decompression
120 | const unzip = new AdmZip(appZipPath)
121 | unzip.extractAllTo (resourcePath, true)
122 | console.log () app.asar.unpacked . zip decompression complete ')
123 | console.log ('update complete, restarting...')
124 | mainWindow.webContents.send ('partUpdateReady')
125 | setTimeout(() => {
126 | app.relaunch (); // restart
127 | app.exit (0);
128 | }, 1800);
129 | } catch (error) {
130 | console.error (`extractAllToERROR: ${error}`);
131 | }
132 | //Update window
133 | // BrowserWindow.getAllWindows ().forEach((win: any) => {
134 | // win.webContents.reload ()
135 | // // remote.app.relaunch (); // restart
136 | // // remote.app.exit (0);
137 | // })
138 | console.log ('webcontents reload completed ')
139 | } catch (error) {
140 | console.error (`checkForPartUpdatesERROR`, error)
141 | if ( fs.existsSync (`${localresourcePath}.back`)) {
142 | fs.renameSync (`${localresourcePath}.back`, localresourcePath);
143 | }
144 | }
145 | }
146 | ```
--------------------------------------------------------------------------------
/src/main/controls/AppAutoUpdater.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 自动更新
3 | * https://www.electron.build/auto-update
4 | */
5 |
6 | const { autoUpdater } = require('electron-updater')
7 | const { ipcMain, app } = require('electron')
8 | const log = require('electron-log');
9 | autoUpdater.logger = require("electron-log")
10 | autoUpdater.logger.transports.file.level = "info"
11 | const isElectronDev = require('electron-is-dev')
12 | const path = require('path')
13 | const AdmZip = require('adm-zip')
14 | const fs = require('fs')
15 | const electronMainUtils = require('../util/utils')
16 |
17 |
18 | let localresourcePath = '' // 本地resource/app路径
19 | let resourcePath = '' // 本地resource 路径
20 | let appZipPath = '' // app压缩包位置
21 | let yourFileServer = '' // 文件服务器
22 | const remoteAppURL = `https://${yourFileServer}/app.zip` // yourFileServer: 你的远程文件服务器
23 | // windows 本地测试 admin:改为你的用户名
24 | if (isElectronDev && process.platform === 'win32') {
25 | // win 本地安装包路径
26 | // YOURAPP: app名称,一般取自package.json
27 | localresourcePath = `C:/Users/admin/AppData/Local/Programs/YOURAPP/resources/app`
28 | resourcePath = `C:/Users/admin/AppData/Local/Programs/YOURAPP/resources`
29 | appZipPath = `C:/Users/admin/AppData/Local/Programs/YOURAPP/resources/app.zip`
30 | }
31 | if (!isElectronDev && process.platform === 'win32') { // win平台
32 | localresourcePath = `./resources/app`
33 | resourcePath = `./resources`
34 | appZipPath = `./resources/app.zip`
35 | }
36 | if (!isElectronDev && process.platform === 'darwin') { // mac平台
37 | localresourcePath = `/Applications/YOURAPP.app/Contents/Resources/app`
38 | resourcePath = `/Applications/YOURAPP.app/Contents/Resources`
39 | appZipPath = `/Applications/YOURAPP.app/Contents/Resources/app.zip`
40 | }
41 |
42 | /**
43 | * autoUpdater - 更新操作
44 | * @param {object} mainWindow 实例
45 | */
46 | function updateHandle(mainWindow) {
47 | log.info('enter updateHandle')
48 | log.info(`mainWindow: ${mainWindow}`)
49 | autoUpdater.on('error', function (error) {
50 | electronMainUtils.sendUpdateMessage(`ERROR: 检查更新出错:${error}`, mainWindow)
51 | });
52 | autoUpdater.on('checking-for-update', function () {
53 | log.info(`enter checking-for-update`)
54 | log.info(`正在检查更新…`)
55 | });
56 | autoUpdater.on('update-available', function (UpdateInfo) {
57 | log.info('UpdateInfo', UpdateInfo)
58 | electronMainUtils.sendUpdateMessage('检测到新版本,正在下载…', mainWindow)
59 | });
60 | autoUpdater.on('update-not-available', function (info) {
61 | log.info(`现在使用的已经是最新版本`)
62 | electronMainUtils.sendUpdateMessage('现在使用的已经是最新版本', mainWindow)
63 | });
64 |
65 | /**
66 | * 更新下载进度事件
67 | */
68 | autoUpdater.on('download-progress', function (progressObj) {
69 | mainWindow.webContents.send('downloadProgress', progressObj)
70 | });
71 |
72 | /**
73 | * 下载更新包完成
74 | */
75 | autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
76 | // 渲染层回复立即更新,则自动退出当前程序,然后进行程序更新
77 | ipcMain.on('updateNow', (e, arg) => {
78 | log.info('开始更新');
79 | autoUpdater.quitAndInstall();
80 | });
81 | // 询问渲染层是否立即更新
82 | mainWindow.webContents.send('isUpdateNow')
83 | });
84 |
85 | /**
86 | * 增量更新
87 | */
88 | ipcMain.on('checkForPartUpdates', async (e, msg) => {
89 | try {
90 | if (fs.existsSync(`${localresourcePath}.back`)) { // 删除旧备份
91 | electronMainUtils.deleteDirSync(`${localresourcePath}.back`)
92 | }
93 | if (fs.existsSync(localresourcePath)) {
94 | fs.renameSync(localresourcePath, `${localresourcePath}.back`); // 备份目录
95 | }
96 | await electronMainUtils.downloadFile(remoteAppURL, appZipPath)
97 | if (!fs.existsSync(`${localresourcePath}`)) { // 删除旧备份
98 | fs.mkdirSync(localresourcePath) // 创建app来解压用
99 | }
100 | try {
101 | // 同步解压缩
102 | const unzip = new AdmZip(appZipPath)
103 | unzip.extractAllTo(resourcePath, true)
104 | console.log('app.asar.unpacked.zip 解压缩完成')
105 | console.log('更新完成,正在重启...')
106 | mainWindow.webContents.send('partUpdateReady') // 此处可以: 通知渲染进程,进行指定操作
107 | setTimeout(() => {
108 | app.relaunch(); // 重启
109 | app.exit(0);
110 | }, 1800);
111 | } catch (error) {
112 | console.error(`extractAllToERROR: ${error}`);
113 | }
114 | console.log('webContents reload完成')
115 | } catch (error) {
116 | console.error(`checkForPartUpdatesERROR`, error)
117 | } finally {
118 | if (fs.existsSync(`${localresourcePath}.back`)) {
119 | fs.renameSync(`${localresourcePath}.back`, localresourcePath);
120 | }
121 | }
122 | })
123 |
124 | }
125 |
126 | if (process.platform === 'win32') {
127 | autoUpdater.updateConfigPath = path.join(__dirname, 'app-win-update.yml') // 远程包配置-win
128 | } else {
129 | autoUpdater.updateConfigPath = path.join(__dirname, 'app-mac-update.yml') // 远程包配置-mac
130 | }
131 |
132 | /**
133 | * 检查更新
134 | */
135 | function checkForUpdates() {
136 | console.log('enter checkForUpdates')
137 | if (isElectronDev) {
138 | autoUpdater.checkForUpdates()
139 | }
140 | if (!isElectronDev) {
141 | autoUpdater.checkForUpdatesAndNotify()
142 | }
143 | return;
144 | }
145 |
146 | // 通知更新检查(远程版本号大于本地即可拉去更新)
147 | ipcMain.on('checkForUpdates', (e, msg) => {
148 | console.log('checkForUpdatesMsg', msg)
149 | checkForUpdates()
150 | })
151 |
152 | module.exports = {
153 | updateHandle,
154 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # electron-react-umi-tpl
2 |
3 | [github](https://github.com/qld-cf/electron-react-tpl)
4 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/README_EN.md)
5 |
6 | 体验:
7 | [下载](https://www.yuque.com/docs/share/c453798a-12b3-4eb7-a725-4b960732eab1?#《资源1》)
8 |
9 |
10 | ##### 相对纯净模板,请[戳这里(待维护)](https://github.com/qld-cf/electron-common-react-tpl),无umi更自由,热加载更快
11 |
12 |
13 | ##### TODO
14 | 1. web+cdn版本,便于web服务器部署;
15 |
16 |
17 | 更新日志:
18 |
19 | 1. 2020-06-08 添加[全量更新](https://segmentfault.com/a/1190000016674982)功能
20 | 2. 2020-06-29 添加[远程增量更新功能](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/PART_UPDATE.md),无需下载包来重新安装更新;
21 | 3. 2020-07-27 优化初始化客户端loading等待页面,优化页面
22 | 4. 2020-08-18 添加[选中复制右键黏贴 | 自定义功能](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/Copy.md)
23 | 5. 2020-08-24 优化win7部分系统白屏(win7关闭硬件加速)
24 | 6. 2020-08-28 添加`koa2 + typescript + websocket + redis + log4js`服务端websocket|redis功能,客户端socket.io
25 | 7. 2020-09-20 新增错误边界识别和处理(页面中遇到React语法等报错后,自动捕捉记录错误日志,页面返回首页,开发模式默认关闭,生产环境开启)
26 | 8. 2020-10-16 新增 [限制只允许应用单开/单实例](https://www.electronjs.org/docs/api/app#%E4%BA%8B%E4%BB%B6-second-instance)
27 | 9. 2020-11-05 修复增量更新问题,优化部分代码,移除旧版打印,新增cli工具命令行启动
28 | 10. 2021-02-02 修复eslint, tslint问题
29 | 11. 2021-03-29 新增主进程HRM功能,修改主进程可以重新reload客户端
30 | 12. 2022-01-19 升级electron版本到12,修复升级引出的问题(又是瞎忙的一年..)
31 | 13. 2022-02-21 加入本地数据持久化sqlite + sequelize,分支:[feature/add_sqlite_20220209](https://github.com/qld-cf/electron-react-tpl/tree/feature/add_sqlite_20220209)
32 |
33 |
34 | ---
35 |
36 | [中文](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/CHANGE_LOG.md)
37 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/CHANGE_LOG_EN.md)
38 |
39 |
40 |
41 | `electron 12` + `umi 3.2` + `typescript` + `react 16.12` + `redux` + `antDesign 4.0` + `eslint tslint react-tslint`脚手架, 下载即用,已经为你做好了基座设施
42 |
43 | #### 客户端集成:
44 | - [x] 自动更新(electron-builder)
45 | - [x] 托盘菜单 app启动loading加载条
46 | - [x] electron-log 本地日志 electron-store 本地存储
47 | - [x] app打包图标 添加增量更新
48 | - [x] 添加redux-devtools插件
49 | - [x] 右键复制黏贴
50 | - [ ] app崩溃信息采集
51 | - [ ] app消息通知,快捷键等
52 |
53 |
54 |
55 | #### web端:
56 | 1. 基于[umi](https://umijs.org/zh-CN)脚手架,基础配置已集成,开发者关注业务代码编写即可
57 | 2. 本地存储redux(redux-saga)
58 | 3. antDesign >= 4.0
59 | 4. iconfont图标
60 |
61 | - 菜单配置 `src/layouts/menu/config.tsx`
62 |
63 |
64 | #### Fix:
65 | - [x] 升级到electron9.1.0,同步官方
66 | - [x] 部分win7白屏解决方案
67 | - [ ] 待修复sqlite打包问题
68 | - [ ] node Api功能封装与渲染进程业务解耦
69 |
70 |
71 | #### Next Feature
72 | - [x] 基于create-react-app的版本
73 |
74 |
75 | ### 工具命令行启动
76 |
77 | ```
78 | > npm i -g maple-react-cli // 全局安装cli工具
79 | > maple-react-cli // 初始化
80 |
81 | ? 请选择您接下来的操作 选择模板类型
82 | ? 选一个项目模板来初始化您的项目~
83 | ✔ electron-react-tpl
84 | ✔ 初始化中..
85 | ✔ 准备拉取代码...
86 | ? 请输入您本地初始化的项目名~ 输入自定义项目名,如project
87 | ✔ 拉取代码成功
88 | ✔ 安装依赖成功~
89 | ? 是否运行项目? yes
90 | 恭喜~项目启动成功~请稍候...
91 | ```
92 |
93 | [cli工具](https://github.com/qld-cf/maple-react-cli)
94 |
95 |
96 |
97 | ### 本地开启
98 |
99 | ```
100 | npm i // 安装不成功请用yarn
101 | npm start
102 | npm run pack // 默认根据当前系统打包
103 | npm run pack-mac // 打包mac平台
104 | npm run pack-windows // 打包windows平台
105 | npm run pack-all // 打包所有平台
106 |
107 | // 如果需要开启websocket
108 |
109 | cd ./server
110 | npm run dev
111 | ```
112 |
113 |
114 | ### 目录树
115 | ```
116 | |-- project
117 | |-- .editorconfig
118 | |-- .eslintrc.js
119 | |-- .gitignore
120 | |-- .gitlab-ci.yml
121 | |-- .prettierignore
122 | |-- .prettierrc.js
123 | |-- directoryList.md
124 | |-- package-lock.json
125 | |-- package.json
126 | |-- README.md
127 | |-- tsconfig.json
128 | |-- typings.d.ts
129 | |-- eslint-rules 自定义eslint配置
130 | | |-- base.js
131 | | |-- react.js
132 | | |-- ts.js
133 | |-- src
134 | |-- main 主进程
135 | | |-- app-update.yml 生产环境自动更新配置
136 | | |-- bundle.js 自动生成
137 | | |-- bundle.js.map
138 | | |-- dev-app-update.yml 开发环境自动更新配置
139 | | |-- index.js 入口
140 | | |-- loading.html
141 | | |-- preload.js
142 | | |-- README.md
143 | | |-- config 编译配置
144 | | | |-- config.js
145 | | | |-- webpack.config.js
146 | | |-- controls 控制集
147 | | | |-- AppAutoUpdater.js
148 | | | |-- AppMainWindow.js
149 | | | |-- AppTray.js
150 | | | |-- electron-helper.js
151 | | |-- public 附件
152 | | | |-- icon.ico
153 | | | |-- icon.png
154 | | | |-- tray.png
155 | | |-- script 编译脚本
156 | | |-- build.js
157 | |-- render 渲染进程
158 | |-- .env
159 | |-- .umirc.ts
160 | |-- app.ts
161 | |-- global.less
162 | |-- README.md
163 | |-- .umi umi自动生成配置和插件等
164 | | |-- umi.ts
165 | | |-- core
166 | | |-- plugin-dva
167 | | |-- plugin-initial-state
168 | | |-- plugin-model
169 | | |-- plugin-request
170 | |-- api 接口集合
171 | | |-- api.list.ts
172 | |-- assets 附件
173 | | |-- image
174 | | | |-- yay.jpg
175 | | |-- style
176 | | |-- bootstrap-part.less
177 | | |-- common.less
178 | |-- common 通用
179 | | |-- enum.ts
180 | | |-- global.ts
181 | |-- components 组件
182 | | |-- readme.md
183 | | |-- AutoUpdate
184 | | | |-- index.tsx
185 | | | |-- style.less
186 | | |-- FormCps
187 | | | |-- index.tsx
188 | | | |-- readme.md
189 | | |-- TableCps
190 | | |-- index.tsx
191 | | |-- readme.md
192 | |-- config 配置
193 | | |-- iconfont.ts
194 | | |-- menus.tsx
195 | |-- dist 本地打包生成文件
196 | |-- layouts 布局
197 | | |-- index.less
198 | | |-- index.tsx
199 | | |-- header
200 | | | |-- index.less
201 | | | |-- index.tsx
202 | | |-- loading
203 | | | |-- index.less
204 | | | |-- index.tsx
205 | | |-- menu
206 | | |-- index.less
207 | | |-- index.tsx
208 | |-- mock
209 | | |-- foo.ts
210 | |-- models redux
211 | | |-- xxStore.ts
212 | |-- pages
213 | | |-- home.normal.less
214 | | |-- index.tsx
215 | | |-- Foo 示例
216 | | | |-- index.tsx
217 | | | |-- components
218 | | | | |-- TableList.tsx
219 | | | |-- models
220 | | | | |-- foo.ts
221 | | | |-- services
222 | | | |-- foo.ts
223 | | |-- Home 业务
224 | | |-- Edge
225 | | | |-- index.tsx
226 | | |-- WebSocket
227 | | |-- index.tsx
228 | |-- utils 工具集
229 |
230 | ```
231 |
232 | ### log
233 |
234 | - 本地调试日志
235 |
236 | ```js
237 | const log = require('electron-log');
238 | // log.transports.file.file = 'xx/record.log' 本地可指定文件
239 | // 默认日志存放
240 | // on Linux: ~/.config/{appName}/log.log
241 | // on macOS: ~/Library/Logs/{appName}/log.log
242 | // on Windows: user\AppData\Roaming\{appName}\log.log
243 | log.info('Hello, log');
244 | log.warn('Some problem appears');
245 | ```
246 |
247 | ### 注意事项
248 |
249 | 1. 下载依赖和打包运行错误,请用cnpm或者配置npm config的electron ERROR路径
250 | 2. 任何地方的component文件夹名不可首字母大写 会被umi识别为路由而影响热加载等
251 | 3. 卡在node install.js : npm config edit 添加:electron_mirror="https://npm.taobao.org/mirrors/electron/"
252 | 4. 下载electron 一直失败,请删除包,然后安装全局的12版本的electron即可
253 | 5. umi版本随着业务量增大,tsx数量暴涨后,热加载效率会变低,可以尝试配置路由而不选用动态路由
254 | ```
255 | // .umirc.ts
256 | const routes = [] // 自定义路由,来自src/render/.umi/core/routes.ts
257 | routes: closeFlexRoute ? routes : undefined,
258 | ```
259 |
260 | ### 参考
261 |
262 | (官方electron文档)[https://www.electronjs.org/docs]
263 | (官方umi文档)[https://umijs.org/]
264 | (electron9版本升级到12)[https://www.cnblogs.com/mapleChain/p/15823267.html]
265 |
266 | ###### 能提供开发思路或者学习的话,麻烦给一颗卑微的星星star~谢谢 补充或者建议请提issue
267 |
268 |
269 |
270 | [github](https://github.com/qld-cf/electron-react-tpl)
271 |
272 |
273 | ### 附录
274 |
275 | 
276 | 
277 |
--------------------------------------------------------------------------------
/src/render/assets/style/bootstrap-part.less:
--------------------------------------------------------------------------------
1 | /**
2 | * bootstrap@4 部分样式
3 | */
4 |
5 | .m-0 {
6 | margin: 0 !important;
7 | }
8 |
9 | .mt-0,
10 | .my-0 {
11 | margin-top: 0 !important;
12 | }
13 |
14 | .mr-0,
15 | .mx-0 {
16 | margin-right: 0 !important;
17 | }
18 |
19 | .mb-0,
20 | .my-0 {
21 | margin-bottom: 0 !important;
22 | }
23 |
24 | .ml-0,
25 | .mx-0 {
26 | margin-left: 0 !important;
27 | }
28 |
29 | .m-1 {
30 | margin: 0.25rem !important;
31 | }
32 |
33 | .mt-1,
34 | .my-1 {
35 | margin-top: 0.25rem !important;
36 | }
37 |
38 | .mr-1,
39 | .mx-1 {
40 | margin-right: 0.25rem !important;
41 | }
42 |
43 | .mb-1,
44 | .my-1 {
45 | margin-bottom: 0.25rem !important;
46 | }
47 |
48 | .ml-1,
49 | .mx-1 {
50 | margin-left: 0.25rem !important;
51 | }
52 |
53 | .m-2 {
54 | margin: 0.5rem !important;
55 | }
56 |
57 | .mt-2,
58 | .my-2 {
59 | margin-top: 0.5rem !important;
60 | }
61 |
62 | .mr-2,
63 | .mx-2 {
64 | margin-right: 0.5rem !important;
65 | }
66 |
67 | .mb-2,
68 | .my-2 {
69 | margin-bottom: 0.5rem !important;
70 | }
71 |
72 | .ml-2,
73 | .mx-2 {
74 | margin-left: 0.5rem !important;
75 | }
76 |
77 | .m-3 {
78 | margin: 1rem !important;
79 | }
80 |
81 | .mt-3,
82 | .my-3 {
83 | margin-top: 1rem !important;
84 | }
85 |
86 | .mr-3,
87 | .mx-3 {
88 | margin-right: 1rem !important;
89 | }
90 |
91 | .mb-3,
92 | .my-3 {
93 | margin-bottom: 1rem !important;
94 | }
95 |
96 | .ml-3,
97 | .mx-3 {
98 | margin-left: 1rem !important;
99 | }
100 |
101 | .m-4 {
102 | margin: 1.5rem !important;
103 | }
104 |
105 | .mt-4,
106 | .my-4 {
107 | margin-top: 1.5rem !important;
108 | }
109 |
110 | .mr-4,
111 | .mx-4 {
112 | margin-right: 1.5rem !important;
113 | }
114 |
115 | .mb-4,
116 | .my-4 {
117 | margin-bottom: 1.5rem !important;
118 | }
119 |
120 | .ml-4,
121 | .mx-4 {
122 | margin-left: 1.5rem !important;
123 | }
124 |
125 | .m-5 {
126 | margin: 3rem !important;
127 | }
128 |
129 | .mt-5,
130 | .my-5 {
131 | margin-top: 3rem !important;
132 | }
133 |
134 | .mr-5,
135 | .mx-5 {
136 | margin-right: 3rem !important;
137 | }
138 |
139 | .mb-5,
140 | .my-5 {
141 | margin-bottom: 3rem !important;
142 | }
143 |
144 | .ml-5,
145 | .mx-5 {
146 | margin-left: 3rem !important;
147 | }
148 |
149 | .p-0 {
150 | padding: 0 !important;
151 | }
152 |
153 | .pt-0,
154 | .py-0 {
155 | padding-top: 0 !important;
156 | }
157 |
158 | .pr-0,
159 | .px-0 {
160 | padding-right: 0 !important;
161 | }
162 |
163 | .pb-0,
164 | .py-0 {
165 | padding-bottom: 0 !important;
166 | }
167 |
168 | .pl-0,
169 | .px-0 {
170 | padding-left: 0 !important;
171 | }
172 |
173 | .p-1 {
174 | padding: 0.25rem !important;
175 | }
176 |
177 | .pt-1,
178 | .py-1 {
179 | padding-top: 0.25rem !important;
180 | }
181 |
182 | .pr-1,
183 | .px-1 {
184 | padding-right: 0.25rem !important;
185 | }
186 |
187 | .pb-1,
188 | .py-1 {
189 | padding-bottom: 0.25rem !important;
190 | }
191 |
192 | .pl-1,
193 | .px-1 {
194 | padding-left: 0.25rem !important;
195 | }
196 |
197 | .p-2 {
198 | padding: 0.5rem !important;
199 | }
200 |
201 | .pt-2,
202 | .py-2 {
203 | padding-top: 0.5rem !important;
204 | }
205 |
206 | .pr-2,
207 | .px-2 {
208 | padding-right: 0.5rem !important;
209 | }
210 |
211 | .pb-2,
212 | .py-2 {
213 | padding-bottom: 0.5rem !important;
214 | }
215 |
216 | .pl-2,
217 | .px-2 {
218 | padding-left: 0.5rem !important;
219 | }
220 |
221 | .p-3 {
222 | padding: 1rem !important;
223 | }
224 |
225 | .pt-3,
226 | .py-3 {
227 | padding-top: 1rem !important;
228 | }
229 |
230 | .pr-3,
231 | .px-3 {
232 | padding-right: 1rem !important;
233 | }
234 |
235 | .pb-3,
236 | .py-3 {
237 | padding-bottom: 1rem !important;
238 | }
239 |
240 | .pl-3,
241 | .px-3 {
242 | padding-left: 1rem !important;
243 | }
244 |
245 | .p-4 {
246 | padding: 1.5rem !important;
247 | }
248 |
249 | .pt-4,
250 | .py-4 {
251 | padding-top: 1.5rem !important;
252 | }
253 |
254 | .pr-4,
255 | .px-4 {
256 | padding-right: 1.5rem !important;
257 | }
258 |
259 | .pb-4,
260 | .py-4 {
261 | padding-bottom: 1.5rem !important;
262 | }
263 |
264 | .pl-4,
265 | .px-4 {
266 | padding-left: 1.5rem !important;
267 | }
268 |
269 | .p-5 {
270 | padding: 3rem !important;
271 | }
272 |
273 | .pt-5,
274 | .py-5 {
275 | padding-top: 3rem !important;
276 | }
277 |
278 | .pr-5,
279 | .px-5 {
280 | padding-right: 3rem !important;
281 | }
282 |
283 | .pb-5,
284 | .py-5 {
285 | padding-bottom: 3rem !important;
286 | }
287 |
288 | .pl-5,
289 | .px-5 {
290 | padding-left: 3rem !important;
291 | }
292 |
293 | .h-100 {
294 | height: 100%;
295 | }
296 |
297 | .w-100 {
298 | width: 100%;
299 | }
300 |
301 | .d-flex {
302 | display: flex !important;
303 | }
304 |
305 |
306 | .flex-row {
307 | -ms-flex-direction: row !important;
308 | flex-direction: row !important;
309 | }
310 |
311 | .flex-column {
312 | -ms-flex-direction: column !important;
313 | flex-direction: column !important;
314 | }
315 |
316 | .flex-row-reverse {
317 | -ms-flex-direction: row-reverse !important;
318 | flex-direction: row-reverse !important;
319 | }
320 |
321 | .flex-column-reverse {
322 | -ms-flex-direction: column-reverse !important;
323 | flex-direction: column-reverse !important;
324 | }
325 |
326 | .flex-wrap {
327 | -ms-flex-wrap: wrap !important;
328 | flex-wrap: wrap !important;
329 | }
330 |
331 | .flex-nowrap {
332 | -ms-flex-wrap: nowrap !important;
333 | flex-wrap: nowrap !important;
334 | }
335 |
336 | .flex-wrap-reverse {
337 | -ms-flex-wrap: wrap-reverse !important;
338 | flex-wrap: wrap-reverse !important;
339 | }
340 |
341 | .flex-fill {
342 | -ms-flex: 1 1 auto !important;
343 | flex: 1 1 auto !important;
344 | }
345 |
346 | .flex-grow-0 {
347 | -ms-flex-positive: 0 !important;
348 | flex-grow: 0 !important;
349 | }
350 |
351 | .flex-grow-1 {
352 | -ms-flex-positive: 1 !important;
353 | flex-grow: 1 !important;
354 | }
355 |
356 | .flex-shrink-0 {
357 | -ms-flex-negative: 0 !important;
358 | flex-shrink: 0 !important;
359 | }
360 |
361 | .flex-shrink-1 {
362 | -ms-flex-negative: 1 !important;
363 | flex-shrink: 1 !important;
364 | }
365 |
366 | .justify-content-start {
367 | -ms-flex-pack: start !important;
368 | justify-content: flex-start !important;
369 | }
370 |
371 | .justify-content-end {
372 | -ms-flex-pack: end !important;
373 | justify-content: flex-end !important;
374 | }
375 |
376 | .justify-content-center {
377 | -ms-flex-pack: center !important;
378 | justify-content: center !important;
379 | }
380 |
381 | .justify-content-between {
382 | -ms-flex-pack: justify !important;
383 | justify-content: space-between !important;
384 | }
385 |
386 | .justify-content-around {
387 | -ms-flex-pack: distribute !important;
388 | justify-content: space-around !important;
389 | }
390 |
391 | .align-items-start {
392 | -ms-flex-align: start !important;
393 | align-items: flex-start !important;
394 | }
395 |
396 | .align-items-end {
397 | -ms-flex-align: end !important;
398 | align-items: flex-end !important;
399 | }
400 |
401 | .align-items-center {
402 | -ms-flex-align: center !important;
403 | align-items: center !important;
404 | }
405 |
406 | .align-items-baseline {
407 | -ms-flex-align: baseline !important;
408 | align-items: baseline !important;
409 | }
410 |
411 | .align-items-stretch {
412 | -ms-flex-align: stretch !important;
413 | align-items: stretch !important;
414 | }
415 |
416 | .align-content-start {
417 | -ms-flex-line-pack: start !important;
418 | align-content: flex-start !important;
419 | }
420 |
421 | .align-content-end {
422 | -ms-flex-line-pack: end !important;
423 | align-content: flex-end !important;
424 | }
425 |
426 | .align-content-center {
427 | -ms-flex-line-pack: center !important;
428 | align-content: center !important;
429 | }
430 |
431 | .align-content-between {
432 | -ms-flex-line-pack: justify !important;
433 | align-content: space-between !important;
434 | }
435 |
436 | .align-content-around {
437 | -ms-flex-line-pack: distribute !important;
438 | align-content: space-around !important;
439 | }
440 |
441 | .align-content-stretch {
442 | -ms-flex-line-pack: stretch !important;
443 | align-content: stretch !important;
444 | }
445 |
446 | .align-self-auto {
447 | -ms-flex-item-align: auto !important;
448 | align-self: auto !important;
449 | }
450 |
451 | .align-self-start {
452 | -ms-flex-item-align: start !important;
453 | align-self: flex-start !important;
454 | }
455 |
456 | .align-self-end {
457 | -ms-flex-item-align: end !important;
458 | align-self: flex-end !important;
459 | }
460 |
461 | .align-self-center {
462 | -ms-flex-item-align: center !important;
463 | align-self: center !important;
464 | }
465 |
466 | .align-self-baseline {
467 | -ms-flex-item-align: baseline !important;
468 | align-self: baseline !important;
469 | }
470 |
471 | .align-self-stretch {
472 | -ms-flex-item-align: stretch !important;
473 | align-self: stretch !important;
474 | }
475 |
476 | .text-center {
477 | text-align: center;
478 | }
479 |
480 | .text-left {
481 | text-align: left;
482 | }
483 |
484 | .text-right {
485 | text-align: right;
486 | }
487 |
488 | .cursor-pointer {
489 | cursor: pointer;
490 | }
491 |
--------------------------------------------------------------------------------
/README_EN.md:
--------------------------------------------------------------------------------
1 | # electron-react-umi-tpl
2 |
3 | [github](https://github.com/qld-cf/electron-react-tpl)
4 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/README_EN.md)
5 |
6 | Experience:
7 | [Download](https://www.yuque.com/docs/share/c453798a-12b3-4eb7-a725-4b960732eab1?#《Resource 1》)
8 |
9 |
10 | ##### Relatively pure template, please [click here (to be maintained)](https://github.com/qld-cf/electron-common-react-tpl), no umi is more free, hot loading is faster
11 |
12 |
13 | ##### TODO
14 | 1. The web+cdn version is convenient for web server deployment;
15 |
16 | Changelog:
17 |
18 | 1. 2020-06-08 Added [full update](https://segmentfault.com/a/1190000016674982) function
19 | 2. 2020-06-29 Add [remote incremental update function](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/PART_UPDATE.md), no need to download the package to renew install updates;
20 | 3. 2020-07-27 Optimize the initialization client loading waiting page, optimize the page
21 | 4. 2020-08-18 Added [select copy, right-click and paste | custom function](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/Copy.md)
22 | 5. 2020-08-24 Optimize the white screen of some win7 systems (win7 turns off hardware acceleration)
23 | 6. 2020-08-28 Add `koa2 + typescript + websocket + redis + log4js` server websocket|redis function, client socket.io
24 | 7. 2020-09-20 Added error boundary recognition and processing (after encountering errors such as React syntax on the page, the error log is automatically captured and recorded, the page returns to the home page, the development mode is turned off by default, and the production environment is turned on)
25 | 8. 2020-10-16 Added [Restriction to allow only single application/single instance](https://www.electronjs.org/docs/api/app#%E4%BA%8B%E4%BB%B6- second-instance)
26 | 9. 2020-11-05 Fix the incremental update problem, optimize some codes, remove the old version of printing, and add the cli tool to start the command line
27 | 10. 2021-02-02 Fix eslint, tslint issues
28 | 11. 2021-03-29 Added the main process HRM function, modifying the main process can reload the client
29 | 12. 2022-01-19 Upgrade the electron version to 12, fix the problems caused by the upgrade (another busy year..)
30 | 13. 2022-02-21 Add local data persistence sqlite + sequelize, branch: [feature/add_sqlite_20220209](https://github.com/qld-cf/electron-react-tpl/tree/feature/add_sqlite_20220209)
31 |
32 | ---
33 |
34 | [Chinese](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/CHANGE_LOG.md)
35 | [English Version](https://github.com/qld-cf/electron-react-tpl/blob/master/docs/CHANGE_LOG_EN.md)
36 |
37 |
38 |
39 | `electron 12` + `umi 3.2` + `typescript` + `react 16.12` + `redux` + `antDesign 4.0` + `eslint tslint react-tslint` scaffolding, download and use, the infrastructure has been prepared for you
40 |
41 | #### Client Integration:
42 | - [x] Auto update (electron-builder)
43 | - [x] Tray menu app start loading loading bar
44 | - [x] electron-log local log electron-store local storage
45 | - [x] app packaging icon added incremental update
46 | - [x] Add redux-devtools plugin
47 | - [x] right click copy paste
48 | - [ ] App crash information collection
49 | - [ ] App message notification, shortcut keys, etc.
50 |
51 |
52 | #### web side:
53 | 1. Based on [umi](https://umijs.org/zh-CN) scaffolding, the basic configuration has been integrated, and developers can focus on writing business code
54 | 2. Local storage redux (redux-saga)
55 | 3. antDesign >= 4.0
56 | 4. iconfont icon
57 |
58 | - Menu configuration `src/layouts/menu/config.tsx`
59 |
60 |
61 | #### Fix:
62 | - [x] Upgrade to electron9.1.0, sync official
63 | - [x] Some win7 white screen solutions
64 | - [ ] To be fixed sqlite packaging problem
65 | - [ ] Node Api function encapsulation is decoupled from rendering process business
66 |
67 |
68 | #### Next Feature
69 | - [x] version based on create-react-app
70 |
71 |
72 | ### Tool command line startup
73 |
74 | ````
75 | > npm i -g maple-react-cli // Install the cli tool globally
76 | > maple-react-cli // initialization
77 |
78 | ? Please select your next action Select template type
79 | ? Choose a project template to initialize your project~
80 | ✔ electron-react-tpl
81 | ✔ Initializing..
82 | ✔ Ready to pull code...
83 | ? Please enter your local initialized project name~ Enter a custom project name, such as project
84 | ✔ Pull code successfully
85 | ✔ Installed dependencies successfully~
86 | ? Run the project? yes
87 | Congratulations~The project started successfully~Please wait...
88 | ````
89 |
90 | [cli tool](https://github.com/qld-cf/maple-react-cli)
91 |
92 |
93 | ### local start
94 |
95 | ````
96 | npm i // If the installation is unsuccessful, please use yarn
97 | npm start
98 | npm run pack // The default is to pack according to the current system
99 | npm run pack-mac // Pack the mac platform
100 | npm run pack-windows // package windows platform
101 | npm run pack-all // pack all platforms
102 |
103 | // If you need to open websocket
104 |
105 | cd ./server
106 | npm run dev
107 | ````
108 |
109 |
110 | ### directory tree
111 | ````
112 | |-- project
113 | |-- .editorconfig
114 | |-- .eslintrc.js
115 | |-- .gitignore
116 | |-- .gitlab-ci.yml
117 | |-- .prettierignore
118 | |-- .prettierrc.js
119 | |-- directoryList.md
120 | |-- package-lock.json
121 | |-- package.json
122 | |-- README.md
123 | |-- tsconfig.json
124 | |-- typings.d.ts
125 | |-- eslint-rules Customize eslint configuration
126 | | |-- base.js
127 | | |-- react.js
128 | | |-- ts.js
129 | |-- src
130 | |-- main main process
131 | |-- app-update.yml Automatic update configuration for production environment
132 | | |-- bundle.js is automatically generated
133 | | |-- bundle.js.map
134 | | |-- dev-app-update.yml The development environment automatically updates the configuration
135 | | |-- index.js entry
136 | | |-- loading.html
137 | | |-- preload.js
138 | | |-- README.md
139 | | |-- config compile configuration
140 | | | |-- config.js
141 | | | |-- webpack.config.js
142 | | |-- controls control set
143 | | | |-- AppAutoUpdater.js
144 | | | |-- AppMainWindow.js
145 | | | |-- AppTray.js
146 | | | |-- electron-helper.js
147 | | |-- public attachments
148 | | | |-- icon.ico
149 | | | |-- icon.png
150 | | | |-- tray.png
151 | | |-- script compile script
152 | | |-- build.js
153 | |-- render rendering process
154 | |-- .env
155 | |-- .umirc.ts
156 | |-- app.ts
157 | |-- global.less
158 | |-- README.md
159 | |-- .umi umi automatically generates configuration and plugins, etc.
160 | | |-- umi.ts
161 | | |-- core
162 | | |-- plugin-dva
163 | |-- plugin-initial-state
164 | |-- plugin-model
165 | | |-- plugin-request
166 | |-- api interface collection
167 | | |-- api.list.ts
168 | |-- assets attachment
169 | | |-- image
170 | | | |-- yay.jpg
171 | | |-- style
172 | | |-- bootstrap-part.less
173 | | |-- common.less
174 | |-- common
175 | | |-- enum.ts
176 | | |-- global.ts
177 | |-- components component
178 | | |-- readme.md
179 | | |-- AutoUpdate
180 | | | |-- index.tsx
181 | | | |-- style.less
182 | | |-- FormCps
183 | | | |-- index.tsx
184 | | | |-- readme.md
185 | | |-- TableCps
186 | | |-- index.tsx
187 | | |-- readme.md
188 | |-- config configuration
189 | | |-- iconfont.ts
190 | | |-- menus.tsx
191 | |-- dist local packaging generated files
192 | |-- layouts layout
193 | | |-- index.less
194 | | |-- index.tsx
195 | | |-- header
196 | | | |-- index.less
197 | | | |-- index.tsx
198 | | |-- loading
199 | | | |-- index.less
200 | | | |-- index.tsx
201 | | |-- menu
202 | | |-- index.less
203 | | |-- index.tsx
204 | |-- mock
205 | | |-- foo.ts
206 | |-- models redux
207 | | |-- xxStore.ts
208 | |-- pages
209 | | |-- home.normal.less
210 | | |-- index.tsx
211 | | |-- Foo example
212 | | | |-- index.tsx
213 | | | |-- components
214 | | | | |-- TableList.tsx
215 | | | |-- models
216 | | | | |-- foo.ts
217 | | | |-- services
218 | | | |-- foo.ts
219 | | |-- Home Business
220 | | |-- Edge
221 | | | |-- index.tsx
222 | | |-- WebSocket
223 | | |-- index.tsx
224 | |-- utils toolset
225 |
226 | ````
227 |
228 | ### log
229 |
230 | - Local debug log
231 |
232 | ````js
233 | const log = require('electron-log');
234 | // log.transports.file.file = 'xx/record.log' local specifiable file
235 | // default log storage
236 | // on Linux: ~/.config/{appName}/log.log
237 | // on macOS: ~/Library/Logs/{appName}/log.log
238 | // on Windows: user\AppData\Roaming\{appName}\log.log
239 | log.info('Hello, log');
240 | log.warn('Some problem appears');
241 | ````
242 |
243 | ### Precautions
244 |
245 | 1. Download dependencies and package running errors, please use cnpm or configure the electron ERROR path of npm config
246 | 2. The name of the component folder in any place cannot be capitalized. It will be recognized by umi as a route and affect hot loading, etc.
247 | 3. Stuck in node install.js : npm config edit add: electron_mirror="https://npm.taobao.org/mirrors/electron/"
248 | 4. The download of electron has been failing, please delete the package, and then install the global version 12 of electron.
249 | 5. As the business volume of the umi version increases and the number of tsx surges, the hot loading efficiency will become lower. You can try to configure routing instead of dynamic routing.
250 | ````
251 | // .umirc.ts
252 | const routes = [] // custom routes, from src/render/.umi/core/routes.ts
253 | routes: closeFlexRoute ? routes : undefined,
254 | ````
255 |
256 | ### refer to
257 |
258 | (Official electron documentation)[https://www.electronjs.org/docs]
259 | (Official umi documentation) [https://umijs.org/]
260 | (electron9 version upgraded to 12)[https://www.cnblogs.com/mapleChain/p/15823267.html]
261 |
262 | ###### Can be used or easy to use, please give a humble star~ Thank you, please file an issue for supplements or suggestions
263 |
264 |
265 |
266 | [github](https://github.com/qld-cf/electron-react-tpl)
267 |
268 |
269 | ### Appendix
270 |
271 | 
272 | 
--------------------------------------------------------------------------------
/src/render/config/iconfont.ts:
--------------------------------------------------------------------------------
1 | const icons = [
2 | 'step-backward',
3 | 'step-forward',
4 | 'fast-backward',
5 | 'fast-forward',
6 | 'shrink',
7 | 'arrows-alt',
8 | 'down',
9 | 'up',
10 | 'left',
11 | 'right',
12 | 'caret-up',
13 | 'caret-down',
14 | 'caret-left',
15 | 'caret-right',
16 | 'up-circle',
17 | 'down-circle',
18 | 'left-circle',
19 | 'right-circle',
20 | 'double-right',
21 | 'double-left',
22 | 'vertical-left',
23 | 'vertical-right',
24 | 'vertical-align-top',
25 | 'vertical-align-middle',
26 | 'vertical-align-bottom',
27 | 'forward',
28 | 'backward',
29 | 'rollback',
30 | 'enter',
31 | 'retweet',
32 | 'swap',
33 | 'swap-left',
34 | 'swap-right',
35 | 'arrow-up',
36 | 'arrow-down',
37 | 'arrow-left',
38 | 'arrow-right',
39 | 'play-circle',
40 | 'up-square',
41 | 'down-square',
42 | 'left-square',
43 | 'right-square',
44 | 'login',
45 | 'logout',
46 | 'menu-fold',
47 | 'menu-unfold',
48 | 'border-bottom',
49 | 'border-horizontal',
50 | 'border-inner',
51 | 'border-outer',
52 | 'border-left',
53 | 'border-right',
54 | 'border-top',
55 | 'border-verticle',
56 | 'pic-center',
57 | 'pic-left',
58 | 'pic-right',
59 | 'radius-bottomleft',
60 | 'radius-bottomright',
61 | 'radius-upleft',
62 | 'radius-upright',
63 | 'fullscreen',
64 | 'fullscreen-exit',
65 | 'question',
66 | 'question-circle',
67 | 'plus',
68 | 'plus-circle',
69 | 'pause',
70 | 'pause-circle',
71 | 'minus',
72 | 'minus-circle',
73 | 'plus-square',
74 | 'minus-square',
75 | 'info',
76 | 'info-circle',
77 | 'exclamation',
78 | 'exclamation-circle',
79 | 'close',
80 | 'close-circle',
81 | 'close-square',
82 | 'check',
83 | 'check-circle',
84 | 'check-square',
85 | 'clock-circle',
86 | 'warning',
87 | 'issues-close',
88 | 'stop',
89 | 'edit',
90 | 'form',
91 | 'copy',
92 | 'scissor',
93 | 'delete',
94 | 'snippets',
95 | 'diff',
96 | 'highlight',
97 | 'align-center',
98 | 'align-left',
99 | 'align-right',
100 | 'bg-colors',
101 | 'bold',
102 | 'italic',
103 | 'underline',
104 | 'strikethrough',
105 | 'redo',
106 | 'undo',
107 | 'zoom-in',
108 | 'zoom-out',
109 | 'font-colors',
110 | 'font-size',
111 | 'line-height',
112 | 'dash',
113 | 'small-dash',
114 | 'sort-ascending',
115 | 'sort-descending',
116 | 'drag',
117 | 'ordered-list',
118 | 'unordered-list',
119 | 'radius-setting',
120 | 'column-width',
121 | 'column-height',
122 | 'area-chart',
123 | 'pie-chart',
124 | 'bar-chart',
125 | 'dot-chart',
126 | 'line-chart',
127 | 'radar-chart',
128 | 'heat-map',
129 | 'fall',
130 | 'rise',
131 | 'stock',
132 | 'box-plot',
133 | 'fund',
134 | 'sliders',
135 | 'android',
136 | 'apple',
137 | 'windows',
138 | 'ie',
139 | 'chrome',
140 | 'github',
141 | 'aliwangwang',
142 | 'dingding',
143 | 'weibo-square',
144 | 'weibo-circle',
145 | 'taobao-circle',
146 | 'html5',
147 | 'weibo',
148 | 'twitter',
149 | 'wechat',
150 | 'youtube',
151 | 'alipay-circle',
152 | 'taobao',
153 | 'skype',
154 | 'qq',
155 | 'medium-workmark',
156 | 'gitlab',
157 | 'medium',
158 | 'linkedin',
159 | 'google-plus',
160 | 'dropbox',
161 | 'facebook',
162 | 'codepen',
163 | 'code-sandbox',
164 | 'amazon',
165 | 'google',
166 | 'codepen-circle',
167 | 'alipay',
168 | 'ant-design',
169 | 'ant-cloud',
170 | 'aliyun',
171 | 'zhihu',
172 | 'slack',
173 | 'slack-square',
174 | 'behance',
175 | 'behance-square',
176 | 'dribbble',
177 | 'dribbble-square',
178 | 'instagram',
179 | 'yuque',
180 | 'alibaba',
181 | 'yahoo',
182 | 'reddit',
183 | 'sketch',
184 | 'account-book',
185 | 'alert',
186 | 'api',
187 | 'appstore',
188 | 'audio',
189 | 'bank',
190 | 'bell',
191 | 'book',
192 | 'bug',
193 | 'bulb',
194 | 'calculator',
195 | 'build',
196 | 'calendar',
197 | 'camera',
198 | 'car',
199 | 'carry-out',
200 | 'cloud',
201 | 'code',
202 | 'compass',
203 | 'contacts',
204 | 'container',
205 | 'control',
206 | 'credit-card',
207 | 'crown',
208 | 'customer-service',
209 | 'dashboard',
210 | 'database',
211 | 'dislike',
212 | 'environment',
213 | 'experiment',
214 | 'eye-invisible',
215 | 'eye',
216 | 'file-add',
217 | 'file-excel',
218 | 'file-exclamation',
219 | 'file-image',
220 | 'file-markdown',
221 | 'file-pdf',
222 | 'file-ppt',
223 | 'file-text',
224 | 'file-unknown',
225 | 'file-word',
226 | 'file-zip',
227 | 'file',
228 | 'filter',
229 | 'fire',
230 | 'flag',
231 | 'folder-add',
232 | 'folder',
233 | 'folder-open',
234 | 'frown',
235 | 'funnel-plot',
236 | 'gift',
237 | 'hdd',
238 | 'heart',
239 | 'home',
240 | 'hourglass',
241 | 'idcard',
242 | 'insurance',
243 | 'interaction',
244 | 'layout',
245 | 'like',
246 | 'lock',
247 | 'mail',
248 | 'medicine-box',
249 | 'meh',
250 | 'message',
251 | 'mobile',
252 | 'money-collect',
253 | 'pay-circle',
254 | 'notification',
255 | 'phone',
256 | 'picture',
257 | 'play-square',
258 | 'printer',
259 | 'profile',
260 | 'project',
261 | 'pushpin',
262 | 'property-safety',
263 | 'read',
264 | 'reconciliation',
265 | 'red-envelope',
266 | 'rest',
267 | 'rocket',
268 | 'safety-certificate',
269 | 'save',
270 | 'schedule',
271 | 'security-scan',
272 | 'setting',
273 | 'shop',
274 | 'shopping',
275 | 'skin',
276 | 'smile',
277 | 'sound',
278 | 'star',
279 | 'switcher',
280 | 'tablet',
281 | 'tag',
282 | 'tags',
283 | 'tool',
284 | 'thunderbolt',
285 | 'trophy',
286 | 'unlock',
287 | 'usb',
288 | 'video-camera',
289 | 'wallet',
290 | 'apartment',
291 | 'audit',
292 | 'barcode',
293 | 'bars',
294 | 'block',
295 | 'border',
296 | 'branches',
297 | 'ci',
298 | 'cloud-download',
299 | 'cloud-server',
300 | 'cloud-sync',
301 | 'cloud-upload',
302 | 'cluster',
303 | 'coffee',
304 | 'copyright',
305 | 'deployment-unit',
306 | 'desktop',
307 | 'disconnect',
308 | 'dollar',
309 | 'download',
310 | 'ellipsis',
311 | 'euro',
312 | 'exception',
313 | 'export',
314 | 'file-done',
315 | 'file-jpg',
316 | 'file-protect',
317 | 'file-sync',
318 | 'file-search',
319 | 'fork',
320 | 'gateway',
321 | 'global',
322 | 'gold',
323 | 'history',
324 | 'import',
325 | 'inbox',
326 | 'key',
327 | 'laptop',
328 | 'link',
329 | 'line',
330 | 'loading-3-quarters',
331 | 'loading',
332 | 'man',
333 | 'menu',
334 | 'monitor',
335 | 'more',
336 | 'number',
337 | 'percentage',
338 | 'paper-clip',
339 | 'pound',
340 | 'poweroff',
341 | 'pull-request',
342 | 'qrcode',
343 | 'reload',
344 | 'safety',
345 | 'robot',
346 | 'scan',
347 | 'search',
348 | 'select',
349 | 'shake',
350 | 'share-alt',
351 | 'shopping-cart',
352 | 'solution',
353 | 'sync',
354 | 'table',
355 | 'team',
356 | 'to-top',
357 | 'trademark',
358 | 'transaction',
359 | 'upload',
360 | 'user-add',
361 | 'user-delete',
362 | 'usergroup-add',
363 | 'user',
364 | 'usergroup-delete',
365 | 'wifi',
366 | 'woman'
367 | ];
368 |
369 | const iconfontIcons = [
370 | '两季品',
371 | '物流',
372 | '预约',
373 | '定位',
374 | '素材库',
375 | '上门取货',
376 | '超级服务',
377 | '素材中心',
378 | '全链路可售',
379 | '权益发放工具',
380 | 'S&OP',
381 | '单仓发全国',
382 | '增值服务',
383 | '活动监控',
384 | '活动全景',
385 | '活动协同',
386 | 'WBR',
387 | '目标监控',
388 | '赚翻乐',
389 | 'ANS',
390 | '价值评估',
391 | '活动复盘',
392 | '活动协同',
393 | '目标制定',
394 | '目标管理',
395 | '营销流量配置',
396 | 'GMV目标',
397 | '选品系统',
398 | '运营工具',
399 | '营销流量配置',
400 | '盘货系统',
401 | '货品调拨监控',
402 | '数据风控预警平台',
403 | '运营中枢',
404 | '结算效率分析',
405 | '销量实时看板',
406 | '实时看板',
407 | '订单履约看板',
408 | '实时预测',
409 | '销量预测',
410 | '进货参谋',
411 | '多维度看板',
412 | '财务分析',
413 | '前端商品毛利',
414 | '经营分析',
415 | '数据分析',
416 | '商家数据',
417 | '准确率分析',
418 | '数据管理',
419 | '分销商结算',
420 | '结算工具箱',
421 | '成本查询',
422 | '后端商品毛利',
423 | '供应商结算',
424 | '补货计划',
425 | '协同计划',
426 | '销量计划',
427 | '清仓计划',
428 | '经销计划',
429 | '优化计划',
430 | '清退计划',
431 | '管理计划',
432 | '退仓计划',
433 | '商品计划',
434 | '销售计划',
435 | '贴纸计划',
436 | '运营管理',
437 | '会场管理',
438 | '会场',
439 | '菜鸟运营指标',
440 | '配置管理',
441 | '权益管理',
442 | '生命周期',
443 | '公告管理',
444 | '排期',
445 | '销售管理',
446 | '排期管理',
447 | '生命周期管理',
448 | '品牌授权管理',
449 | '招商管理',
450 | '品类管理',
451 | '品控',
452 | '绩效',
453 | '销售',
454 | '岗位授权',
455 | '品牌授权',
456 | '活动管理',
457 | '风控',
458 | '绩效管理',
459 | '岗位授权管理',
460 | '风控管理',
461 | '合同管理',
462 | '品控管理',
463 | '智慧营销管理',
464 | '智慧销售管理',
465 | '合同',
466 | '智慧销售',
467 | '智慧营销',
468 | '品类规划',
469 | '货品分层',
470 | '智能选品',
471 | '新品发布',
472 | '询货管理',
473 | '货品择仓',
474 | '进货管理',
475 | '进货',
476 | '备货管理',
477 | '备货',
478 | '货品调拨',
479 | '询货',
480 | '盘货',
481 | '补货管理',
482 | '盘或管理',
483 | '补货',
484 | '货品调拨管理',
485 | '退仓管理',
486 | '仓库健康指数管理',
487 | '路由仓管理',
488 | '清仓管理',
489 | '出入库管理',
490 | '库存保障管理',
491 | '库存健康管理',
492 | '库存健康指数',
493 | '退仓',
494 | '库存健康',
495 | '清仓',
496 | '路由仓',
497 | '库存保障',
498 | '渠道库存',
499 | '出入库',
500 | '入库管理',
501 | '渠道库存管理',
502 | '库存',
503 | '库存管理',
504 | '入库',
505 | '调拨执行',
506 | '调拨差异',
507 | '调拨管理',
508 | '渠道退货',
509 | '渠道销售',
510 | '渠道分货',
511 | '渠道管理',
512 | '退货单管理',
513 | '在途报表管理',
514 | '进销存报表',
515 | '进销存报表管理',
516 | '在途报表',
517 | '报废单管理',
518 | '报表管理',
519 | '退货单',
520 | '调拨单管理',
521 | '报废单',
522 | '调拨单',
523 | '头程单管理',
524 | '退仓单管理',
525 | '退仓单',
526 | '头程单',
527 | '单据管理',
528 | '月结管理',
529 | '返利规则管理',
530 | '结算报表',
531 | '结算单管理',
532 | '月结',
533 | '返利规则',
534 | '结算管理',
535 | '结算单',
536 | '结算报表',
537 | '结算',
538 | '定价管理',
539 | '定价',
540 | '采购计划管理',
541 | '预约采购',
542 | '资源订购管理',
543 | '采购计划',
544 | '预约采购管理',
545 | '采购单管理',
546 | '资源订购',
547 | '采购价管理',
548 | '采购管理',
549 | '采购价',
550 | '采购单',
551 | '选品',
552 | '商品备案管理',
553 | '爆款商品',
554 | '商品复制',
555 | '商品复制管理',
556 | '商品发布管理',
557 | '商品备案',
558 | '商品管理',
559 | '供应商管理',
560 | '商家',
561 | '分销商管理',
562 | '商家管理',
563 | '人群管理',
564 | '人群',
565 | '用户管理',
566 | '分销商',
567 | '供应商',
568 | '货',
569 | '资源',
570 | '配置',
571 | '权益',
572 | '目录',
573 | '服务',
574 | '中枢',
575 | '经销',
576 | '运营',
577 | '图片',
578 | '平台',
579 | '列表',
580 | '活动',
581 | '看板',
582 | '进/补',
583 | '工具',
584 | '对话',
585 | '公告',
586 | '结构',
587 | '指标',
588 | '券',
589 | '删除',
590 | '解锁',
591 | '进出',
592 | '工具箱',
593 | '发布',
594 | '上升',
595 | '日历',
596 | '仓库',
597 | '价格',
598 | '采购',
599 | '渠道',
600 | '报表',
601 | '锁',
602 | '单据',
603 | '商品',
604 | '招商',
605 | '智慧',
606 | '正向',
607 | '目标',
608 | '关闭',
609 | '逆向',
610 | '加',
611 | '健康',
612 | '减',
613 | '协同',
614 | '价格',
615 | '设置/管理',
616 | '监控',
617 | '收起',
618 | '拒绝',
619 | '流量',
620 | '注意',
621 | '下拉',
622 | '调拨',
623 | '能力',
624 | '疑问',
625 | '数据',
626 | '安全',
627 | '用户',
628 | '消息',
629 | '搜索',
630 | '完成',
631 | '关闭',
632 | '减'
633 | ];
634 |
635 | const iconfontIconValues = [
636 | 'icon-liangjipin',
637 | 'icon-RectangleCopy258',
638 | 'icon-RectangleCopy257',
639 | 'icon-dingwei',
640 | 'icon-RectangleCopy256',
641 | 'icon-RectangleCopy255',
642 | 'icon-RectangleCopy254',
643 | 'icon-RectangleCopy253',
644 | 'icon-RectangleCopy252',
645 | 'icon-RectangleCopy251',
646 | 'icon-RectangleCopy250',
647 | 'icon-RectangleCopy249',
648 | 'icon-RectangleCopy248',
649 | 'icon-RectangleCopy247',
650 | 'icon-RectangleCopy246',
651 | 'icon-RectangleCopy245',
652 | 'icon-RectangleCopy244',
653 | 'icon-RectangleCopy243',
654 | 'icon-RectangleCopy242',
655 | 'icon-RectangleCopy241',
656 | 'icon-RectangleCopy240',
657 | 'icon-RectangleCopy239',
658 | 'icon-RectangleCopy238',
659 | 'icon-RectangleCopy237',
660 | 'icon-RectangleCopy236',
661 | 'icon-RectangleCopy235',
662 | 'icon-RectangleCopy234',
663 | 'icon-RectangleCopy233',
664 | 'icon-RectangleCopy232',
665 | 'icon-RectangleCopy231',
666 | 'icon-RectangleCopy230',
667 | 'icon-RectangleCopy229',
668 | 'icon-RectangleCopy228',
669 | 'icon-RectangleCopy227',
670 | 'icon-RectangleCopy226',
671 | 'icon-RectangleCopy225',
672 | 'icon-RectangleCopy224',
673 | 'icon-RectangleCopy223',
674 | 'icon-RectangleCopy222',
675 | 'icon-RectangleCopy221',
676 | 'icon-RectangleCopy220',
677 | 'icon-RectangleCopy219',
678 | 'icon-RectangleCopy218',
679 | 'icon-RectangleCopy217',
680 | 'icon-RectangleCopy216',
681 | 'icon-RectangleCopy215',
682 | 'icon-RectangleCopy214',
683 | 'icon-RectangleCopy213',
684 | 'icon-RectangleCopy212',
685 | 'icon-RectangleCopy211',
686 | 'icon-RectangleCopy210',
687 | 'icon-RectangleCopy209',
688 | 'icon-RectangleCopy208',
689 | 'icon-RectangleCopy207',
690 | 'icon-RectangleCopy206',
691 | 'icon-RectangleCopy205',
692 | 'icon-RectangleCopy204',
693 | 'icon-RectangleCopy203',
694 | 'icon-RectangleCopy202',
695 | 'icon-RectangleCopy201',
696 | 'icon-RectangleCopy200',
697 | 'icon-RectangleCopy199',
698 | 'icon-RectangleCopy198',
699 | 'icon-RectangleCopy197',
700 | 'icon-RectangleCopy196',
701 | 'icon-RectangleCopy195',
702 | 'icon-RectangleCopy194',
703 | 'icon-RectangleCopy193',
704 | 'icon-RectangleCopy192',
705 | 'icon-RectangleCopy191',
706 | 'icon-RectangleCopy190',
707 | 'icon-RectangleCopy189',
708 | 'icon-RectangleCopy188',
709 | 'icon-RectangleCopy187',
710 | 'icon-RectangleCopy186',
711 | 'icon-RectangleCopy185',
712 | 'icon-RectangleCopy184',
713 | 'icon-RectangleCopy183',
714 | 'icon-RectangleCopy182',
715 | 'icon-RectangleCopy181',
716 | 'icon-RectangleCopy180',
717 | 'icon-RectangleCopy179',
718 | 'icon-RectangleCopy178',
719 | 'icon-RectangleCopy177',
720 | 'icon-RectangleCopy176',
721 | 'icon-RectangleCopy175',
722 | 'icon-RectangleCopy174',
723 | 'icon-RectangleCopy173',
724 | 'icon-RectangleCopy172',
725 | 'icon-RectangleCopy171',
726 | 'icon-RectangleCopy170',
727 | 'icon-RectangleCopy169',
728 | 'icon-RectangleCopy168',
729 | 'icon-RectangleCopy167',
730 | 'icon-RectangleCopy166',
731 | 'icon-RectangleCopy165',
732 | 'icon-RectangleCopy164',
733 | 'icon-RectangleCopy163',
734 | 'icon-RectangleCopy162',
735 | 'icon-RectangleCopy161',
736 | 'icon-RectangleCopy160',
737 | 'icon-RectangleCopy159',
738 | 'icon-RectangleCopy158',
739 | 'icon-RectangleCopy157',
740 | 'icon-RectangleCopy156',
741 | 'icon-RectangleCopy155',
742 | 'icon-RectangleCopy154',
743 | 'icon-RectangleCopy153',
744 | 'icon-RectangleCopy152',
745 | 'icon-RectangleCopy151',
746 | 'icon-RectangleCopy150',
747 | 'icon-RectangleCopy149',
748 | 'icon-RectangleCopy148',
749 | 'icon-RectangleCopy147',
750 | 'icon-RectangleCopy146',
751 | 'icon-RectangleCopy145',
752 | 'icon-RectangleCopy144',
753 | 'icon-RectangleCopy143',
754 | 'icon-RectangleCopy142',
755 | 'icon-RectangleCopy141',
756 | 'icon-RectangleCopy140',
757 | 'icon-RectangleCopy139',
758 | 'icon-RectangleCopy138',
759 | 'icon-RectangleCopy137',
760 | 'icon-RectangleCopy136',
761 | 'icon-RectangleCopy135',
762 | 'icon-RectangleCopy134',
763 | 'icon-RectangleCopy133',
764 | 'icon-RectangleCopy132',
765 | 'icon-RectangleCopy131',
766 | 'icon-RectangleCopy130',
767 | 'icon-RectangleCopy129',
768 | 'icon-RectangleCopy128',
769 | 'icon-RectangleCopy127',
770 | 'icon-RectangleCopy126',
771 | 'icon-RectangleCopy125',
772 | 'icon-RectangleCopy124',
773 | 'icon-RectangleCopy123',
774 | 'icon-RectangleCopy122',
775 | 'icon-RectangleCopy121',
776 | 'icon-RectangleCopy120',
777 | 'icon-RectangleCopy119',
778 | 'icon-RectangleCopy118',
779 | 'icon-RectangleCopy117',
780 | 'icon-RectangleCopy116',
781 | 'icon-RectangleCopy115',
782 | 'icon-RectangleCopy114',
783 | 'icon-RectangleCopy113',
784 | 'icon-RectangleCopy112',
785 | 'icon-RectangleCopy111',
786 | 'icon-RectangleCopy110',
787 | 'icon-RectangleCopy109',
788 | 'icon-RectangleCopy108',
789 | 'icon-RectangleCopy107',
790 | 'icon-RectangleCopy106',
791 | 'icon-RectangleCopy105',
792 | 'icon-RectangleCopy104',
793 | 'icon-RectangleCopy103',
794 | 'icon-RectangleCopy102',
795 | 'icon-RectangleCopy101',
796 | 'icon-RectangleCopy100',
797 | 'icon-RectangleCopy99',
798 | 'icon-RectangleCopy98',
799 | 'icon-RectangleCopy97',
800 | 'icon-RectangleCopy96',
801 | 'icon-RectangleCopy95',
802 | 'icon-RectangleCopy94',
803 | 'icon-RectangleCopy93',
804 | 'icon-RectangleCopy92',
805 | 'icon-RectangleCopy91',
806 | 'icon-RectangleCopy90',
807 | 'icon-RectangleCopy89',
808 | 'icon-RectangleCopy88',
809 | 'icon-RectangleCopy87',
810 | 'icon-RectangleCopy86',
811 | 'icon-RectangleCopy85',
812 | 'icon-RectangleCopy84',
813 | 'icon-RectangleCopy83',
814 | 'icon-RectangleCopy82',
815 | 'icon-RectangleCopy81',
816 | 'icon-RectangleCopy80',
817 | 'icon-RectangleCopy79',
818 | 'icon-RectangleCopy78',
819 | 'icon-RectangleCopy77',
820 | 'icon-RectangleCopy76',
821 | 'icon-RectangleCopy75',
822 | 'icon-RectangleCopy74',
823 | 'icon-RectangleCopy73',
824 | 'icon-RectangleCopy72',
825 | 'icon-RectangleCopy71',
826 | 'icon-RectangleCopy70',
827 | 'icon-RectangleCopy69',
828 | 'icon-RectangleCopy68',
829 | 'icon-RectangleCopy67',
830 | 'icon-RectangleCopy66',
831 | 'icon-RectangleCopy65',
832 | 'icon-RectangleCopy64',
833 | 'icon-RectangleCopy63',
834 | 'icon-RectangleCopy62',
835 | 'icon-RectangleCopy61',
836 | 'icon-RectangleCopy60',
837 | 'icon-RectangleCopy59',
838 | 'icon-RectangleCopy58',
839 | 'icon-RectangleCopy57',
840 | 'icon-RectangleCopy56',
841 | 'icon-RectangleCopy55',
842 | 'icon-RectangleCopy54',
843 | 'icon-RectangleCopy53',
844 | 'icon-RectangleCopy52',
845 | 'icon-RectangleCopy51',
846 | 'icon-RectangleCopy50',
847 | 'icon-RectangleCopy49',
848 | 'icon-RectangleCopy48',
849 | 'icon-RectangleCopy47',
850 | 'icon-RectangleCopy46',
851 | 'icon-RectangleCopy45',
852 | 'icon-RectangleCopy44',
853 | 'icon-RectangleCopy43',
854 | 'icon-RectangleCopy42',
855 | 'icon-RectangleCopy41',
856 | 'icon-RectangleCopy40',
857 | 'icon-RectangleCopy39',
858 | 'icon-RectangleCopy38',
859 | 'icon-RectangleCopy37',
860 | 'icon-RectangleCopy36',
861 | 'icon-RectangleCopy35',
862 | 'icon-RectangleCopy34',
863 | 'icon-RectangleCopy33',
864 | 'icon-RectangleCopy32',
865 | 'icon-RectangleCopy31',
866 | 'icon-RectangleCopy30',
867 | 'icon-RectangleCopy29',
868 | 'icon-RectangleCopy28',
869 | 'icon-RectangleCopy27',
870 | 'icon-RectangleCopy26',
871 | 'icon-RectangleCopy25',
872 | 'icon-RectangleCopy24',
873 | 'icon-RectangleCopy23',
874 | 'icon-RectangleCopy22',
875 | 'icon-RectangleCopy21',
876 | 'icon-RectangleCopy20',
877 | 'icon-RectangleCopy19',
878 | 'icon-Rectangle',
879 | 'icon-RectangleCopy18',
880 | 'icon-RectangleCopy17',
881 | 'icon-RectangleCopy16',
882 | 'icon-RectangleCopy15',
883 | 'icon-RectangleCopy14',
884 | 'icon-RectangleCopy13',
885 | 'icon-RectangleCopy12',
886 | 'icon-RectangleCopy11',
887 | 'icon-RectangleCopy10',
888 | 'icon-RectangleCopy9',
889 | 'icon-RectangleCopy8',
890 | 'icon-RectangleCopy7',
891 | 'icon-RectangleCopy6',
892 | 'icon-RectangleCopy5',
893 | 'icon-RectangleCopy4',
894 | 'icon-RectangleCopy3',
895 | 'icon-RectangleCopy2',
896 | 'icon-RectangleCopy1',
897 | 'icon-RectangleCopy',
898 | 'icon-jian'
899 | ];
900 |
901 | const iconFonts = iconfontIcons.map((item, index) => ({
902 | value: iconfontIconValues[index],
903 | label: item
904 | }));
905 |
906 | export { icons, iconFonts };
907 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | root: true,
4 | env: {
5 | browser: true,
6 | es6: true
7 | },
8 | settings: {
9 | "version": "detect"
10 | },
11 | extends: [
12 | 'plugin:@typescript-eslint/recommended',
13 | 'plugin:react/recommended'
14 | ],
15 | globals: {
16 | Atomics: 'readonly',
17 | SharedArrayBuffer: 'readonly'
18 | },
19 | parser: '@typescript-eslint/parser',
20 | parserOptions: {
21 | ecmaFeatures: {
22 | experimentalObjectRestSpread: true, // es6对象的扩展运算符
23 | jsx: true // //指定ESLint可以解析JSX语法
24 | },
25 | ecmaVersion: 2019,
26 | sourceType: 'module'
27 | },
28 | plugins: [
29 | 'html',
30 | 'react',
31 | '@typescript-eslint',
32 | 'react-hooks'
33 | ],
34 | rules: {
35 | /** ********************************** js相关配置 ******************************************/
36 | 'default-case-last': 'off',
37 | 'no-promise-executor-return': 'off',
38 | 'no-useless-backreference': 'off',
39 | /**
40 | * setter 必须有对应的 getter,getter 可以没有对应的 setter
41 | */
42 | 'accessor-pairs': [
43 | 'error',
44 | {
45 | setWithoutGet: true,
46 | getWithoutSet: false
47 | }
48 | ],
49 | /**
50 | * 数组的方法除了 forEach 之外,回调函数必须有返回值
51 | */
52 | 'array-callback-return': 'error',
53 | /**
54 | * 将 var 定义的变量视为块作用域,禁止在块外使用
55 | * @reason 已经禁止使用 var 了
56 | */
57 | 'block-scoped-var': 'off',
58 | /**
59 | * callback 之后必须立即 return
60 | */
61 | 'callback-return': 'off',
62 | /**
63 | * 变量名必须是 camelcase 风格的
64 | * @reason 很多 api 或文件名都不是 camelcase 风格的
65 | */
66 | camelcase: 'off',
67 | /**
68 | * 注释的首字母必须大写
69 | */
70 | 'capitalized-comments': 'off',
71 | /**
72 | * 在类的非静态方法中,必须存在对 this 的引用
73 | */
74 | 'class-methods-use-this': 'off',
75 | /**
76 | * 禁止函数的循环复杂度超过 20
77 | * @reason https://en.wikipedia.org/wiki/Cyclomatic_complexity
78 | */
79 | complexity: [
80 | 'error',
81 | {
82 | max: 20
83 | }
84 | ],
85 | /**
86 | * 禁止函数在不同分支返回不同类型的值
87 | * @reason 缺少 TypeScript 的支持,类型判断是不准确的
88 | */
89 | 'consistent-return': 'off',
90 | /**
91 | * 限制 this 的别名
92 | */
93 | 'consistent-this': 'off',
94 | /**
95 | * constructor 中必须有 super
96 | */
97 | 'constructor-super': 'error',
98 | /**
99 | * switch 语句必须有 default
100 | */
101 | 'default-case': 'off',
102 | /**
103 | * 有默认值的参数必须放在函数参数的末尾
104 | */
105 | 'default-param-last': 'off',
106 | /**
107 | * 禁止使用 foo['bar'],必须写成 foo.bar
108 | * @reason 当需要写一系列属性的时候,可以更统一
109 | */
110 | 'dot-notation': 'off',
111 | /**
112 | * 必须使用 === 或 !==,禁止使用 == 或 !=
113 | */
114 | eqeqeq: ['error', 'always'],
115 | /**
116 | * 禁止方向错误的 for 循环
117 | */
118 | 'for-direction': 'error',
119 | /**
120 | * 函数赋值给变量的时候,函数名必须与变量名一致
121 | */
122 | 'func-name-matching': [
123 | 'error',
124 | 'always',
125 | {
126 | includeCommonJSModuleExports: false
127 | }
128 | ],
129 | /**
130 | * 函数必须有名字
131 | */
132 | 'func-names': 'off',
133 | /**
134 | * 必须只使用函数声明或只使用函数表达式
135 | */
136 | 'func-style': 'off',
137 | /**
138 | * getter 必须有返回值,并且禁止返回空
139 | */
140 | 'getter-return': 'error',
141 | /**
142 | * require 必须在全局作用域下
143 | */
144 | 'global-require': 'off',
145 | /**
146 | * setter 和 getter 必须写在一起
147 | */
148 | 'grouped-accessor-pairs': 'off',
149 | /**
150 | * for in 内部必须有 hasOwnProperty
151 | */
152 | 'guard-for-in': 'off',
153 | /**
154 | * callback 中的 err 必须被处理
155 | * @reason 它是通过字符串匹配来判断函数参数 err 的,不准确
156 | */
157 | 'handle-callback-err': 'off',
158 | /**
159 | * 禁止使用指定的标识符
160 | */
161 | 'id-blacklist': 'off',
162 | /**
163 | * 限制变量名长度
164 | */
165 | 'id-length': 'off',
166 | /**
167 | * 限制变量名必须匹配指定的正则表达式
168 | */
169 | 'id-match': 'off',
170 | /**
171 | * 变量必须在定义的时候赋值
172 | */
173 | 'init-declarations': 'off',
174 | /**
175 | * 单行注释必须写在上一行
176 | */
177 | 'line-comment-position': 'off',
178 | /**
179 | * 类的成员之间是否需要空行
180 | * @reason 有时为了紧凑需要挨在一起,有时为了可读性需要空一行
181 | */
182 | 'lines-between-class-members': 'off',
183 | /**
184 | * 限制一个文件中类的数量
185 | */
186 | 'max-classes-per-file': 'off',
187 | /**
188 | * 代码块嵌套的深度禁止超过 5 层
189 | */
190 | 'max-depth': ['error', 5],
191 | /**
192 | * 限制一个文件最多的行数
193 | */
194 | 'max-lines': 'off',
195 | /**
196 | * 限制函数块中的代码行数
197 | */
198 | 'max-lines-per-function': 'off',
199 | /**
200 | * 回调函数嵌套禁止超过 3 层,多了请用 async await 替代
201 | */
202 | 'max-nested-callbacks': ['error', 3],
203 | /**
204 | * 函数的参数禁止超过 3 个
205 | */
206 | 'max-params': ['error', 8],
207 | /**
208 | * 限制函数块中的语句数量
209 | */
210 | 'max-statements': 'off',
211 | /**
212 | * 限制一行中的语句数量
213 | */
214 | 'max-statements-per-line': 'off',
215 | /**
216 | * 约束多行注释的格式
217 | * @reason 能写注释已经不容易了,不需要限制太多
218 | */
219 | 'multiline-comment-style': 'off',
220 | /**
221 | * new 后面的类名必须首字母大写
222 | */
223 | 'new-cap': [
224 | 'error',
225 | {
226 | newIsCap: true,
227 | capIsNew: false,
228 | properties: true
229 | }
230 | ],
231 | /**
232 | * 禁止使用 alert
233 | */
234 | 'no-alert': 'off',
235 | /**
236 | * 禁止使用 Array 构造函数时传入的参数超过一个
237 | * @reason 参数为一个时表示创建一个指定长度的数组,比较常用
238 | * 参数为多个时表示创建一个指定内容的数组,此时可以用数组字面量实现,不必使用构造函数
239 | */
240 | 'no-array-constructor': 'error',
241 | /**
242 | * 禁止将 async 函数做为 new Promise 的回调函数
243 | * @reason 出现这种情况时,一般不需要使用 new Promise 实现异步了
244 | */
245 | 'no-async-promise-executor': 'error',
246 | /**
247 | * 禁止将 await 写在循环里,因为这样就无法同时发送多个异步请求了
248 | * @reason 要求太严格了,有时需要在循环中写 await
249 | */
250 | 'no-await-in-loop': 'off',
251 | /**
252 | * 禁止使用位运算
253 | */
254 | 'no-bitwise': 'off',
255 | /**
256 | * 禁止直接使用 Buffer
257 | * @reason Buffer 构造函数是已废弃的语法
258 | */
259 | 'no-buffer-constructor': 'error',
260 | /**
261 | * 禁止使用 caller 或 callee
262 | * @reason 它们是已废弃的语法
263 | */
264 | 'no-caller': 'error',
265 | /**
266 | * switch 的 case 内有变量定义的时候,必须使用大括号将 case 内变成一个代码块
267 | */
268 | 'no-case-declarations': 'error',
269 | /**
270 | * 禁止对已定义的 class 重新赋值
271 | */
272 | 'no-class-assign': 'error',
273 | /**
274 | * 禁止与负零进行比较
275 | */
276 | 'no-compare-neg-zero': 'error',
277 | /**
278 | * 禁止在测试表达式中使用赋值语句,除非这个赋值语句被括号包起来了
279 | */
280 | 'no-cond-assign': ['error', 'except-parens'],
281 | /**
282 | * 禁止使用 console
283 | */
284 | 'no-console': 'off',
285 | /**
286 | * 禁止对使用 const 定义的常量重新赋值
287 | */
288 | 'no-const-assign': 'error',
289 | /**
290 | * 禁止将常量作为分支条件判断中的测试表达式,但允许作为循环条件判断中的测试表达式
291 | */
292 | 'no-constant-condition': [
293 | 'error',
294 | {
295 | checkLoops: false
296 | }
297 | ],
298 | /**
299 | * 禁止在构造函数中返回值
300 | */
301 | 'no-constructor-return': 'off',
302 | /**
303 | * 禁止使用 continue
304 | */
305 | 'no-continue': 'off',
306 | /**
307 | * 禁止在正则表达式中出现 Ctrl 键的 ASCII 表示,即禁止使用 /\x1f/
308 | * @reason 几乎不会遇到这种场景
309 | */
310 | 'no-control-regex': 'off',
311 | /**
312 | * 禁止使用 debugger
313 | */
314 | 'no-debugger': 'error',
315 | /**
316 | * 禁止对一个变量使用 delete
317 | * @reason 编译阶段就会报错了
318 | */
319 | 'no-delete-var': 'off',
320 | /**
321 | * 禁止在正则表达式中出现形似除法操作符的开头,如 let a = /=foo/
322 | * @reason 有代码高亮的话,在阅读这种代码时,也完全不会产生歧义或理解上的困难
323 | */
324 | 'no-div-regex': 'off',
325 | /**
326 | * 禁止在函数参数中出现重复名称的参数
327 | * @reason 编译阶段就会报错了
328 | */
329 | 'no-dupe-args': 'off',
330 | /**
331 | * 禁止重复定义类的成员
332 | */
333 | 'no-dupe-class-members': 'error',
334 | /**
335 | * 禁止 if else 的条件判断中出现重复的条件
336 | */
337 | 'no-dupe-else-if': 'off',
338 | /**
339 | * 禁止在对象字面量中出现重复的键名
340 | */
341 | 'no-dupe-keys': 'error',
342 | /**
343 | * 禁止在 switch 语句中出现重复测试表达式的 case
344 | */
345 | 'no-duplicate-case': 'error',
346 | /**
347 | * 禁止重复导入模块
348 | */
349 | 'no-duplicate-imports': 'error',
350 | /**
351 | * 禁止在 else 内使用 return,必须改为提前结束
352 | * @reason else 中使用 return 可以使代码结构更清晰
353 | */
354 | 'no-else-return': 'off',
355 | /**
356 | * 禁止出现空代码块,允许 catch 为空代码块
357 | */
358 | 'no-empty': [
359 | 'error',
360 | {
361 | allowEmptyCatch: true
362 | }
363 | ],
364 | /**
365 | * 禁止在正则表达式中使用空的字符集 []
366 | */
367 | 'no-empty-character-class': 'error',
368 | /**
369 | * 不允许有空函数
370 | * @reason 有时需要将一个空函数设置为某个项的默认值
371 | */
372 | 'no-empty-function': 'off',
373 | /**
374 | * 禁止解构赋值中出现空 {} 或 []
375 | */
376 | 'no-empty-pattern': 'error',
377 | /**
378 | * 禁止使用 foo == null,必须使用 foo === null
379 | */
380 | 'no-eq-null': 'error',
381 | /**
382 | * 禁止使用 eval
383 | */
384 | 'no-eval': 'error',
385 | /**
386 | * 禁止将 catch 的第一个参数 error 重新赋值
387 | */
388 | 'no-ex-assign': 'error',
389 | /**
390 | * 禁止修改原生对象
391 | * @reason 修改原生对象可能会与将来版本的 js 冲突
392 | */
393 | 'no-extend-native': 'error',
394 | /**
395 | * 禁止出现没必要的 bind
396 | */
397 | 'no-extra-bind': 'error',
398 | /**
399 | * 禁止不必要的布尔类型转换
400 | */
401 | 'no-extra-boolean-cast': 'error',
402 | /**
403 | * 禁止出现没必要的 label
404 | * @reason 已经禁止使用 label 了
405 | */
406 | 'no-extra-label': 'off',
407 | /**
408 | * switch 的 case 内必须有 break, return 或 throw,空的 case 除外
409 | */
410 | 'no-fallthrough': 'error',
411 | /**
412 | * 禁止将一个函数声明重新赋值
413 | */
414 | 'no-func-assign': 'error',
415 | /**
416 | * 禁止对全局变量赋值
417 | */
418 | 'no-global-assign': 'error',
419 | /**
420 | * 禁止使用 ~+ 等难以理解的类型转换,仅允许使用 !!
421 | */
422 | 'no-implicit-coercion': [
423 | 'error',
424 | {
425 | allow: ['!!']
426 | }
427 | ],
428 | /**
429 | * 禁止在全局作用域下定义变量或申明函数
430 | * @reason 模块化之后,不会出现这种在全局作用域下定义变量的情况
431 | */
432 | 'no-implicit-globals': 'off',
433 | /**
434 | * 禁止在 setTimeout 或 setInterval 中传入字符串
435 | */
436 | 'no-implied-eval': 'error',
437 | /**
438 | * 禁止对导入的模块进行赋值
439 | */
440 | 'no-import-assign': 'off',
441 | /**
442 | * 禁止在代码后添加单行注释
443 | */
444 | 'no-inline-comments': 'off',
445 | /**
446 | * 禁止在 if 代码块内出现函数声明
447 | */
448 | 'no-inner-declarations': ['error', 'both'],
449 | /**
450 | * 禁止在 RegExp 构造函数中出现非法的正则表达式
451 | */
452 | 'no-invalid-regexp': 'error',
453 | /**
454 | * 禁止在类之外的地方使用 this
455 | * @reason 只允许在 class 中使用 this
456 | */
457 | 'no-invalid-this': 'error',
458 | /**
459 | * 禁止使用特殊空白符(比如全角空格),除非是出现在字符串、正则表达式或模版字符串中
460 | */
461 | 'no-irregular-whitespace': [
462 | 'error',
463 | {
464 | skipStrings: true,
465 | skipComments: false,
466 | skipRegExps: true,
467 | skipTemplates: true
468 | }
469 | ],
470 | /**
471 | * 禁止使用 __iterator__
472 | * @reason __iterator__ 是一个已废弃的属性
473 | * 使用 [Symbol.iterator] 替代它
474 | */
475 | 'no-iterator': 'error',
476 | /**
477 | * 禁止 label 名称与已定义的变量重复
478 | * @reason 已经禁止使用 label 了
479 | */
480 | 'no-label-var': 'off',
481 | /**
482 | * 禁止使用 label
483 | */
484 | 'no-labels': 'error',
485 | /**
486 | * 禁止使用没必要的 {} 作为代码块
487 | */
488 | 'no-lone-blocks': 'error',
489 | /**
490 | * 禁止 else 中只有一个单独的 if
491 | * @reason 单独的 if 可以把逻辑表达的更清楚
492 | */
493 | 'no-lonely-if': 'off',
494 | /**
495 | * 禁止在循环内的函数内部出现循环体条件语句中定义的变量
496 | * @reason 使用 let 就已经解决了这个问题了
497 | */
498 | 'no-loop-func': 'off',
499 | /**
500 | * 禁止使用 magic numbers
501 | */
502 | 'no-magic-numbers': 'off',
503 | /**
504 | * 禁止正则表达式中使用肉眼无法区分的特殊字符
505 | * @reason 某些特殊字符很难看出差异,最好不要在正则中使用
506 | */
507 | 'no-misleading-character-class': 'error',
508 | /**
509 | * 相同类型的 require 必须放在一起
510 | */
511 | 'no-mixed-requires': 'off',
512 | /**
513 | * 禁止连续赋值,比如 foo = bar = 1
514 | */
515 | 'no-multi-assign': 'off',
516 | /**
517 | * 禁止使用 \ 来换行字符串
518 | */
519 | 'no-multi-str': 'error',
520 | /**
521 | * 禁止 if 里有否定的表达式
522 | * @reason 否定的表达式可以把逻辑表达的更清楚
523 | */
524 | 'no-negated-condition': 'off',
525 | /**
526 | * 禁止使用嵌套的三元表达式,比如 a ? b : c ? d : e
527 | */
528 | 'no-nested-ternary': 'off',
529 | /**
530 | * 禁止直接 new 一个类而不赋值
531 | * @reason new 应该作为创建一个类的实例的方法,所以不能不赋值
532 | */
533 | 'no-new': 'error',
534 | /**
535 | * 禁止使用 new Function
536 | * @reason 这和 eval 是等价的
537 | */
538 | 'no-new-func': 'error',
539 | /**
540 | * 禁止直接 new Object
541 | */
542 | 'no-new-object': 'error',
543 | /**
544 | * 禁止直接 new require('foo')
545 | */
546 | 'no-new-require': 'error',
547 | /**
548 | * 禁止使用 new 来生成 Symbol
549 | */
550 | 'no-new-symbol': 'error',
551 | /**
552 | * 禁止使用 new 来生成 String, Number 或 Boolean
553 | */
554 | 'no-new-wrappers': 'error',
555 | /**
556 | * 禁止将 Math, JSON 或 Reflect 直接作为函数调用
557 | */
558 | 'no-obj-calls': 'error',
559 | /**
560 | * 禁止使用 0 开头的数字表示八进制数
561 | * @reason 编译阶段就会报错了
562 | */
563 | 'no-octal': 'off',
564 | /**
565 | * 禁止使用八进制的转义符
566 | * @reason 编译阶段就会报错了
567 | */
568 | 'no-octal-escape': 'off',
569 | /**
570 | * 禁止对函数的参数重新赋值
571 | */
572 | 'no-param-reassign': 'off',
573 | /**
574 | * 禁止对 __dirname 或 __filename 使用字符串连接
575 | * @reason 不同平台下的路径符号不一致,建议使用 path 处理平台差异性
576 | */
577 | 'no-path-concat': 'error',
578 | /**
579 | * 禁止使用 ++ 或 --
580 | */
581 | 'no-plusplus': 'off',
582 | /**
583 | * 禁止使用 process.env.NODE_ENV
584 | */
585 | 'no-process-env': 'off',
586 | /**
587 | * 禁止使用 process.exit(0)
588 | */
589 | 'no-process-exit': 'off',
590 | /**
591 | * 禁止使用 __proto__
592 | * @reason __proto__ 是已废弃的语法
593 | */
594 | 'no-proto': 'error',
595 | /**
596 | * 禁止使用 hasOwnProperty, isPrototypeOf 或 propertyIsEnumerable
597 | * @reason hasOwnProperty 比较常用
598 | */
599 | 'no-prototype-builtins': 'off',
600 | /**
601 | * 禁止重复定义变量
602 | * @reason 禁用 var 之后,编译阶段就会报错了
603 | */
604 | 'no-redeclare': 'off',
605 | /**
606 | * 禁止在正则表达式中出现连续的空格
607 | */
608 | 'no-regex-spaces': 'error',
609 | /**
610 | * 禁止使用指定的全局变量
611 | */
612 | 'no-restricted-globals': 'off',
613 | /**
614 | * 禁止导入指定的模块
615 | */
616 | 'no-restricted-imports': 'off',
617 | /**
618 | * 禁止使用指定的模块
619 | */
620 | 'no-restricted-modules': 'off',
621 | /**
622 | * 禁止使用指定的对象属性
623 | */
624 | 'no-restricted-properties': 'off',
625 | /**
626 | * 禁止使用指定的语法
627 | */
628 | 'no-restricted-syntax': 'off',
629 | /**
630 | * 禁止在 return 语句里赋值
631 | */
632 | 'no-return-assign': ['error', 'always'],
633 | /**
634 | * 禁止在 return 语句里使用 await
635 | */
636 | 'no-return-await': 'error',
637 | /**
638 | * 禁止出现 location.href = 'javascript:void(0)';
639 | * @reason 有些场景下还是需要用到这个
640 | */
641 | 'no-script-url': 'off',
642 | /**
643 | * 禁止将自己赋值给自己
644 | */
645 | 'no-self-assign': 'error',
646 | /**
647 | * 禁止将自己与自己比较
648 | */
649 | 'no-self-compare': 'error',
650 | /**
651 | * 禁止使用逗号操作符
652 | */
653 | 'no-sequences': 'error',
654 | /**
655 | * 禁止 setter 有返回值
656 | */
657 | 'no-setter-return': 'off',
658 | /**
659 | * 禁止变量名与上层作用域内的已定义的变量重复
660 | * @reason 很多时候函数的形参和传参是同名的
661 | */
662 | 'no-shadow': 'off',
663 | /**
664 | * 禁止使用保留字作为变量名
665 | */
666 | 'no-shadow-restricted-names': 'error',
667 | /**
668 | * 禁止在数组中出现连续的逗号
669 | */
670 | 'no-sparse-arrays': 'error',
671 | /**
672 | * 禁止使用 node 中的同步的方法,比如 fs.readFileSync
673 | */
674 | 'no-sync': 'off',
675 | /**
676 | * 禁止在普通字符串中出现模版字符串里的变量形式
677 | */
678 | 'no-template-curly-in-string': 'error',
679 | /**
680 | * 禁止使用三元表达式
681 | */
682 | 'no-ternary': 'off',
683 | /**
684 | * 禁止在 super 被调用之前使用 this 或 super
685 | */
686 | 'no-this-before-super': 'error',
687 | /**
688 | * 禁止 throw 字面量,必须 throw 一个 Error 对象
689 | */
690 | 'no-throw-literal': 'error',
691 | /**
692 | * 禁止使用未定义的变量
693 | */
694 | // 'no-undef': 'error',
695 | /**
696 | * 禁止将 undefined 赋值给变量
697 | */
698 | // 'no-undef-init': 'error',
699 | /**
700 | * 禁止使用 undefined
701 | */
702 | // 'no-undefined': 'off',
703 | /**
704 | * 禁止变量名出现下划线
705 | */
706 | 'no-underscore-dangle': 'off',
707 | /**
708 | * 循环内必须对循环条件中的变量有修改
709 | */
710 | 'no-unmodified-loop-condition': 'error',
711 | /**
712 | * 必须使用 !a 替代 a ? false : true
713 | * @reason 后者表达的更清晰
714 | */
715 | 'no-unneeded-ternary': 'off',
716 | /**
717 | * 禁止在 return, throw, break 或 continue 之后还有代码
718 | */
719 | 'no-unreachable': 'error',
720 | /**
721 | * 禁止在 finally 中出现 return, throw, break 或 continue
722 | * @reason finally 中的语句会在 try 之前执行
723 | */
724 | 'no-unsafe-finally': 'error',
725 | /**
726 | * 禁止在 in 或 instanceof 操作符的左侧变量前使用感叹号
727 | */
728 | 'no-unsafe-negation': 'error',
729 | /**
730 | * 禁止无用的表达式
731 | */
732 | 'no-unused-expressions': [
733 | 'error',
734 | {
735 | allowShortCircuit: true,
736 | allowTernary: true,
737 | allowTaggedTemplates: true
738 | }
739 | ],
740 | /**
741 | * 禁止出现没用到的 label
742 | * @reason 已经禁止使用 label 了
743 | */
744 | 'no-unused-labels': 'off',
745 | /**
746 | * 已定义的变量必须使用
747 | */
748 | 'no-unused-vars': 'off',
749 | // [
750 | // 'warn',
751 | // {
752 | // vars: 'all',
753 | // args: 'none',
754 | // ignoreRestSiblings: false,
755 | // caughtErrors: 'none'
756 | // }
757 | // ],
758 | /**
759 | * 变量必须先定义后使用
760 | */
761 | 'no-use-before-define': 'off',
762 | // [
763 | // 'error',
764 | // {
765 | // variables: false,
766 | // functions: false,
767 | // classes: false
768 | // }
769 | // ],
770 | /**
771 | * 禁止出现没必要的 call 或 apply
772 | */
773 | 'no-useless-call': 'error',
774 | /**
775 | * 禁止在 catch 中仅仅只是把错误 throw 出去
776 | * @reason 这样的 catch 是没有意义的,等价于直接执行 try 里的代码
777 | */
778 | 'no-useless-catch': 'error',
779 | /**
780 | * 禁止出现没必要的计算键名
781 | */
782 | 'no-useless-computed-key': 'error',
783 | /**
784 | * 禁止出现没必要的字符串连接
785 | */
786 | 'no-useless-concat': 'error',
787 | /**
788 | * 禁止出现没必要的 constructor
789 | */
790 | 'no-useless-constructor': 'error',
791 | /**
792 | * 禁止出现没必要的转义
793 | * @reason 转义可以使代码更易懂
794 | */
795 | 'no-useless-escape': 'off',
796 | /**
797 | * 禁止解构赋值时出现同样名字的的重命名,比如 let { foo: foo } = bar;
798 | */
799 | 'no-useless-rename': 'error',
800 | /**
801 | * 禁止没必要的 return
802 | */
803 | 'no-useless-return': 'off',
804 | /**
805 | * 禁止使用 var
806 | */
807 | 'no-var': 'warn',
808 | /**
809 | * 禁止使用 void
810 | */
811 | 'no-void': 'error',
812 | /**
813 | * 禁止注释中出现 TODO 和 FIXME
814 | */
815 | 'no-warning-comments': 'off',
816 | /**
817 | * 禁止使用 with
818 | * @reason 编译阶段就会报错了
819 | */
820 | 'no-with': 'off',
821 | /**
822 | * 必须使用 a = {b} 而不是 a = {b: b}
823 | * @reason 有时后者可以使代码结构更清晰
824 | */
825 | 'object-shorthand': 'off',
826 | /**
827 | * 禁止变量申明时用逗号一次申明多个
828 | */
829 | 'one-var': ['error', 'never'],
830 | /**
831 | * 必须使用 x = x + y 而不是 x += y
832 | */
833 | 'operator-assignment': 'off',
834 | /**
835 | * 限制语句之间的空行规则,比如变量定义完之后必须要空行
836 | */
837 | 'padding-line-between-statements': 'off',
838 | /**
839 | * 申明后不再被修改的变量必须使用 const 来申明
840 | */
841 | 'prefer-const': 'off',
842 | /**
843 | * 必须使用解构赋值
844 | */
845 | 'prefer-destructuring': 'off',
846 | /**
847 | * 使用 ES2016 的语法 ** 替代 Math.pow
848 | */
849 | 'prefer-exponentiation-operator': 'off',
850 | /**
851 | * 使用 ES2018 中的正则表达式命名组
852 | * @reason 正则表达式已经较难理解了,没必要强制加上命名组
853 | */
854 | 'prefer-named-capture-group': 'off',
855 | /**
856 | * 必须使用 0b11111011 而不是 parseInt()
857 | */
858 | 'prefer-numeric-literals': 'off',
859 | /**
860 | * 必须使用 ... 而不是 Object.assign,除非 Object.assign 的第一个参数是一个变量
861 | */
862 | 'prefer-object-spread': 'error',
863 | /**
864 | * Promise 的 reject 中必须传入 Error 对象,而不是字面量
865 | */
866 | 'prefer-promise-reject-errors': 'error',
867 | /**
868 | * 优先使用正则表达式字面量,而不是 RegExp 构造函数
869 | */
870 | 'prefer-regex-literals': 'off',
871 | /**
872 | * 必须使用 ...args 而不是 arguments
873 | */
874 | 'prefer-rest-params': 'off',
875 | /**
876 | * 必须使用 ... 而不是 apply,比如 foo(...args)
877 | */
878 | 'prefer-spread': 'off',
879 | /**
880 | * 必须使用模版字符串而不是字符串连接
881 | */
882 | 'prefer-template': 'off',
883 | /**
884 | * parseInt 必须传入第二个参数
885 | */
886 | radix: 'error',
887 | /**
888 | * 禁止将 await 或 yield 的结果做为运算符的后面项
889 | * @reason 这样会导致不符合预期的结果
890 | * https://github.com/eslint/eslint/issues/11899
891 | * 在上面 issue 修复之前,关闭此规则
892 | */
893 | 'require-atomic-updates': 'off',
894 | /**
895 | * async 函数中必须存在 await 语句
896 | */
897 | 'require-await': 'off',
898 | /**
899 | * 正则表达式中必须要加上 u 标志
900 | */
901 | 'require-unicode-regexp': 'off',
902 | /**
903 | * generator 函数内必须有 yield
904 | */
905 | 'require-yield': 'error',
906 | /**
907 | * 导入必须按规则排序
908 | */
909 | 'sort-imports': 'off',
910 | /**
911 | * 对象字面量的键名必须排好序
912 | */
913 | 'sort-keys': 'off',
914 | /**
915 | * 变量申明必须排好序
916 | */
917 | 'sort-vars': 'off',
918 | /**
919 | * 注释的斜线或 * 后必须有空格
920 | */
921 | 'spaced-comment': [
922 | 'error',
923 | 'always',
924 | {
925 | block: {
926 | exceptions: ['*'],
927 | balanced: true
928 | }
929 | }
930 | ],
931 | /**
932 | * 禁止使用 'strict';
933 | */
934 | strict: ['off', 'never'],
935 | /**
936 | * 创建 Symbol 时必须传入参数
937 | */
938 | 'symbol-description': 'error',
939 | /**
940 | * 必须使用 isNaN(foo) 而不是 foo === NaN
941 | */
942 | 'use-isnan': 'error',
943 | /**
944 | * typeof 表达式比较的对象必须是 'undefined', 'object', 'boolean', 'number', 'string', 'function', 'symbol', 或 'bigint'
945 | */
946 | 'valid-typeof': 'error',
947 | /**
948 | * var 必须在作用域的最前面
949 | */
950 | 'vars-on-top': 'off',
951 | /**
952 | * 必须使用 if (foo === 5) 而不是 if (5 === foo)
953 | */
954 | yoda: [
955 | 'error',
956 | 'never',
957 | {
958 | onlyEquality: true
959 | }
960 | ],
961 |
962 | /** ********************************** react/jsx相关配置 ******************************************/
963 | /**
964 | * 布尔值类型的 propTypes 的 name 必须为 is 或 has 开头
965 | * @reason 类型相关的约束交给 TypeScript
966 | */
967 | 'react/boolean-prop-naming': 'off',
968 | /**
969 | *