= ({
11 | children,
12 | authority,
13 | noMatch = null,
14 | }) => {
15 | const childrenRender: React.ReactNode =
16 | typeof children === 'undefined' ? null : children;
17 | const dom = checkPermissions(authority, childrenRender, noMatch);
18 | return <>{dom}>;
19 | };
20 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/scripts/webpack/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const merge = require('webpack-merge');
3 |
4 | const { devConfig } = require('../../../../scripts/webpack/webpack.config');
5 |
6 | const themeConfig = require('./webpack.config.theme');
7 |
8 | const config = merge(devConfig, themeConfig, {
9 | entry: {
10 | index: [
11 | 'react-hot-loader/patch',
12 | path.resolve(__dirname, '../../src/index'),
13 | ],
14 | },
15 | devServer: {
16 | contentBase: path.resolve(__dirname, '../../public'),
17 | hot: false,
18 | },
19 | });
20 |
21 | module.exports = config;
22 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/__test__/authority.test.ts:
--------------------------------------------------------------------------------
1 | import { getAuthority } from '../authority';
2 |
3 | describe('getAuthority should be strong', () => {
4 | it('string', () => {
5 | expect(getAuthority('admin')).toEqual(['admin']);
6 | });
7 | it('array with double quotes', () => {
8 | expect(getAuthority('"admin"')).toEqual(['admin']);
9 | });
10 | it('array with single item', () => {
11 | expect(getAuthority('["admin"]')).toEqual(['admin']);
12 | });
13 | it('array with multiple items', () => {
14 | expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/containers/IntervalComponent/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export abstract class IntervalComponent extends React.PureComponent<
4 | P,
5 | S
6 | > {
7 | intervalHandler: any;
8 | interval: number;
9 |
10 | componentDidMount() {
11 | this.onInterval();
12 |
13 | this.intervalHandler = setInterval(() => {
14 | this.onInterval();
15 | }, this.interval || 15 * 1000);
16 | }
17 |
18 | componentWillUnmount() {
19 | if (this.intervalHandler) {
20 | clearInterval(this.intervalHandler);
21 | }
22 | }
23 |
24 | onInterval: Function;
25 | }
26 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/electron-builder.yml:
--------------------------------------------------------------------------------
1 | appId: com.biz
2 | buildVersion: 0.0.1
3 | productName: Biz App
4 | artifactName: ${productName}-${version}.${ext}
5 | extraResources: extra/
6 | directories:
7 | output: './out'
8 | files:
9 | - '**/*'
10 | - '!**/*.map'
11 | mac:
12 | target: pkg
13 | win:
14 | target: nsis
15 | requestedExecutionLevel: requireAdministrator
16 | nsis:
17 | oneClick: false
18 | perMachine: true
19 | allowToChangeInstallationDirectory: true
20 | installerLanguages: zh-CN
21 | language: 2052
22 | menuCategory: Biz App
23 | deleteAppDataOnUninstall: true
24 | publish:
25 | provider: generic
26 | url: http://tool.bizapp
27 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/malfunction.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/InstallableAppSeriesView/index.scss:
--------------------------------------------------------------------------------
1 | .product-line-container {
2 | width: 100%;
3 | border-bottom: 1px solid #f1f1f1;
4 | padding: 10px 60px 10px 40px;
5 | -webkit-app-region: no-drag;
6 |
7 | &:last-child {
8 | border: none;
9 | }
10 |
11 | .product-line-name {
12 | font-size: 16px;
13 | color: #2e72b8;
14 | letter-spacing: 0.57px;
15 | line-height: 22px;
16 | margin: 0 0 15px;
17 | font-weight: 300;
18 | }
19 |
20 | .product-list {
21 | display: grid;
22 | grid-template-columns: repeat(5, 1fr);
23 | grid-column-gap: 68px;
24 | grid-row-gap: 12px;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/src/render/App.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
25 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/hooks/state.ts:
--------------------------------------------------------------------------------
1 | import { merge } from 'lodash';
2 | import { useCallback, useState } from 'react';
3 |
4 | import { DeepPartial } from '@/schema/helpers';
5 |
6 | export const useLiteState = (
7 | initialState?: T | (() => T),
8 | ): [T, (newState: DeepPartial) => void] => {
9 | const [state, setState] = useState(initialState);
10 | const setLiteState = useCallback(
11 | (newState: DeepPartial | null) => {
12 | if (newState == null) {
13 | setState(newState as null);
14 | return;
15 | }
16 | setState(merge({}, state, newState));
17 | },
18 | [state],
19 | );
20 | return [state, setLiteState];
21 | };
22 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/docker/build-locally-release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # 本地构建并推送项目镜像
4 | #
5 | # Globals:
6 | # DOCKER_REGISTRY_SERVER
7 | # TAG
8 |
9 | set -e
10 |
11 | cd $(dirname $0)/../..
12 |
13 | PACKAGE_VERSION=$(cat package.json | grep version | head -1 | awk -F= "{ print $2 }" | sed 's/[version:,\",]//g' | tr -d '[[:space:]]')
14 |
15 | DOCKER_REGISTRY_SERVER=${DOCKER_REGISTRY_SERVER:=registry.yourbiz.com}
16 | IMAGE=${DOCKER_REGISTRY_SERVER}/m-fe-rtw
17 | TAG=${PACKAGE_VERSION:=latest}
18 |
19 | yarn build
20 |
21 | echo "[*] Finished building"
22 |
23 | docker build --tag $IMAGE:$TAG -f scripts/docker/Dockerfile.local ./build
24 |
25 | echo "[*] Pushing $IMAGE:$TAG"
26 |
27 | docker push $IMAGE:$TAG
28 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/defectives.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/shared/components/Copyright/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames';
2 | import React from 'react';
3 |
4 | import styles from './index.less';
5 |
6 | export interface CopyrightProps {
7 | className?: string;
8 | style?: Record;
9 | }
10 |
11 | export const Copyright = ({ className, style }: CopyrightProps) => {
12 | return (
13 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/env/dev.ts:
--------------------------------------------------------------------------------
1 | import MobileDetect from 'mobile-detect';
2 |
3 | window.gConfig = {};
4 |
5 | // 如果是在本地开发,则默认为开发环境服务器
6 | export const HOST = __DEV__
7 | ? 'https://api.unionfab.com'
8 | : window.gConfig.HOST
9 | ? `https://${window.gConfig.HOST}`
10 | : 'https://api.test.unionfab.com';
11 |
12 | export const UFI_HOST = `https://${(window.gConfig || {}).UFI_HOST}`;
13 |
14 | export const NODE_HOST = __DEV__
15 | ? 'https://node-api.unionfab.com'
16 | : window.gConfig.HOST
17 | ? `https://${window.gConfig.NODE_HOST}`
18 | : 'https://node-api.test.unionfab.com';
19 |
20 | export const WITH_AUTH = true;
21 |
22 | const md = new MobileDetect(window.navigator.userAgent);
23 |
24 | export const isMobile = !!md.mobile();
25 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | build/
3 | dist/
4 | node_modules/
5 | out/
6 |
7 | # Logs
8 | logs
9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Yarn Integrity file
15 | .yarn-integrity
16 |
17 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # Optional npm cache directory
21 | .npm
22 |
23 | # Optional eslint cache
24 | .eslintcache
25 |
26 | # Windows thumbnail cache files
27 | Thumbs.db
28 |
29 | # OS X files
30 | .DS_Store
31 | ._*
32 |
33 | # Node files
34 | node_modules
35 |
36 | # OS junk files
37 | .DS_Store
38 | .stylelintcache
39 | .eslintcache
40 |
41 | # Project specific stuff
42 | .cache-loader
43 | @coverage
44 | *.log
45 | dist
46 | build
47 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/CopyBlock/index.less:
--------------------------------------------------------------------------------
1 | .copy-block {
2 | position: fixed;
3 | right: 80px;
4 | bottom: 40px;
5 | z-index: 99;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | justify-content: center;
10 | width: 40px;
11 | height: 40px;
12 | font-size: 20px;
13 | background: #fff;
14 | border-radius: 40px;
15 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14),
16 | 0 1px 10px 0 rgba(0, 0, 0, 0.12);
17 | cursor: pointer;
18 | }
19 |
20 | .copy-block-view {
21 | position: relative;
22 | .copy-block-code {
23 | display: inline-block;
24 | margin: 0 0.2em;
25 | padding: 0.2em 0.4em 0.1em;
26 | font-size: 85%;
27 | border-radius: 3px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/typings/global.d.ts:
--------------------------------------------------------------------------------
1 | import 'dayjs/plugin/relativeTime';
2 | import HtmlWebpackPlugin from 'html-webpack-plugin';
3 | import { Plugin } from 'webpack';
4 | import * as S from 'ufc-schema';
5 |
6 | declare global {
7 | const __DEV__: boolean;
8 |
9 | interface Window {
10 | Sentry: any;
11 | System: SystemJSLoader.System;
12 | gConfig: {
13 | pwa?: false;
14 | user?: S.User;
15 | HOST?: string;
16 | UFI_HOST?: string;
17 | NODE_HOST?: string;
18 | };
19 | }
20 | }
21 |
22 | declare module 'html-webpack-plugin' {
23 | namespace HtmlWebpackPlugin {
24 | interface Options {
25 | alwaysWriteToDisk?: boolean;
26 | inlineSource?: string | RegExp;
27 | }
28 | }
29 |
30 | export = HtmlWebpackPlugin;
31 | }
32 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "node": true
5 | },
6 | "extends": "airbnb",
7 | "rules": {
8 | "no-unused-vars": "warn",
9 | "max-len": [
10 | "error",
11 | {
12 | "ignoreUrls": true
13 | }
14 | ],
15 | "import/no-extraneous-dependencies": [
16 | "error",
17 | {
18 | "devDependencies": [
19 | "webpack.*.js",
20 | "test/**/*.js"
21 | ]
22 | }
23 | ],
24 | "sort-imports": 2,
25 | "import/prefer-default-export": 0,
26 | "import/no-default-export": 2,
27 | "import/no-named-default": 0,
28 | "react/jsx-no-bind": false
29 | },
30 | "plugins": [
31 | "import"
32 | ],
33 | "settings": {
34 | "import/resolver": "webpack"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/HeaderSearch/index.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .headerSearch {
4 | :global(.anticon-search) {
5 | font-size: 16px;
6 | cursor: pointer;
7 | }
8 | .input {
9 | width: 0;
10 | background: transparent;
11 | border-radius: 0;
12 | transition: width 0.3s, margin-left 0.3s;
13 | :global(.ant-select-selection) {
14 | background: transparent;
15 | }
16 | input {
17 | padding-right: 0;
18 | padding-left: 0;
19 | border: 0;
20 | box-shadow: none !important;
21 | }
22 | &,
23 | &:hover,
24 | &:focus {
25 | border-bottom: 1px solid @border-color-base;
26 | }
27 | &.show {
28 | width: 210px;
29 | margin-left: 8px;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/HeaderDropdown/index.tsx:
--------------------------------------------------------------------------------
1 | import { Dropdown } from 'antd';
2 | import { DropDownProps } from 'antd/es/dropdown';
3 | import classNames from 'classnames';
4 | import * as React from 'react';
5 |
6 | import styles from './index.less';
7 |
8 | export interface HeaderDropdownProps extends DropDownProps {
9 | overlayClassName?: string;
10 | placement?:
11 | | 'bottomLeft'
12 | | 'bottomRight'
13 | | 'topLeft'
14 | | 'topCenter'
15 | | 'topRight'
16 | | 'bottomCenter';
17 | }
18 |
19 | const HeaderDropdown: React.FC = ({
20 | overlayClassName: cls,
21 | ...restProps
22 | }) => (
23 |
27 | );
28 |
29 | export default HeaderDropdown;
30 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/exception/500/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Result } from 'antd';
2 | import * as React from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | import { formatMessage } from '@/i18n';
6 |
7 | export default () => (
8 |
20 |
26 |
27 | }
28 | />
29 | );
30 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/extra/regedit/vbs/regDeleteKey.wsf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/Menu/index.scss:
--------------------------------------------------------------------------------
1 | .menu-container {
2 | width: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | font-weight: 300;
6 | -webkit-app-region: no-drag;
7 | }
8 |
9 | .memu-item-container {
10 | width: 100%;
11 | height: 36px;
12 | cursor: pointer;
13 | transition: all 0.2s ease;
14 | color: #fff;
15 | margin-bottom: 18px;
16 |
17 | &:last-child {
18 | margin-bottom: 0;
19 | }
20 |
21 | .menu-item-content {
22 | max-width: 90px;
23 | height: 100%;
24 | margin: 0 auto;
25 | display: flex;
26 | justify-content: space-between;
27 | align-items: center;
28 | letter-spacing: 0.5px;
29 | }
30 | }
31 |
32 | .memu-item-container.menu-item-selected,
33 | .memu-item-container:hover {
34 | background-color: #fff;
35 | color: #2e72b8;
36 | }
37 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/exception/403/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Result } from 'antd';
2 | import * as React from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | import { formatMessage } from '@/i18n';
6 |
7 | export const Exception403 = () => (
8 |
20 |
26 |
27 | }
28 | />
29 | );
30 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/scripts/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CopyPkgJsonPlugin = require('copy-pkg-json-webpack-plugin');
3 | const merge = require('webpack-merge');
4 |
5 | const config = merge(
6 | require('@m-fe/webpack-config/webpack.config.node')({
7 | rootPath: path.resolve(__dirname, '../../'),
8 | }),
9 | {
10 | target: 'electron-main',
11 | node: {
12 | __dirname: false,
13 | __filename: false,
14 | },
15 | plugins: [
16 | new CopyPkgJsonPlugin({
17 | remove: ['scripts', 'devDependencies', 'build'],
18 | replace: {
19 | main: './index.js',
20 | scripts: { start: 'electron ./index.js' },
21 | postinstall: 'electron-builder install-app-deps',
22 | },
23 | }),
24 | ],
25 | },
26 | );
27 |
28 | module.exports = config;
29 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/src/main/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller } from '@nestjs/common'
2 | import { IpcHandle, Window } from '@doubleshot/nest-electron'
3 | import { Payload } from '@nestjs/microservices'
4 | import { type Observable, of } from 'rxjs'
5 | import type { BrowserWindow } from 'electron'
6 | import { AppService } from './app.service'
7 |
8 | @Controller()
9 | export class AppController {
10 | constructor(
11 | private readonly appService: AppService,
12 | @Window() private readonly mainWin: BrowserWindow,
13 | ) { }
14 |
15 | @IpcHandle('msg')
16 | public handleSendMsg(@Payload() msg: string): Observable {
17 | const { webContents } = this.mainWin
18 | webContents.send('reply-msg', 'this is msg from webContents.send')
19 | return of(`The main process received your message: ${msg} at time: ${this.appService.getTime()}`)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/user/ducks/user.ts:
--------------------------------------------------------------------------------
1 | import { createActions, handleActions } from 'redux-actions';
2 | import { handle } from 'redux-pack-fsa';
3 | import * as S from 'ufc-schema';
4 |
5 | import { getUsers } from '@/apis';
6 |
7 | export interface IState {
8 | users: S.User[];
9 | }
10 |
11 | const initialState: IState = {
12 | users: [],
13 | };
14 |
15 | export const actions = createActions({
16 | async loadUsers() {
17 | return getUsers();
18 | },
19 | });
20 |
21 | export const userActions = actions;
22 |
23 | export default handleActions(
24 | {
25 | [actions.loadUsers.toString()](state: IState, action) {
26 | const { payload } = action;
27 |
28 | return handle(state, action, {
29 | success: (prevState: IState) => ({ ...prevState, users: payload }),
30 | });
31 | },
32 | },
33 |
34 | initialState,
35 | );
36 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | DOCKER_DRIVER: overlay2
3 | EFF_NO_LINK_RULES: 'true'
4 | GIT_CLEAN_FLAGS: -ffdx -e /dist/
5 | NODE_OPTIONS: --max-old-space-size=2048
6 | PARSER_NO_WATCH: 'true'
7 |
8 | build image:
9 | only:
10 | - dev
11 | - master
12 | stage: build
13 | image: docker:git
14 | services:
15 | - name: docker:dind
16 | before_script:
17 | - docker info
18 | - docker login ${DOCKER_REGISTRY_SERVER} -u ${DOCKER_REGISTRY_USER} -p ${DOCKER_REGISTRY_PASSWORD}
19 | script:
20 | # - if [ "master" == "$CI_COMMIT_REF_NAME" ]; then export IMAGE_SUFFIX="-prod"; fi
21 | - export IMAGE=${DOCKER_REGISTRY_SERVER}/m-fe-rtw
22 | # - docker build --tag $IMAGE:$CI_COMMIT_SHA --tag $IMAGE:latest -f scripts/docker/Dockerfile .
23 | # - docker push $IMAGE:$CI_COMMIT_SHA
24 | # - docker push $IMAGE:latest
25 | - sh ./scripts/docker/build-on-gitlab.sh
26 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "jsx": "preserve",
5 | "lib": [
6 | "esnext",
7 | "dom"
8 | ],
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "baseUrl": ".",
12 | "module": "ESNext",
13 | "moduleResolution": "node",
14 | "paths": {
15 | "@render/*": [
16 | "src/render/*"
17 | ],
18 | "@main/*": [
19 | "src/main/*"
20 | ]
21 | },
22 | "resolveJsonModule": true,
23 | "types": [
24 | "vite/client",
25 | "./src/preload"
26 | ],
27 | "strict": false,
28 | "noImplicitAny": false,
29 | "noEmit": true,
30 | "sourceMap": true,
31 | "esModuleInterop": true,
32 | "skipLibCheck": true
33 | },
34 | "include": [
35 | "src/**/*.ts",
36 | "src/**/*.d.ts",
37 | "src/**/*.tsx",
38 | "src/**/*.vue"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/extra/regedit/vbs/regCreateKey.wsf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
32 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/exception/404/index.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Result } from 'antd';
2 | import * as React from 'react';
3 | import { Link } from 'react-router-dom';
4 |
5 | import { formatMessage } from '@/i18n';
6 |
7 | export const Exception404 = () => (
8 |
20 |
21 |
22 |
28 |
29 |
30 | }
31 | />
32 | );
33 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/src/index.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 |
3 | import { app } from 'electron';
4 | import log from 'electron-log';
5 |
6 | import * as R from 'rte-core';
7 |
8 | import { getVersion } from './provider';
9 | import { AppManager } from './AppManager';
10 |
11 | if (process.env.NODE_ENV !== 'production') {
12 | // eslint-disable-next-line @typescript-eslint/no-require-imports
13 | require('electron-debug')({ showDevTools: true });
14 | }
15 |
16 | process.on('uncaughtException', error => {
17 | // Handle the error
18 | log.error(error);
19 | });
20 |
21 | declare const global: {
22 | searchLocation: R.searchLocationFunc;
23 | run: R.runFunc;
24 | download: R.downloadFunc;
25 | getVersion: R.getVersionFunc;
26 | };
27 |
28 | global.getVersion = getVersion;
29 |
30 | const url =
31 | process.env.ELECTRON_START_URL || `file://${__dirname}/assets/index.html`;
32 | const appManager = new AppManager(app, url);
33 | appManager.initApp();
34 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/user/containers/PersonList/index.less:
--------------------------------------------------------------------------------
1 | @import '~@/skeleton/styles/variables.less';
2 |
3 | .container {
4 | .appContainer;
5 |
6 | overflow-y: auto;
7 | overflow-x: hidden;
8 | padding: 24px;
9 | }
10 |
11 | .filters {
12 | width: 100%;
13 | height: 36px;
14 | display: flex;
15 | margin-bottom: 16px;
16 | align-items: center;
17 |
18 | .count {
19 | width: 100%;
20 | font-size: 18px;
21 | color: rgb(0, 0, 0, 0.75);
22 | line-height: 50px;
23 |
24 | .num {
25 | color: #6874e2;
26 | }
27 | }
28 |
29 | .item {
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 |
34 | .field {
35 | font-size: 14px;
36 | margin-left: 28px;
37 | white-space: nowrap;
38 | }
39 |
40 | .input {
41 | .flexCenter;
42 |
43 | margin-left: 14px;
44 | }
45 | }
46 |
47 | .actions {
48 | .flexCenter;
49 |
50 | margin-left: 14px;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/InstallableAppSeriesView/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { InstallableApp, InstallableAppSeries } from 'rte-core';
4 |
5 | import { InstallableAppView } from '../InstallableAppView';
6 |
7 | import './index.scss';
8 |
9 | export interface InstallableAppSeriesView {
10 | name: string;
11 | id: string;
12 | children: InstallableApp[];
13 | }
14 |
15 | interface InstallableAppSeriesViewProps {
16 | data: InstallableAppSeries;
17 | }
18 |
19 | export const InstallableAppSeriesView = (
20 | props: InstallableAppSeriesViewProps,
21 | ): JSX.Element => {
22 | const appSeries = props.data;
23 | return (
24 |
25 |
{appSeries.name}
26 |
27 | {appSeries.apps.map(p => (
28 |
29 | ))}
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/src/service/UpdateService.ts:
--------------------------------------------------------------------------------
1 | import { autoUpdater } from 'electron-updater';
2 |
3 | export interface ProgressInfo {
4 | total: number;
5 | delta: number;
6 | transferred: number;
7 | percent: number;
8 | bytesPerSecond: number;
9 | }
10 |
11 | export abstract class AbstractUpdateService {
12 | abstract checkUpdate(
13 | onDownloading: (progress: ProgressInfo) => void,
14 | onDownloaded: () => void,
15 | ): void;
16 | abstract installUpdate(): void;
17 |
18 | downloadUpdate() {}
19 | }
20 |
21 | export default class UpdateService extends AbstractUpdateService {
22 | checkUpdate(
23 | onDownloading: (progress: ProgressInfo) => void,
24 | onDownloaded: () => void,
25 | ) {
26 | autoUpdater.on('download-progress', onDownloading);
27 | autoUpdater.on('update-downloaded', onDownloaded);
28 | autoUpdater.checkForUpdatesAndNotify();
29 | }
30 |
31 | installUpdate() {
32 | setImmediate(() => autoUpdater.quitAndInstall(true, true));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/AuthorizedRoute.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Redirect, Route } from 'react-router';
3 |
4 | import { IAuthorityType } from './permissions';
5 | import { Authorized } from './Authorized';
6 |
7 | interface AuthorizedRoutePops {
8 | currentAuthority: string;
9 | component: React.ComponentClass;
10 | render: (props: any) => React.ReactNode;
11 | redirectPath: string;
12 | authority: IAuthorityType;
13 | }
14 |
15 | export const AuthorizedRoute: React.SFC = ({
16 | component: Component,
17 | render,
18 | authority,
19 | redirectPath,
20 | ...rest
21 | }) => (
22 | }
28 | />
29 | }
30 | >
31 |
34 | Component ? : render(props)
35 | }
36 | />
37 |
38 | );
39 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/containers/App/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Route, RouteComponentProps, Switch } from 'react-router';
4 | import { withRouter } from 'react-router-dom';
5 |
6 | import { LoginPage } from '@/apps/auth/containers/LoginPage';
7 | import { Home } from '@/apps/home/containers/Home';
8 | import { Exception403 } from '@/skeleton';
9 |
10 | export interface IAppProps extends RouteComponentProps {}
11 |
12 | export interface IAppState {}
13 |
14 | export class App extends React.Component {
15 | constructor(props: IAppProps) {
16 | super(props);
17 |
18 | this.state = {};
19 | }
20 |
21 | render() {
22 | return (
23 |
24 |
25 | } />
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | export default connect(_state => ({}), {})(withRouter(App));
33 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/containers/SoftwareMesh/index.scss:
--------------------------------------------------------------------------------
1 | .home-container {
2 | width: 100%;
3 | height: 100%;
4 |
5 | .banner {
6 | height: 183px;
7 | background-image: url('../../../../assets/bg1.png');
8 | background-size: cover;
9 | padding-top: 22px;
10 | padding-left: 100px;
11 | color: #fff;
12 |
13 | p {
14 | margin: 0;
15 | padding: 0;
16 | font-weight: 100;
17 | }
18 |
19 | .slogon-big {
20 | font-size: 22px;
21 | letter-spacing: 1.18px;
22 | margin-bottom: 19px;
23 | line-height: 30px;
24 | }
25 |
26 | .slogon-small {
27 | font-size: 10px;
28 | letter-spacing: 0.54px;
29 | line-height: 14px;
30 | }
31 | }
32 |
33 | #sg-tools {
34 | grid-template-areas: 'yhwsgzb yhwyqzb sgtb . .';
35 | grid-template-columns: repeat(3, auto) 1fr 1fr;
36 | grid-template-rows: auto;
37 | }
38 |
39 | #jl-tools {
40 | grid-template-rows: auto;
41 | grid-template-columns: repeat(2, auto) 1fr;
42 | grid-template-areas: 'jlzb jltb';
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/helm/charts/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | {{- $serviceName := include "context.fullname" . -}}
3 | apiVersion: extensions/v1beta1
4 | kind: Ingress
5 | metadata:
6 | name: {{ include "context.fullname" . }}
7 | labels:
8 | {{ include "context.labels" . | indent 4 }}
9 | {{- with .Values.ingress.annotations }}
10 | annotations:
11 | {{- toYaml . | nindent 4 }}
12 | {{- end }}
13 | spec:
14 | {{- if .Values.ingress.tls }}
15 | tls:
16 | {{- range .Values.ingress.tls }}
17 | - hosts:
18 | {{- range .hosts }}
19 | - {{ . | quote }}
20 | {{- end }}
21 | secretName: {{ .secretName }}
22 | {{- end }}
23 | {{- end }}
24 | rules:
25 | # test api ingress rules
26 | {{- range .Values.ingress.context.hosts }}
27 | - host: {{ .host | quote }}
28 | http:
29 | paths:
30 | {{- range .paths }}
31 | - path: {{ . }}
32 | backend:
33 | serviceName: {{ $serviceName }}
34 | servicePort: http
35 | {{- end }}
36 | {{- end }}
37 | {{- end }}
38 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'node:path'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import { VitePluginDoubleshot } from 'vite-plugin-doubleshot'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | root: join(__dirname, 'src/render'),
9 | plugins: [
10 | vue(),
11 | VitePluginDoubleshot({
12 | type: 'electron',
13 | main: 'dist/main/index.js',
14 | entry: 'src/main/index.ts',
15 | outDir: 'dist/main',
16 | external: ['electron'],
17 | electron: {
18 | build: {
19 | config: './electron-builder.config.js',
20 | },
21 | preload: {
22 | entry: 'src/preload/index.ts',
23 | outDir: 'dist/preload',
24 | },
25 | },
26 | }),
27 | ],
28 | resolve: {
29 | alias: {
30 | '@render': join(__dirname, 'src/render'),
31 | '@main': join(__dirname, 'src/main'),
32 | },
33 | },
34 | base: './',
35 | build: {
36 | outDir: join(__dirname, 'dist/render'),
37 | emptyOutDir: true,
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/containers/SoftwareMesh/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { InstallableAppSeries } from 'rte-core';
4 |
5 | import { InstallableAppSeriesView } from '../../components/InstallableAppSeriesView';
6 |
7 | import './index.scss';
8 |
9 | interface SoftwareMeshProps {}
10 |
11 | export class SoftwareMesh extends Component {
12 | componentDidMount() {}
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
简单、易操作
20 |
生成文件全面、清晰
21 |
智能查错
22 |
23 |
24 |
专用工具软件,兼容性强,支持XP/WIN7/WIN8/WIN10环境下运行。
25 |
26 |
27 |
28 | {[].map((pl: InstallableAppSeries) => (
29 |
30 | ))}
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/device.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/layouts/index.less:
--------------------------------------------------------------------------------
1 | :global(.ant-pro-sider-menu-sider) {
2 | flex: 0 0 220px;
3 | max-width: 220px;
4 | min-width: 220px;
5 | width: 220px;
6 |
7 | :global(.ant-pro-sider-menu-logo) {
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | padding-left: 0;
12 | h1 {
13 | display: none;
14 | }
15 | }
16 | }
17 |
18 | :global(.ant-layout-header) {
19 | margin: 0 24px;
20 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
21 | width: unset !important;
22 | }
23 |
24 | :global(.ant-layout-content) {
25 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
26 | background-color: white;
27 | margin: 24px;
28 | }
29 |
30 | .selectedMenu {
31 | color: white;
32 | background: #6874e2;
33 | box-shadow: 0 0 7px 0 rgba(255, 255, 255, 0.36),
34 | 0 2px 10px 0 rgba(104, 116, 226, 0.44);
35 | border-radius: 0 10px 10px 0;
36 | transform: translateX(-24px);
37 | padding-left: 24px;
38 | }
39 |
40 | .selectedMenuCollapsed {
41 | border-radius: 10px;
42 | transform: translateX(-16px);
43 | padding-left: 16px;
44 | }
45 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/env/theme.ts:
--------------------------------------------------------------------------------
1 | import generate from '@ant-design/colors/lib/generate';
2 | import * as client from 'webpack-theme-color-replacer/client';
3 |
4 | export const themeClient = {
5 | getAntdSerials(color: string): string[] {
6 | const lightCount = 9;
7 | const divide = 10;
8 | // 淡化(即less的tint)
9 | let lightens = new Array(lightCount).fill(0);
10 | lightens = lightens.map((_, i) =>
11 | client.varyColor.lighten(color, i / divide),
12 | );
13 | const colorPalettes = generate(color);
14 | return lightens.concat(colorPalettes);
15 | },
16 |
17 | changeColor(color?: string): Promise {
18 | if (!color) {
19 | return Promise.resolve();
20 | }
21 | const options = {
22 | // new colors array, one-to-one corresponde with `matchColors`
23 | newColors: this.getAntdSerials(color),
24 | changeUrl(cssUrl: string): string {
25 | // while router is not `hash` mode, it needs absolute path
26 | return `/${cssUrl}`;
27 | },
28 | };
29 | return client.changer.changeColor(options, Promise);
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/types/comp.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export const isComponentClass = (
4 | component: React.ComponentClass | React.ReactNode,
5 | ): boolean => {
6 | if (!component) return false;
7 | const proto = Object.getPrototypeOf(component);
8 | if (proto === React.Component || proto === Function.prototype) return true;
9 | return isComponentClass(proto);
10 | };
11 |
12 | // Determine whether the incoming component has been instantiated
13 | // AuthorizedRoute is already instantiated
14 | // Authorized render is already instantiated, children is no instantiated
15 | // Secured is not instantiated
16 | export const checkIsInstantiation = (
17 | target: React.ComponentClass | React.ReactNode,
18 | ) => {
19 | if (isComponentClass(target)) {
20 | const Target = target as React.ComponentClass;
21 | return (props: any) => ;
22 | }
23 | if (React.isValidElement(target)) {
24 | return (props: any) => React.cloneElement(target, props);
25 | }
26 | return () => target;
27 | };
28 |
29 | export type StyleObject = Record;
30 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/src/render/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 | {{ props.title }}
34 |
35 |
36 |
37 |
38 |
41 |
42 |
43 |
44 |
47 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 王下邀月熊
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-present ArcherGu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/typings/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'slash2';
2 | declare module 'antd-theme-webpack-plugin';
3 |
4 | declare module '*.css';
5 | declare module '*.scss';
6 | declare module '*.sass';
7 | declare module '*.svg';
8 | declare module '*.png';
9 | declare module '*.jpg';
10 | declare module '*.jpeg';
11 | declare module '*.gif';
12 | declare module '*.bmp';
13 | declare module '*.tiff';
14 | declare module 'omit.js';
15 | declare module 'react-copy-to-clipboard';
16 | declare module 'react-fittext';
17 | declare module '@antv/data-set';
18 | declare module 'nzh/cn';
19 | declare module 'webpack-theme-color-replacer';
20 | declare module 'webpack-theme-color-replacer/client';
21 | declare module 'redux-pack';
22 |
23 | // google analytics interface
24 | interface GAFieldsObject {
25 | eventCategory: string;
26 | eventAction: string;
27 | eventLabel?: string;
28 | eventValue?: number;
29 | nonInteraction?: boolean;
30 | }
31 | interface Window {
32 | ga: (
33 | command: 'send',
34 | hitType: 'event' | 'pageview',
35 | fieldsObject: GAFieldsObject | string,
36 | ) => void;
37 | }
38 |
39 | declare let ga: Function;
40 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/src/main/app.module.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'node:path'
2 | import { Module } from '@nestjs/common'
3 | import { ElectronModule } from '@doubleshot/nest-electron'
4 | import { BrowserWindow, app } from 'electron'
5 | import { AppController } from './app.controller'
6 | import { AppService } from './app.service'
7 |
8 | @Module({
9 | imports: [ElectronModule.registerAsync({
10 | useFactory: async () => {
11 | const isDev = !app.isPackaged
12 | const win = new BrowserWindow({
13 | width: 1024,
14 | height: 768,
15 | autoHideMenuBar: true,
16 | webPreferences: {
17 | contextIsolation: true,
18 | preload: join(__dirname, '../preload/index.js'),
19 | },
20 | })
21 |
22 | win.on('closed', () => {
23 | win.destroy()
24 | })
25 |
26 | const URL = isDev
27 | ? process.env.DS_RENDERER_URL
28 | : `file://${join(app.getAppPath(), 'dist/render/index.html')}`
29 |
30 | win.loadURL(URL)
31 |
32 | return { win }
33 | },
34 | })],
35 | controllers: [AppController],
36 | providers: [AppService],
37 | })
38 | export class AppModule { }
39 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/extra/regedit/vbs/regList.wsf:
--------------------------------------------------------------------------------
1 | '
2 | ' Lists the sub keys and values of a given registry key
3 | '
4 | ' cscript regList.wsg HKLM\Software
5 | '
6 | ' Will Yield:
7 | '
8 | ' {
9 | ' "hklm\software": {
10 | ' "keys": [ .. array of sub keys .. ],
11 | ' "values": {
12 | ' "moo": {
13 | ' "type": "REG_SZ",
14 | ' "value": "bar"
15 | ' }
16 | ' }
17 | ' }
18 | ' }
19 |
20 |
21 |
22 |
46 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/authority.ts:
--------------------------------------------------------------------------------
1 | let currentAuthority: string[] | null = null;
2 |
3 | const key = 'ufo-authority';
4 |
5 | // use localStorage to store the authority info, which might be sent from server in actual project.
6 | export function getAuthority(str?: string): string[] {
7 | if (currentAuthority) {
8 | return currentAuthority;
9 | }
10 |
11 | const authorityString =
12 | typeof str === 'undefined' ? localStorage.getItem(key) : str;
13 |
14 | // authorityString could be admin, "admin", ["admin"]
15 | let authority;
16 |
17 | try {
18 | if (authorityString) {
19 | authority = JSON.parse(authorityString);
20 | }
21 | } catch (e) {
22 | authority = authorityString;
23 | }
24 |
25 | if (typeof authority === 'string') {
26 | authority = [authority];
27 | }
28 |
29 | if (!authority) {
30 | authority = ['anonymous'];
31 | }
32 |
33 | currentAuthority = authority;
34 |
35 | return authority;
36 | }
37 |
38 | export function setAuthority(authority: string | string[]): void {
39 | const proAuthority = typeof authority === 'string' ? [authority] : authority;
40 |
41 | currentAuthority = proAuthority;
42 |
43 | return localStorage.setItem(key, JSON.stringify(proAuthority));
44 | }
45 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/styles/animation.less:
--------------------------------------------------------------------------------
1 | @durationFast: 100ms;
2 | @durationSlow: 200ms;
3 |
4 | .standardEase {
5 | // For moving visible elements.
6 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
7 | }
8 |
9 | .decelerateEase {
10 | // For incoming elements, likes ease-out.
11 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
12 | }
13 |
14 | .accelerateEase {
15 | // For exiting elements, likes ease-in.
16 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
17 | }
18 |
19 | .zoomIn {
20 | animation: zoomIn @durationSlow both;
21 | .decelerateEase();
22 | }
23 | @keyframes zoomIn {
24 | from {
25 | opacity: 0;
26 | transform: scale3d(0.3, 0.3, 0.3);
27 | }
28 | }
29 |
30 | .zoomOut {
31 | animation: zoomOut @durationFast both;
32 | .accelerateEase();
33 | }
34 | @keyframes zoomOut {
35 | to {
36 | opacity: 0;
37 | transform: scale3d(0.3, 0.3, 0.3);
38 | }
39 | }
40 |
41 | .fadeIn {
42 | animation: fadeIn @durationSlow both;
43 | .decelerateEase();
44 | }
45 | @keyframes fadeIn {
46 | from {
47 | opacity: 0;
48 | }
49 | }
50 |
51 | .fadeOut {
52 | animation: fadeOut @durationFast both;
53 | .accelerateEase();
54 | }
55 | @keyframes fadeOut {
56 | to {
57 | opacity: 0;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/user/components/PersonTable/index.tsx:
--------------------------------------------------------------------------------
1 | /** 用户列表 */
2 | import { Table } from 'antd';
3 | import { ColumnType, TableProps } from 'antd/es/table';
4 | import dayjs from 'dayjs';
5 | import React from 'react';
6 | import * as S from 'ufc-schema';
7 |
8 | export type PersonTableColumns = Array>;
9 |
10 | const defaultColumns: PersonTableColumns = [
11 | {
12 | key: 'username',
13 | dataIndex: 'username',
14 | title: '用户名',
15 | },
16 | {
17 | key: 'userRoleText',
18 | dataIndex: 'userRoleText',
19 | title: '用户角色',
20 | },
21 | {
22 | key: 'createdAt',
23 | dataIndex: 'createdAt',
24 | title: '创建时间',
25 | render: (c: string) => dayjs(c).format('YYYY-MM-DD'),
26 | },
27 | ];
28 |
29 | interface PersonTableProps extends Partial> {
30 | action?: PersonTableColumns;
31 | }
32 |
33 | const PersonTable: React.FC = ({
34 | action,
35 | columns,
36 | ...rest
37 | }) => {
38 | let finalColumns: PersonTableColumns;
39 | if (!!columns && !action) {
40 | finalColumns = columns;
41 | } else {
42 | finalColumns = [...defaultColumns, ...(action || [])];
43 | }
44 |
45 | return ;
46 | };
47 |
48 | export default PersonTable;
49 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/extra/regedit/vbs/regListStream.wsf:
--------------------------------------------------------------------------------
1 | '
2 | ' Lists the sub keys and values of a given registry key, this script is slightly different
3 | ' than regList because it reads stdin for the keys to list
4 | '
5 | ' cscript regList.wsg HKLM\Software
6 | '
7 | ' Will Yield:
8 | '
9 | ' {
10 | ' "hklm\software": {
11 | ' "keys": [ .. array of sub keys .. ],
12 | ' "values": {
13 | ' "moo": {
14 | ' "type": "REG_SZ",
15 | ' "value": "bar"
16 | ' }
17 | ' }
18 | ' }
19 | ' }
20 |
21 |
22 |
23 |
46 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/shared/components/FieldsDetail/index.less:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | height: fit-content;
5 |
6 | .header {
7 | height: 75px;
8 | width: 100%;
9 |
10 | .title {
11 | height: 100%;
12 | width: fit-content;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | float: left;
17 | margin-left: 32px;
18 | font-size: 18px;
19 | color: rgba(0, 0, 0, 0.75);
20 | }
21 |
22 | .actions {
23 | height: 100%;
24 | width: fit-content;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | float: right;
29 | margin-right: 32px;
30 | }
31 | }
32 | }
33 |
34 | .fields {
35 | width: 100%;
36 | height: fit-content;
37 | display: flex;
38 | flex-flow: wrap;
39 |
40 | .field_item {
41 | height: 50px;
42 | margin-left: 32px;
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 |
47 | .field_name {
48 | font-size: 16px;
49 | color: rgba(0, 0, 0, 0.65);
50 | }
51 |
52 | .field_value {
53 | min-width: 200px;
54 | margin-left: 16px;
55 | font-size: 16px;
56 | color: rgba(0, 0, 0, 0.65);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@m-fe/webpack-config')({
2 | themeVars: {
3 | 'primary-color': '#5d4bff',
4 | },
5 | extendedBaseConfig: {
6 | target: 'electron-renderer',
7 | output: { publicPath: './' },
8 | module: {
9 | rules: [
10 | {
11 | test: /\.s[ac]ss$/i,
12 | use: [
13 | // Creates `style` nodes from JS strings
14 | 'style-loader',
15 | // Translates CSS into CommonJS
16 | 'css-loader',
17 | // Compiles Sass to CSS
18 | 'sass-loader',
19 | ],
20 | },
21 | // svg 的加载交于应用自身决定
22 | {
23 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
24 | oneOf: [
25 | {
26 | issuer: /\.[jt]sx?$/,
27 | use: [
28 | {
29 | loader: '@svgr/webpack',
30 | // loader: 'svg-inline-loader',
31 | },
32 | ],
33 | },
34 | {
35 | loader: 'url-loader',
36 | },
37 | ],
38 | },
39 | ],
40 | },
41 | resolve: {
42 | alias: {
43 | dayjs: 'dayjs/esm',
44 | moment$: 'dayjs/esm',
45 | systemjs$: 'systemjs/dist/system.js',
46 | },
47 | },
48 | },
49 | });
50 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/src/main/index.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core'
2 | import { app } from 'electron'
3 | import type { MicroserviceOptions } from '@nestjs/microservices'
4 | import { ElectronIpcTransport } from '@doubleshot/nest-electron'
5 | import { AppModule } from './app.module'
6 |
7 | process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'
8 |
9 | async function electronAppInit() {
10 | const isDev = !app.isPackaged
11 | app.on('window-all-closed', () => {
12 | if (process.platform !== 'darwin')
13 | app.quit()
14 | })
15 |
16 | if (isDev) {
17 | if (process.platform === 'win32') {
18 | process.on('message', (data) => {
19 | if (data === 'graceful-exit')
20 | app.quit()
21 | })
22 | }
23 | else {
24 | process.on('SIGTERM', () => {
25 | app.quit()
26 | })
27 | }
28 | }
29 |
30 | await app.whenReady()
31 | }
32 |
33 | async function bootstrap() {
34 | try {
35 | await electronAppInit()
36 |
37 | const nestApp = await NestFactory.createMicroservice(
38 | AppModule,
39 | {
40 | strategy: new ElectronIpcTransport('IpcTransport'),
41 | },
42 | )
43 |
44 | await nestApp.listen()
45 | }
46 | catch (error) {
47 | // eslint-disable-next-line no-console
48 | console.log(error)
49 | app.quit()
50 | }
51 | }
52 |
53 | bootstrap()
54 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/scripts/webpack/webpack.config.theme.js:
--------------------------------------------------------------------------------
1 | const ThemeColorReplacer = require('webpack-theme-color-replacer');
2 |
3 | module.exports = {
4 | plugins: [
5 | new ThemeColorReplacer({
6 | fileName: 'theme-colors-[contenthash:8].css',
7 | matchColors: getAntdSerials('#5d4bff'), // 主色系列
8 | // 改变样式选择器,解决样式覆盖问题
9 | changeSelector(selector) {
10 | switch (selector) {
11 | case '.ant-calendar-today .ant-calendar-date':
12 | return ':not(.ant-calendar-selected-date)' + selector;
13 | case '.ant-btn:focus,.ant-btn:hover':
14 | return '.ant-btn:focus:not(.ant-btn-primary),.ant-btn:hover:not(.ant-btn-primary)';
15 | case '.ant-btn.active,.ant-btn:active':
16 | return '.ant-btn.active:not(.ant-btn-primary),.ant-btn:active:not(.ant-btn-primary)';
17 | default:
18 | return selector;
19 | }
20 | },
21 | }),
22 | ],
23 | };
24 |
25 | /** 获取系列颜色 */
26 | function getAntdSerials(color) {
27 | var lightens = new Array(9).fill().map((t, i) => {
28 | return ThemeColorReplacer.varyColor.lighten(color, i / 10);
29 | });
30 | // 此处为了简化,采用了darken。实际按color.less需求可以引入tinycolor, colorPalette变换得到颜色值
31 | var darkens = new Array(6).fill().map((t, i) => {
32 | return ThemeColorReplacer.varyColor.darken(color, i / 10);
33 | });
34 | return lightens.concat(darkens);
35 | }
36 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/containers/Home/manifest.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | AppstoreOutlined,
3 | MessageOutlined,
4 | MobileOutlined,
5 | } from '@ant-design/icons';
6 | import React from 'react';
7 | import { RouteProps } from 'react-router-dom';
8 |
9 | import { Mobile } from '../Mobile';
10 | import { SoftwareMesh } from '../SoftwareMesh';
11 | import { Support } from '../Support';
12 |
13 | export interface MenuConfig {
14 | menuProps: {
15 | disabled?: boolean;
16 | text: string;
17 | icon: string;
18 | };
19 | routeProps: RouteProps;
20 | }
21 |
22 | export const menuConfigs: MenuConfig[] = [
23 | {
24 | menuProps: {
25 | disabled: false,
26 | text: '软件工具',
27 | icon: (() as unknown) as string,
28 | },
29 | routeProps: {
30 | path: '/',
31 | component: SoftwareMesh,
32 | exact: true,
33 | },
34 | },
35 | {
36 | menuProps: {
37 | disabled: false,
38 | text: '联系我们',
39 | icon: (() as unknown) as string,
40 | },
41 | routeProps: {
42 | path: '/support',
43 | component: Support,
44 | },
45 | },
46 | {
47 | menuProps: {
48 | disabled: false,
49 | text: '手机下载',
50 | icon: (() as unknown) as string,
51 | },
52 | routeProps: {
53 | path: '/mobile',
54 | component: Mobile,
55 | },
56 | },
57 | ];
58 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | lint:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
17 |
18 | - name: Install pnpm
19 | uses: pnpm/action-setup@v3.0.0
20 |
21 | - name: Set node
22 | uses: actions/setup-node@v4
23 | with:
24 | node-version: 18.x
25 | cache: pnpm
26 |
27 | - name: Install
28 | run: pnpm i
29 |
30 | - name: Lint
31 | run: pnpm run lint
32 |
33 | build:
34 | runs-on: ${{ matrix.os }}
35 |
36 | timeout-minutes: 10
37 |
38 | strategy:
39 | matrix:
40 | node_version: [18.x, 20.x]
41 | os: [ubuntu-latest, windows-latest, macos-latest]
42 | fail-fast: false
43 |
44 | steps:
45 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
46 |
47 | - name: Install pnpm
48 | uses: pnpm/action-setup@v3.0.0
49 |
50 | - name: Set node version to ${{ matrix.node_version }}
51 | uses: actions/setup-node@v4
52 | with:
53 | node-version: ${{ matrix.node_version }}
54 | cache: pnpm
55 |
56 | - name: Install
57 | run: pnpm i
58 |
59 | - name: Build
60 | run: pnpm run build
61 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/token.ts:
--------------------------------------------------------------------------------
1 | import jwtDecode from 'jwt-decode';
2 |
3 | import { setRequestHeader } from '../api/umi/request';
4 |
5 | import { axiosAgent } from './../api/axios/agent';
6 |
7 | /** 基于 Token 的鉴权方式 */
8 | export let token: string | null = null;
9 | const LOCAL_KEY = 'ufc-token';
10 |
11 | export function getToken() {
12 | if (token) {
13 | return token;
14 | }
15 |
16 | const storedToken = localStorage.getItem(LOCAL_KEY);
17 |
18 | if (storedToken) {
19 | return JSON.parse(storedToken);
20 | }
21 |
22 | return storedToken;
23 | }
24 |
25 | export function setToken(_token: string | null) {
26 | token = _token;
27 |
28 | if (_token === null) {
29 | // 设置 Token 之后,修改 request 的 Header
30 | setRequestHeader({});
31 | } else {
32 | setRequestHeader({
33 | Authorization: `Bearer ${token}`,
34 | });
35 | axiosAgent.defaults.headers.Authorization = `Bearer ${token}`;
36 | }
37 |
38 | if (_token) {
39 | return localStorage.setItem(LOCAL_KEY, JSON.stringify(_token));
40 | } else {
41 | return localStorage.removeItem(LOCAL_KEY);
42 | }
43 | }
44 |
45 | /** 判断是否过期 */
46 | export function isTokenExpired(token: string | undefined, lead = 0) {
47 | if (!token) {
48 | return true;
49 | }
50 |
51 | const { exp } = jwtDecode(token);
52 |
53 | // 这里引入提前量,以提前进行 Token 更新
54 | if (exp * 1000 > Date.now() + lead) {
55 | return false;
56 | }
57 |
58 | return true;
59 | }
60 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/types/props.ts:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import { RouteComponentProps } from 'react-router-dom';
3 |
4 | /**
5 | * Represents the base props of a `React` component;
6 | */
7 | export interface BaseReactProps {
8 | /**
9 | * The class name of the component.
10 | */
11 | className?: string;
12 | style?: Record;
13 | children?: ReactNode;
14 | }
15 |
16 | /**
17 | * Represents the base props of a `React` route component;
18 | */
19 | export interface BaseReactRouteProps
20 | extends BaseReactProps,
21 | RouteComponentProps {}
22 |
23 | declare const ButtonTypes: [
24 | 'default',
25 | 'primary',
26 | 'ghost',
27 | 'dashed',
28 | 'danger',
29 | 'link',
30 | ];
31 | export declare type ButtonType = typeof ButtonTypes[number];
32 | declare const ButtonShapes: ['circle', 'circle-outline', 'round'];
33 | export declare type ButtonShape = typeof ButtonShapes[number];
34 | declare const ButtonSizes: ['large', 'default', 'small'];
35 | export declare type ButtonSize = typeof ButtonSizes[number];
36 |
37 | export interface IButtonProps extends BaseReactProps {
38 | type?: ButtonType;
39 | icon?: string;
40 | shape?: ButtonShape;
41 | size?: ButtonSize;
42 | loading?:
43 | | boolean
44 | | {
45 | delay?: number;
46 | };
47 | prefixCls?: string;
48 | className?: string;
49 | ghost?: boolean;
50 | block?: boolean;
51 | children?: React.ReactNode;
52 | }
53 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fast-vite-nestjs-electron",
3 | "version": "0.0.1",
4 | "packageManager": "pnpm@9.0.6",
5 | "description": "Vite + Electron with Doubleshot, so fast! ⚡",
6 | "main": "dist/main/index.js",
7 | "scripts": {
8 | "dev": "rimraf dist && vite",
9 | "debug": "rimraf dist && vite -- --dsb-debug",
10 | "build": "rimraf dist && vue-tsc && vite build",
11 | "lint": "eslint .",
12 | "lint:fix": "eslint . --fix",
13 | "postinstall": "electron-builder install-app-deps"
14 | },
15 | "dependencies": {
16 | "@doubleshot/nest-electron": "^0.2.5",
17 | "@nestjs/common": "^10.3.0",
18 | "@nestjs/core": "^10.3.0",
19 | "@nestjs/microservices": "^10.3.0",
20 | "reflect-metadata": "^0.2.1",
21 | "rxjs": "^7.8.1",
22 | "vue": "^3.4.13"
23 | },
24 | "devDependencies": {
25 | "@lightwing/eslint-config": "^1.0.23",
26 | "@vitejs/plugin-vue": "5.0.4",
27 | "@vue/compiler-sfc": "3.4.26",
28 | "electron": "30.0.2",
29 | "electron-builder": "24.13.3",
30 | "eslint": "8.57.0",
31 | "lint-staged": "15.2.2",
32 | "rimraf": "5.0.5",
33 | "simple-git-hooks": "2.11.1",
34 | "typescript": "5.4.5",
35 | "vite": "5.2.11",
36 | "vite-plugin-doubleshot": "0.0.13",
37 | "vue-tsc": "2.0.16"
38 | },
39 | "simple-git-hooks": {
40 | "pre-commit": "npx lint-staged"
41 | },
42 | "lint-staged": {
43 | "*.{js,ts,tsx,vue,md,json,yml}": [
44 | "eslint --fix"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/env/store.ts:
--------------------------------------------------------------------------------
1 | import { routerMiddleware } from 'connected-react-router';
2 | import {
3 | ReducersMapObject,
4 | applyMiddleware,
5 | compose,
6 | createStore,
7 | } from 'redux';
8 | import { middleware as reduxPackMiddleware } from 'redux-pack-fsa';
9 | import thunkMiddleware from 'redux-thunk';
10 |
11 | import { configReducer } from '../../ducks';
12 |
13 | import { history } from './history';
14 |
15 | declare let __DEV__: boolean;
16 | declare global {
17 | interface Window {
18 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: Function;
19 | }
20 | }
21 |
22 | const middlewares = applyMiddleware(
23 | routerMiddleware(history),
24 | thunkMiddleware,
25 | reduxPackMiddleware,
26 | );
27 |
28 | let enhancers = middlewares;
29 |
30 | if (__DEV__) {
31 | const composeEnhancers =
32 | typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
33 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
34 | : compose;
35 |
36 | enhancers = composeEnhancers(middlewares);
37 | }
38 |
39 | export function configStore(initialState: object = {}) {
40 | const store = createStore(
41 | configReducer({})(history),
42 | initialState,
43 | enhancers,
44 | );
45 |
46 | function appendReducer(asyncReducers: ReducersMapObject) {
47 | store.replaceReducer(configReducer(asyncReducers)(history));
48 | }
49 |
50 | return {
51 | ...store,
52 | appendReducer,
53 | };
54 | }
55 |
56 | export default configStore();
57 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/logo_u.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/Label/index.tsx:
--------------------------------------------------------------------------------
1 | import cn from 'classnames';
2 | import React from 'react';
3 |
4 | import { i18nFormat } from '@/i18n';
5 | import { StyleObject } from '@/skeleton';
6 |
7 | import styles from './index.less';
8 |
9 | export const ColoredLabel = ({
10 | children,
11 | className,
12 | ...restProps
13 | }: {
14 | children: any;
15 | className?: string;
16 | [key: string]: any;
17 | }) => (
18 |
19 | {children}
20 |
21 | );
22 |
23 | export interface TitleWrapperProps {
24 | label: any;
25 | highlight?: React.ReactNode;
26 | unit?: string;
27 | className?: string;
28 | style?: StyleObject;
29 | }
30 |
31 | export const TitleWrapper = ({
32 | label,
33 | highlight,
34 | unit,
35 | className,
36 | style,
37 | }: TitleWrapperProps) => {
38 | return (
39 |
40 | {label}
41 | {highlight !== undefined && highlight !== null ? ': ' : ''}
42 | {highlight}
43 | {unit && {unit}}
44 |
45 | );
46 | };
47 |
48 | export const EmptyPlaceholder = ({
49 | className,
50 | style,
51 | }: {
52 | className?: string;
53 | style?: Record;
54 | }) => {
55 | return (
56 |
57 | {i18nFormat('暂无内容2')}
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/consumable.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/extra/regedit/vbs/regPutValue.wsf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
56 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/helm/charts/values.yaml:
--------------------------------------------------------------------------------
1 | replicaCount: 1
2 |
3 | imagePullSecrets:
4 | - name: regcred
5 | nameOverride: ''
6 | fullnameOverride: ''
7 |
8 | context:
9 | image:
10 | registry: registry.test.wx-coder.cn
11 | repository: test/test-web-client
12 | tag: latest
13 | pullPolicy: Always
14 | service:
15 | type: ClusterIP
16 | port: 2015
17 |
18 | service:
19 | type: LoadBalancer
20 | # HTTP 端口
21 | port: 2015
22 | # nodePorts: 选择 30000-32767 之间的端口
23 | nodePorts:
24 | http: ''
25 |
26 | ingress:
27 | enabled: true
28 | annotations:
29 | nginx.ingress.kubernetes.io/proxy-body-size: '0'
30 | kubernetes.io/ingress.class: 'nginx'
31 | certmanager.k8s.io/issuer: 'letsencrypt-prod'
32 | # kubernetes.io/tls-acme: "true"
33 | context:
34 | hosts:
35 | - host: test.wx-coder.cn
36 | paths:
37 | - '/'
38 |
39 | tls:
40 | - secretName: test-web-client-tls
41 | hosts:
42 | - test.wx-coder.cn
43 |
44 | resources:
45 | {}
46 | # We usually recommend not to specify default resources and to leave this as a conscious
47 | # choice for the user. This also increases chances charts run on environments with little
48 | # resources, such as Minikube. If you do want to specify resources, uncomment the following
49 | # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
50 | # limits:
51 | # cpu: 100m
52 | # memory: 128Mi
53 | # requests:
54 | # cpu: 100m
55 | # memory: 128Mi
56 |
57 | nodeSelector: {}
58 |
59 | tolerations: []
60 |
61 | affinity: {}
62 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/user/containers/PersonList/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { RouteComponentProps, withRouter } from 'react-router';
4 | import * as S from 'ufc-schema';
5 |
6 | import { AppState } from '@/ducks';
7 |
8 | import { userActions } from '../../ducks/user';
9 |
10 | import styles from './index.less';
11 |
12 | export interface IUserMeshProps extends RouteComponentProps {
13 | users: S.User[];
14 | loadUsers: () => void;
15 | }
16 |
17 | export interface IUserMeshState {
18 | visible: boolean;
19 | pageSize: number;
20 | pageNum: number;
21 | }
22 |
23 | export class UserListComp extends React.PureComponent<
24 | IUserMeshProps,
25 | IUserMeshState
26 | > {
27 | constructor(props: IUserMeshProps) {
28 | super(props);
29 |
30 | this.state = {
31 | visible: false,
32 | pageSize: 1,
33 | pageNum: 10,
34 | };
35 | }
36 |
37 | async componentDidMount() {
38 | this.props.loadUsers();
39 | }
40 |
41 | showModal = () => {
42 | this.setState({ visible: true });
43 | };
44 |
45 | handleCancel = () => {
46 | this.setState({ visible: false });
47 | };
48 |
49 | handleOk = () => {
50 | this.setState({ visible: false });
51 | };
52 |
53 | render() {
54 | const { users } = this.props;
55 |
56 | return {JSON.stringify(users)}
;
57 | }
58 | }
59 |
60 | export const UserList = connect(
61 | (state: AppState) => ({
62 | users: state.user.users,
63 | }),
64 | {
65 | loadUsers: userActions.loadUsers,
66 | },
67 | )(withRouter(UserListComp));
68 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/helm/charts/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: {{ include "context.fullname" . }}
5 | labels:
6 | {{ include "context.labels" . | indent 4 }}
7 | spec:
8 | replicas: {{ .Values.replicaCount }}
9 | selector:
10 | matchLabels:
11 | app.kubernetes.io/name: {{ include "context.name" . }}
12 | app.kubernetes.io/instance: {{ .Release.Name }}
13 | template:
14 | metadata:
15 | labels:
16 | app.kubernetes.io/name: {{ include "context.name" . }}
17 | app.kubernetes.io/instance: {{ .Release.Name }}
18 | spec:
19 | ## imagePullSecrets
20 | {{- with .Values.imagePullSecrets }}
21 | imagePullSecrets:
22 | {{- toYaml . | nindent 8 }}
23 | {{- end }}
24 | ## containers
25 | containers:
26 | - name: test-web-client
27 | image: {{ template "context.image" . }}
28 | imagePullPolicy: {{ .Values.context.image.pullPolicy }}
29 | ports:
30 | - name: http
31 | containerPort: {{ .Values.context.service.port }}
32 | protocol: TCP
33 | resources:
34 | {{- toYaml .Values.resources | nindent 12 }}
35 |
36 | ## node selector
37 | {{- with .Values.nodeSelector }}
38 | nodeSelector:
39 | {{- toYaml . | nindent 8 }}
40 | {{- end }}
41 | ## affinity
42 | {{- with .Values.affinity }}
43 | affinity:
44 | {{- toYaml . | nindent 8 }}
45 | {{- end }}
46 | ## tolerations
47 | {{- with .Values.tolerations }}
48 | tolerations:
49 | {{- toYaml . | nindent 8 }}
50 | {{- end }}
51 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/dns.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/containers/Home/index.scss:
--------------------------------------------------------------------------------
1 | .app-container {
2 | width: 100vw;
3 | height: 100vh;
4 | min-width: 900px;
5 | display: grid;
6 | grid-template-columns: 130px 1fr;
7 | grid-template-rows: 100%;
8 | grid-template-areas: 'sidebar content';
9 |
10 | .update-mask {
11 | width: 100vw;
12 | height: 100vh;
13 | position: fixed;
14 | z-index: 1000;
15 | top: 0;
16 | left: 0;
17 | background-color: rgba(0, 0, 0, 0.65);
18 | padding: 0 180px;
19 |
20 | &.flex-container {
21 | flex-direction: column;
22 | }
23 |
24 | .ant-progress-text {
25 | color: #fff;
26 | }
27 |
28 | .update-tip {
29 | color: #fff;
30 | font-size: 22px;
31 | }
32 | }
33 |
34 | .side-bar {
35 | position: relative;
36 | grid-area: sidebar;
37 | background-color: #2e72b8;
38 | overflow: hidden;
39 |
40 | .logo {
41 | width: 60px;
42 | height: 94px;
43 | margin: 30px auto 23px auto;
44 | background-image: url('../../../../assets/logo_u_white.svg');
45 | background-size: 100% 100%;
46 | }
47 |
48 | .version {
49 | position: absolute;
50 | bottom: 0;
51 | left: 0;
52 | width: 100%;
53 | color: #fff;
54 | text-align: center;
55 | font-size: 12px;
56 | font-weight: 200;
57 | }
58 | }
59 |
60 | .content {
61 | grid-area: content;
62 | }
63 |
64 | .toolbar-icon {
65 | margin-right: 5px;
66 | transition: color 0.2s ease;
67 |
68 | &:last-child {
69 | margin-right: 0;
70 | }
71 | }
72 | }
73 |
74 | .anticon {
75 | font-size: 17px;
76 | }
77 |
78 | ::-webkit-scrollbar {
79 | display: none;
80 | }
81 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/ducks/common.ts:
--------------------------------------------------------------------------------
1 | import { createActions, handleActions } from 'redux-actions';
2 | import { handle } from 'redux-pack-fsa';
3 |
4 | export interface IState {
5 | count: number;
6 | }
7 |
8 | const initialState: IState = {
9 | count: 0,
10 | };
11 |
12 | export const actions = createActions({
13 | async incr(step = 1) {
14 | return step;
15 | },
16 |
17 | async dec(step = 1) {
18 | return step;
19 | },
20 |
21 | async error() {
22 | throw new Error('Error');
23 | },
24 | });
25 |
26 | export const commonActions = actions;
27 |
28 | export default handleActions(
29 | {
30 | [actions.incr.toString()](state: IState, action) {
31 | const { payload } = action;
32 |
33 | return handle(state, action, {
34 | start: (prevState: IState) => ({
35 | ...prevState,
36 | isLoading: true,
37 | }),
38 | finish: (prevState: IState) => ({ ...prevState, isLoading: false }),
39 | success: (prevState: IState) => ({
40 | ...prevState,
41 | count: prevState.count + payload,
42 | }),
43 | });
44 | },
45 |
46 | [actions.dec.toString()](state: IState, action) {
47 | const { payload } = action;
48 |
49 | return handle(state, action, {
50 | success: (prevState: IState) => ({
51 | ...prevState,
52 | count: prevState.count - payload,
53 | }),
54 | });
55 | },
56 |
57 | [actions.error.toString()](state: IState, action) {
58 | const { payload } = action;
59 | return handle(state, action, {
60 | failure: (prevState: IState) => ({ ...prevState, error: payload }),
61 | });
62 | },
63 | },
64 | initialState,
65 | );
66 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/withAuth.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { checkIsInstantiation } from '../types/comp';
4 |
5 | import { checkPermissions } from './permissions';
6 |
7 | /**
8 | * 默认不能访问任何页面
9 | * default is "NULL"
10 | */
11 | const Exception403 = () => 403;
12 |
13 | /**
14 | * 用于判断是否拥有权限访问此 view 权限
15 | * authority 支持传入 string, () => boolean | Promise
16 | * e.g. 'user' 只有 user 用户能访问
17 | * e.g. 'user,admin' user 和 admin 都能访问
18 | * e.g. ()=>boolean 返回true能访问,返回false不能访问
19 | * e.g. Promise then 能访问 catch不能访问
20 | * e.g. authority support incoming string, () => boolean | Promise
21 | * e.g. 'user' only user user can access
22 | * e.g. 'user, admin' user and admin can access
23 | * e.g. () => boolean true to be able to visit, return false can not be accessed
24 | * e.g. Promise then can not access the visit to catch
25 | * @param {string | function | Promise} authority
26 | * @param {ReactNode} error 非必需参数
27 | */
28 | export const withAuth = (authority: string, error?: React.ReactNode) => {
29 | /**
30 | * conversion into a class
31 | * 防止传入字符串时找不到staticContext造成报错
32 | * String parameters can cause staticContext not found error
33 | */
34 | let classError: boolean | React.FunctionComponent = false;
35 | if (error) {
36 | classError = (() => error) as React.FunctionComponent;
37 | }
38 |
39 | if (!authority) {
40 | throw new Error('authority is required');
41 | }
42 |
43 | return function decideAuthority(
44 | target: React.ComponentClass | React.ReactNode,
45 | ) {
46 | const component = checkPermissions(
47 | authority,
48 | target,
49 | classError || Exception403,
50 | );
51 | return checkIsInstantiation(component);
52 | };
53 | };
54 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/LangSelector/index.tsx:
--------------------------------------------------------------------------------
1 | import { GlobalOutlined } from '@ant-design/icons';
2 | import { Menu } from 'antd';
3 | import { ClickParam } from 'antd/es/menu';
4 | import classNames from 'classnames';
5 | import * as React from 'react';
6 |
7 | import { formatMessage, getLocale, setLocale } from '@/i18n';
8 |
9 | import HeaderDropdown from '../HeaderDropdown';
10 |
11 | import styles from './index.less';
12 |
13 | interface SelectLangProps {
14 | className?: string;
15 | }
16 | const SelectLang: React.FC = props => {
17 | const { className } = props;
18 | const selectedLang = getLocale();
19 | const changeLang = ({ key }: ClickParam): void => setLocale(key, false);
20 | const locales = ['zh-CN', 'zh-TW', 'en-US', 'pt-BR'];
21 | const languageLabels = {
22 | 'zh-CN': '简体中文',
23 | 'zh-TW': '繁体中文',
24 | 'en-US': 'English',
25 | 'pt-BR': 'Português',
26 | };
27 | const languageIcons = {
28 | 'zh-CN': '🇨🇳',
29 | 'zh-TW': '🇭🇰',
30 | 'en-US': '🇬🇧',
31 | 'pt-BR': '🇧🇷',
32 | };
33 | const langMenu = (
34 |
48 | );
49 | return (
50 |
51 |
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default SelectLang;
59 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/GlobalHeader/UserDropdown.tsx:
--------------------------------------------------------------------------------
1 | import { LogoutOutlined } from '@ant-design/icons';
2 | import { Avatar, Menu, Spin } from 'antd';
3 | import { ClickParam } from 'antd/es/menu';
4 | import * as React from 'react';
5 | import { FormattedMessage } from 'react-intl';
6 |
7 | import { logout } from '@/apis';
8 | import { CurrentUser } from '@/models';
9 |
10 | import HeaderDropdown from '../HeaderDropdown';
11 |
12 | import styles from './index.less';
13 |
14 | export interface GlobalHeaderRightProps {
15 | currentUser?: CurrentUser;
16 | menu?: boolean;
17 | }
18 |
19 | export class UserDropdown extends React.Component {
20 | onMenuClick = (event: ClickParam) => {
21 | const { key } = event;
22 |
23 | if (key === 'logout') {
24 | logout();
25 | }
26 | };
27 |
28 | render(): React.ReactNode {
29 | const { currentUser = { avatar: '', name: '' } } = this.props;
30 |
31 | const menuHeaderDropdown = (
32 |
42 | );
43 |
44 | return currentUser && currentUser.name ? (
45 |
46 |
47 |
53 | {currentUser.name}
54 |
55 |
56 | ) : (
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/scripts/helm/charts/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "context.name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
11 | If release name contains chart name it will be used as a full name.
12 | */}}
13 | {{- define "context.fullname" -}}
14 | {{- if .Values.fullnameOverride -}}
15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
16 | {{- else -}}
17 | {{- $name := default .Chart.Name .Values.nameOverride -}}
18 | {{- if contains $name .Release.Name -}}
19 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}}
20 | {{- else -}}
21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
22 | {{- end -}}
23 | {{- end -}}
24 | {{- end -}}
25 |
26 | {{/*
27 | Create chart name and version as used by the chart label.
28 | */}}
29 | {{- define "context.chart" -}}
30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
31 | {{- end -}}
32 |
33 | {{/*
34 | Common labels
35 | */}}
36 | {{- define "context.labels" -}}
37 | app.kubernetes.io/name: {{ include "context.name" . }}
38 | helm.sh/chart: {{ include "context.chart" . }}
39 | app.kubernetes.io/instance: {{ .Release.Name }}
40 | {{- if .Chart.AppVersion }}
41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
42 | {{- end }}
43 | app.kubernetes.io/managed-by: {{ .Release.Service }}
44 | {{- end -}}
45 |
46 | {{/*
47 | Image Name
48 | 镜像名
49 | */}}
50 | {{- define "context.image" -}}
51 | {{- $registryName := .Values.context.image.registry -}}
52 | {{- $repositoryName := .Values.context.image.repository -}}
53 | {{- $tag := .Values.context.image.tag | toString -}}
54 | {{- printf "%s/%s:%s" $registryName $repositoryName $tag -}}
55 | {{- end -}}
56 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/schema/helpers.ts:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 |
3 | export const ifExist = (
4 | toCheck: T | null | undefined,
5 | truthyValue: T = toCheck,
6 | fallback?: T,
7 | ) => (toCheck ? truthyValue : ((fallback || '') as T));
8 |
9 | export type DeepPartial = {
10 | [key in keyof T]?: DeepPartial;
11 | };
12 |
13 | export type Type = new (...args: unknown[]) => T;
14 |
15 | /** 分组并且填充 */
16 | export function chunkWithFill(
17 | array: any[],
18 | size: number,
19 | fillObj: any,
20 | ): any[][] {
21 | const chunkedArray = _.chunk(array, size);
22 | const lastChunk = chunkedArray[chunkedArray.length - 1];
23 |
24 | if (fillObj !== undefined && lastChunk.length < size) {
25 | lastChunk.push(
26 | ...Array.from({
27 | length: size - lastChunk.length,
28 | }).map(() => fillObj),
29 | );
30 | }
31 |
32 | return chunkedArray;
33 | }
34 |
35 | /** 添加或删除 */
36 | export function addOrRemove(arr: any[], obj: any) {
37 | if (!arr || !Array.isArray(arr)) {
38 | return arr;
39 | }
40 |
41 | // 存在则删除
42 | if (arr.indexOf(obj) > -1) {
43 | return arr.filter(a => a !== obj);
44 | }
45 |
46 | return [...arr, obj];
47 | }
48 |
49 | /** 插入到某个数组中 */
50 | export const insertArray = (arr: any[], index: number, newItem: any) => [
51 | ...arr.slice(0, index),
52 |
53 | newItem,
54 |
55 | ...arr.slice(index),
56 | ];
57 |
58 | /** 格式化百分比 */
59 | export function formatPercent(percent: number) {
60 | return `${(percent * 100).toFixed(2)}%`;
61 | }
62 |
63 | /** 将某个对象转化为 KV */
64 | export function transformToKV(obj: object) {
65 | const kvEntries: {
66 | key: string;
67 | type: string;
68 | value: string | number;
69 | }[] = [];
70 |
71 | Object.keys(obj).forEach(k => {
72 | let type;
73 |
74 | if (typeof obj[k] === 'number') {
75 | type = 'LONG';
76 | } else {
77 | type = 'STRING';
78 | }
79 |
80 | kvEntries.push({
81 | key: k,
82 | type,
83 | value: obj[k],
84 | });
85 | });
86 |
87 | return kvEntries;
88 | }
89 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/CopyBlock/index.tsx:
--------------------------------------------------------------------------------
1 | import { DownloadOutlined } from '@ant-design/icons';
2 | import { Popover, Typography } from 'antd';
3 | import React, { useRef } from 'react';
4 | import { FormattedMessage } from 'react-intl';
5 |
6 | import styles from './index.less';
7 |
8 | export const firstUpperCase = (pathString: string): string =>
9 | pathString
10 | .replace('.', '')
11 | .split(/\/|-/)
12 | .map((s): string =>
13 | s.toLowerCase().replace(/( |^)[a-z]/g, L => L.toUpperCase()),
14 | )
15 | .filter((s): boolean => !!s)
16 | .join('');
17 |
18 | // when click block copy, send block text to ga
19 | const onBlockCopy = (label: string) => {
20 | const ga = window && window.ga;
21 | if (ga) {
22 | ga('send', 'event', {
23 | eventCategory: 'block',
24 | eventAction: 'copy',
25 | eventLabel: label,
26 | });
27 | }
28 | };
29 |
30 | const BlockCodeView: React.SFC<{
31 | text: string;
32 | }> = ({ text }) => {
33 | return (
34 |
35 |
onBlockCopy(text),
39 | }}
40 | style={{
41 | display: 'flex',
42 | }}
43 | >
44 |
45 | {text}
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default () => {
53 | const divDom = useRef(null);
54 | return (
55 |
61 | }
62 | placement="topLeft"
63 | content={}
64 | trigger="click"
65 | getPopupContainer={dom => (divDom.current ? divDom.current : dom)}
66 | >
67 |
68 |
69 |
70 |
71 | );
72 | };
73 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/public/service-worker.js:
--------------------------------------------------------------------------------
1 | /* globals workbox */
2 | workbox.core.setCacheNameDetails({
3 | prefix: 'antd-pro',
4 | suffix: 'v1'
5 | });
6 | // Control all opened tabs ASAP
7 | workbox.clientsClaim();
8 |
9 | /**
10 | * Use precaching list generated by workbox in build process.
11 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
12 | */
13 | workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
14 |
15 | /**
16 | * Register a navigation route.
17 | * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
18 | */
19 | workbox.routing.registerNavigationRoute('/index.html');
20 |
21 | /**
22 | * Use runtime cache:
23 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
24 | *
25 | * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
26 | * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
27 | */
28 |
29 | /**
30 | * Handle API requests
31 | */
32 | workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
33 |
34 | /**
35 | * Handle third party requests
36 | */
37 | workbox.routing.registerRoute(
38 | /^https:\/\/gw.alipayobjects.com\//,
39 | workbox.strategies.networkFirst()
40 | );
41 | workbox.routing.registerRoute(
42 | /^https:\/\/cdnjs.cloudflare.com\//,
43 | workbox.strategies.networkFirst()
44 | );
45 | workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
46 |
47 | /**
48 | * Response to client after skipping waiting with MessageChannel
49 | */
50 | addEventListener('message', event => {
51 | const replyPort = event.ports[0];
52 | const message = event.data;
53 | if (replyPort && message && message.type === 'skip-waiting') {
54 | event.waitUntil(
55 | self
56 | .skipWaiting()
57 | .then(
58 | () => replyPort.postMessage({ error: null }),
59 | error => replyPort.postMessage({ error })
60 | )
61 | );
62 | }
63 | });
64 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/utils/errors.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Represents an enhanced `Error`.
3 | */
4 | export interface EnhancedError extends Error {
5 | /**
6 | * The error code, if available.
7 | */
8 | code?: number | string;
9 |
10 | /**
11 | * The request, if available.
12 | */
13 | request?: any;
14 |
15 | /**
16 | * The response, if available.
17 | */
18 | response?: any;
19 |
20 | /**
21 | * The meta data, if available.
22 | */
23 | meta?: any;
24 | }
25 |
26 | /**
27 | * Creates an `EnhancedError` object with the specified
28 | * message, error code, request, response and meta data.
29 | *
30 | * @param {string} message The error message.
31 | * @param {(number | string)} [code] The error code.
32 | * @param {any} [request] The request.
33 | * @param {any} [response] The response.
34 | * @param {any} [meta] The meta data.
35 | * @returns {Error} The created error.
36 | */
37 | export function createError(
38 | message?: string,
39 | code?: number | string,
40 | request?: any,
41 | response?: any,
42 | meta?: any,
43 | ): EnhancedError {
44 | return enhanceError(new Error(message), code, request, response, meta);
45 | }
46 |
47 | /**
48 | * Enhances an existing `Error` or `EnhancedError` object with the specified
49 | * error code, request, response and meta data.
50 | *
51 | * @param {(Error | EnhancedError)} error The error to enhance.
52 | * @param {(number | string)} [code] The error code.
53 | * @param {any} [request] The request.
54 | * @param {any} [response] The response.
55 | * @param {any} [meta] The meta data.
56 | */
57 | export function enhanceError(
58 | error: Error | EnhancedError,
59 | code?: number | string,
60 | request?: any,
61 | response?: any,
62 | meta?: any,
63 | ): EnhancedError {
64 | const e: EnhancedError = error;
65 | if (code !== undefined) {
66 | e.code = code;
67 | }
68 |
69 | if (request !== undefined) {
70 | e.request = request;
71 | }
72 |
73 | if (response !== undefined) {
74 | e.response = response;
75 | }
76 |
77 | if (meta !== undefined) {
78 | e.meta = meta;
79 | }
80 | return e;
81 | }
82 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/InstallableAppView/index.tsx:
--------------------------------------------------------------------------------
1 | import { Progress } from 'antd';
2 | import React, { Component } from 'react';
3 |
4 | import { InstallableApp } from 'rte-core';
5 |
6 | import './index.scss';
7 |
8 | const icons = {};
9 |
10 | interface InstallableAppViewProps {
11 | data: InstallableApp;
12 | }
13 |
14 | export class InstallableAppView extends Component {
15 | render() {
16 | const product = this.props.data;
17 | const {
18 | label,
19 | icon,
20 | isInstalled,
21 | downloading,
22 | downloadProgess,
23 | hidden,
24 | order,
25 | } = product;
26 | if (hidden) {
27 | return null;
28 | }
29 | let button: JSX.Element;
30 | if (downloading) {
31 | button = (
32 |
39 | );
40 | } else if (isInstalled) {
41 | button = (
42 | product.run()}
45 | >
46 | 打开
47 |
48 | );
49 | } else if (!product.url) {
50 | button = (
51 |
52 | 即将上线
53 |
54 | );
55 | } else {
56 | button = (
57 | product.install()}
60 | >
61 | 安装
62 |
63 | );
64 | }
65 | // const iconUrl = icon
66 | return (
67 |
68 |
isInstalled && product.run()}
72 | />
73 |
{label}
74 | {button}
75 |
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/assets/qq.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { ConfigProvider } from 'antd';
2 | import 'antd/dist/antd.less'; // 引入官方提供的 less 样式入口文件
3 | import zhCN from 'antd/lib/locale-provider/zh_CN';
4 | import { ConnectedRouter } from 'connected-react-router';
5 | import dayjs from 'dayjs';
6 | import timeZone from 'dayjs-ext/plugin/timeZone';
7 | import zh from 'dayjs/locale/zh-cn';
8 | import advancedFormat from 'dayjs/plugin/advancedFormat';
9 | import badMutable from 'dayjs/plugin/badMutable';
10 | import customParseFormat from 'dayjs/plugin/customParseFormat';
11 | import isMoment from 'dayjs/plugin/isMoment';
12 | import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
13 | import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
14 | import localeData from 'dayjs/plugin/localeData';
15 | import relativeTime from 'dayjs/plugin/relativeTime';
16 | import utc from 'dayjs/plugin/utc';
17 | import weekOfYear from 'dayjs/plugin/weekOfYear';
18 | import weekYear from 'dayjs/plugin/weekYear';
19 | import * as React from 'react';
20 | import * as ReactDOM from 'react-dom';
21 | import { IntlProvider } from 'react-intl';
22 | import { Provider } from 'react-redux';
23 | import * as smoothscroll from 'smoothscroll-polyfill';
24 |
25 | import App from './skeleton/containers/App';
26 | import { history } from './skeleton/env/history';
27 | import store from './skeleton/env/store';
28 |
29 | import './skeleton/styles/reset.less';
30 |
31 | smoothscroll.polyfill();
32 |
33 | dayjs.extend(utc);
34 | dayjs.extend(advancedFormat);
35 | dayjs.extend(badMutable);
36 | dayjs.extend(customParseFormat);
37 | dayjs.extend(isMoment);
38 | dayjs.extend(isSameOrAfter);
39 | dayjs.extend(isSameOrBefore);
40 | dayjs.extend(localeData);
41 | dayjs.extend(relativeTime);
42 | dayjs.extend(weekOfYear);
43 | dayjs.extend(weekYear);
44 | dayjs.extend(timeZone);
45 | dayjs.extend(utc);
46 |
47 | dayjs.locale(zh);
48 |
49 | if (!window.gConfig) {
50 | window.gConfig = {};
51 | }
52 |
53 | ReactDOM.render(
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ,
63 | document.getElementById('root'),
64 | );
65 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/containers/AppContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import { Spin } from 'antd';
2 | import cn from 'classnames';
3 | import * as React from 'react';
4 | import { RouteComponentProps } from 'react-router';
5 | import { withRouter } from 'react-router-dom';
6 |
7 | import { ResolvedModule } from '@/skeleton/types';
8 |
9 | const { PureComponent, Suspense, lazy } = React;
10 |
11 | interface IProps extends RouteComponentProps {
12 | appId: string;
13 | appLoader: () => Promise
;
14 | className?: string;
15 | fallback?: JSX.Element;
16 | onAppendReducer: (reducer: { [key: string]: object | undefined }) => void;
17 | }
18 |
19 | interface IState {
20 | appError?: any;
21 | }
22 |
23 | // 应用缓存
24 | const appCache = {};
25 |
26 | /**
27 | * 应用懒加载与容错的容器
28 | */
29 | class AppContainer extends PureComponent {
30 | static defaultProps = {
31 | fallback: ,
32 | };
33 |
34 | state: IState = {};
35 |
36 | loadApp() {
37 | const { appLoader, appId, onAppendReducer } = this.props;
38 |
39 | if (appCache[appId]) {
40 | return appCache[appId];
41 | }
42 |
43 | const app = lazy(() =>
44 | appLoader().then(appModule => {
45 | if ('reducer' in appModule) {
46 | onAppendReducer({
47 | [appId]: appModule.reducer,
48 | });
49 | }
50 |
51 | return appModule;
52 | }),
53 | );
54 |
55 | appCache[appId] = app;
56 |
57 | return app;
58 | }
59 |
60 | componentDidCatch(error: object, errorInfo: object) {
61 | this.setState({ appError: { error, errorInfo } });
62 | }
63 |
64 | renderErrorPage() {
65 | const { appError } = this.state;
66 |
67 | return ;
68 | }
69 |
70 | render() {
71 | const { className, fallback, appId } = this.props;
72 | const { appError } = this.state;
73 |
74 | if (appError) {
75 | return this.renderErrorPage();
76 | }
77 |
78 | const App = this.loadApp();
79 |
80 | return (
81 |
86 | );
87 | }
88 | }
89 |
90 | export default withRouter(AppContainer);
91 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rte-core",
3 | "version": "0.0.1",
4 | "description": "rte-core",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/wx-chevalier/fe-boilerplates"
8 | },
9 | "author": "wx-chevalier@github",
10 | "license": "MIT",
11 | "main": "dist/cjs/index.js",
12 | "module": "dist/es/index.js",
13 | "types": "dist/types/index.d.ts",
14 | "files": [
15 | "dist/"
16 | ],
17 | "keywords": [
18 | "webpack",
19 | "react"
20 | ],
21 | "scripts": {
22 | "build": "npm run build:es && npm run build:cjs",
23 | "build:cjs": "tsc --project ./tsconfig.cjs.json",
24 | "build:es": "tsc --project ./tsconfig.es.json",
25 | "clean": "rimraf dist",
26 | "clean:r": "rimraf ./dist/*.map && rimraf ./dist/**/*.map && rimraf ./dist/**/*.tsbuildinfo",
27 | "dev": "tsc -w --project ./tsconfig.cjs.json",
28 | "lint": "run-p lint:*",
29 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts,tsx -f friendly --max-warnings 10",
30 | "lint:ts": "tslint -p . -t stylish",
31 | "lint:tsc": "tsc -p tsconfig.json --incremental false --noEmit",
32 | "prepublish": "npm run clean:r",
33 | "test": "jest --config ./scripts/jest/jest.config.js",
34 | "test:cov": "npm run cleanCov && npm test -- --coverage",
35 | "test:watch": "npm test -- --watch"
36 | },
37 | "dependencies": {
38 | "ueact-utils": "^0.2.4"
39 | },
40 | "devDependencies": {
41 | "@m-fe/app-config": "^0.4.3",
42 | "cross-env": "^6.0.3",
43 | "webpack": "^4.41.2"
44 | },
45 | "browserslist": [
46 | "extends @m-fe/browserslist-config"
47 | ],
48 | "commitlint": {
49 | "extends": [
50 | "@m-fe"
51 | ]
52 | },
53 | "prettier": "@m-fe/prettier-config/semi",
54 | "remarkConfig": {
55 | "plugins": [
56 | "@m-fe/remark-config"
57 | ]
58 | },
59 | "stylelint": {
60 | "extends": [
61 | "@m-fe/stylelint-config",
62 | "@m-fe/stylelint-config/modules"
63 | ],
64 | "rules": {
65 | "font-family-no-missing-generic-family-keyword": null,
66 | "no-descending-specificity": null,
67 | "plugin/no-unsupported-browser-features": null,
68 | "plugin/no-low-performance-animation-properties": null
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/Menu/index.tsx:
--------------------------------------------------------------------------------
1 | import classnams from 'classnames';
2 | import React, { Component } from 'react';
3 |
4 | import './index.scss';
5 |
6 | type Key = React.Key | null;
7 |
8 | interface MenuItemProps {
9 | isSelected?: boolean;
10 | children?: React.ReactNode;
11 | onClick?: () => void;
12 | }
13 |
14 | const MenuItem = (props: MenuItemProps) => {
15 | const cl: string = classnams('memu-item-container', {
16 | 'menu-item-selected': props.isSelected,
17 | });
18 | return (
19 |
20 |
{props.children}
21 |
22 | );
23 | };
24 |
25 | interface MenuProps {
26 | defaultKey?: string;
27 | selectedKey?: string;
28 | onSelect?: (key: Key) => void;
29 | }
30 |
31 | interface MenuState {
32 | selectedKey: Key;
33 | }
34 |
35 | export class Menu extends Component {
36 | static Item = MenuItem;
37 |
38 | constructor(props: MenuProps) {
39 | super(props);
40 | let selectedKey: Key = null;
41 | if ('defaultKey' in props) {
42 | selectedKey = props.defaultKey!;
43 | }
44 | if ('selectedKey' in props) {
45 | selectedKey = props.selectedKey!;
46 | }
47 | this.state = {
48 | selectedKey: selectedKey,
49 | };
50 | }
51 |
52 | componentWillReceiveProps(newProps: MenuProps) {
53 | if ('selectedKey' in newProps) {
54 | this.setState({
55 | selectedKey: newProps.selectedKey!,
56 | });
57 | }
58 | }
59 |
60 | handleItemClick = (key: Key) => {
61 | if (!('selectedKey' in this.props)) {
62 | this.setState({
63 | selectedKey: key,
64 | });
65 | }
66 | if (typeof this.props.onSelect === 'function') {
67 | this.props.onSelect(key);
68 | }
69 | };
70 |
71 | render() {
72 | return (
73 |
74 | {React.Children.map(
75 | this.props.children,
76 | (item: React.ReactElement) => {
77 | return React.cloneElement(item, {
78 | isSelected: this.state.selectedKey === item.key,
79 | onClick: () => this.handleItemClick(item.key),
80 | } as MenuItemProps);
81 | },
82 | )}
83 |
84 | );
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/api/axios/agent.ts:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 | import axios from 'axios';
3 |
4 | import { HOST } from '@/skeleton';
5 |
6 | // 需要自定义的几个地方
7 | // 1. base URL
8 | // 2. timeout 时间
9 | // 3. 拦截器, 用于字段名的转换
10 | // 4. 错误, 触发一些全局的提示
11 | export const axiosAgent = axios.create({
12 | baseURL: HOST,
13 | timeout: 30 * 60 * 1000,
14 | });
15 |
16 | export const uploadAgent = axios.create({
17 | baseURL: HOST,
18 | timeout: 15 * 1000,
19 | });
20 |
21 | export type NoticeType = 'success' | 'error' | 'info' | 'trusteeship';
22 |
23 | export type Notice = (type: NoticeType, title: string, content: string) => void;
24 |
25 | export const notice: Notice = (type, title, content) => {
26 | notification[type]({
27 | message: title,
28 | description: content,
29 | });
30 | };
31 |
32 | // // 拦截器用来将驼峰和下划线两种风格互转,根据 server 端命名风格决定是否使用
33 |
34 | // // 驼峰转下划线
35 | // agent.interceptors.request.use(
36 | // config => {
37 | // if (config.data) {
38 | // config.data = transformPropertyName(config.data, key =>
39 | // key.replace(/[A-Z]/g, str => '_' + str.toLowerCase())
40 | // );
41 | // }
42 |
43 | // if (config.params) {
44 | // config.params = transformPropertyName(config.params, key =>
45 | // key.replace(/[A-Z]/g, str => '_' + str.toLowerCase())
46 | // );
47 | // }
48 |
49 | // return config;
50 | // },
51 | // error => Promise.reject(error)
52 | // );
53 |
54 | // // 下划线转驼峰
55 | // agent.interceptors.response.use(
56 | // response => {
57 | // response.data = transformPropertyName(response.data, key =>
58 | // key.replace(/\_./g, str => str[1].toUpperCase())
59 | // );
60 |
61 | // return response;
62 | // },
63 | // error => Promise.reject(error)
64 | // );
65 |
66 | // function transformPropertyName(data: object, transformer: (key: string) => string): object {
67 | // if (!isObject(data)) {
68 | // return data;
69 | // } else {
70 | // const newObj = Array.isArray(data) ? [] : {};
71 | // Object.keys(data).forEach(key => {
72 | // const newKey = transformer(key);
73 | // newObj[newKey] = transformPropertyName(data[key], transformer);
74 | // });
75 | // return newObj;
76 | // }
77 | // }
78 |
79 | // // tslint:disable-next-line
80 | // function isObject(data: any) {
81 | // return data !== null && typeof data === 'object';
82 | // }
83 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-node/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rte-node",
3 | "version": "0.0.1",
4 | "description": "Pre-configured boilerplate for Electron, React, TypeScript",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/wx-chevalier/m-fe-electron"
8 | },
9 | "homepage": "https://github.com/wx-chevalier/m-fe-electron#readme",
10 | "bugs": {
11 | "url": "https://github.com/wx-chevalier/m-fe-electron/issues"
12 | },
13 | "author": "wx-chevalier@github",
14 | "license": "MIT",
15 | "main": "./build/index.js",
16 | "keywords": [
17 | "boilerplate",
18 | "Electron",
19 | "React",
20 | "Typescript",
21 | "Webpack"
22 | ],
23 | "scripts": {
24 | "build": "rimraf build && cross-env NODE_ENV=production webpack --config ./scripts/webpack/webpack.config.js --progress --colors -p",
25 | "clean": "rimraf build",
26 | "lint": "run-p lint:*",
27 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts,tsx -f friendly --max-warnings 10",
28 | "lint:ts": "tslint -p . -t stylish",
29 | "lint:tsc": "tsc -p tsconfig.json --incremental false --noEmit",
30 | "start": "npm run start:dev-server",
31 | "start:dev-server": "rimraf build && cross-env NODE_ENV=development webpack --config ./scripts/webpack/webpack.config.js --watch --progress --colors",
32 | "start:main": "cross-env ELECTRON_START_URL=http://localhost:8080 electron ./build/index.js",
33 | "test": "mocha -r ts-node/register -r tsconfig-paths/register \"test/**/*.ts\""
34 | },
35 | "dependencies": {
36 | "@ravshansbox/browser-crypto": "^1.0.0",
37 | "electron-debug": "^3.0.1",
38 | "electron-log": "^4.1.0",
39 | "electron-updater": "^4.2.5"
40 | },
41 | "devDependencies": {
42 | "@m-fe/app-config": "^0.4.3",
43 | "acorn": "^7.1.1",
44 | "ajv": "^6.12.0",
45 | "babel-loader": "^8.1.0",
46 | "cache-loader": "^4.1.0",
47 | "copy-pkg-json-webpack-plugin": "^0.0.38",
48 | "core-js": "^3.6.4",
49 | "cross-env": "^7.0.2",
50 | "electron": "8.2.0",
51 | "electron-builder": "^22.4.0",
52 | "eslint": "^6.8.0",
53 | "npm-run-all": "^4.1.5",
54 | "regedit": "^3.0.3",
55 | "rimraf": "^3.0.2",
56 | "rte-core": "*",
57 | "thread-loader": "^2.1.3",
58 | "tslint": "^6.0.0",
59 | "typescript": "^3.8.3",
60 | "webpack": "4.41.2",
61 | "webpack-cli": "^3.3.11"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/api/umi/request.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * request 网络请求工具
3 | * 更详细的 api 文档: https://github.com/umijs/umi-request
4 | */
5 | import { notification } from 'antd';
6 | import { extend } from 'umi-request';
7 |
8 | import { history } from '@/skeleton/env/history';
9 |
10 | const codeMessage = {
11 | 200: '服务器成功返回请求的数据。',
12 | 201: '新建或修改数据成功。',
13 | 202: '一个请求已经进入后台排队(异步任务)。',
14 | 204: '删除数据成功。',
15 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
16 | 401: '用户没有权限(令牌、用户名、密码错误),请重新登录',
17 | 403: '用户得到授权,但是访问是被禁止的。',
18 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
19 | 406: '请求的格式不可得。',
20 | 410: '请求的资源被永久删除,且不会再得到的。',
21 | 422: '当创建一个对象时,发生一个验证错误。',
22 | 500: '服务器发生错误,请检查服务器。',
23 | 502: '网关错误。',
24 | 503: '服务不可用,服务器暂时过载或维护。',
25 | 504: '网关超时。',
26 | };
27 |
28 | /**
29 | * 异常处理程序
30 | */
31 | const errorHandler = async (error: {
32 | response: Response;
33 | }): Promise => {
34 | const { response } = error;
35 |
36 | try {
37 | if (response && response.status) {
38 | const errorText = codeMessage[response.status] || response.statusText;
39 | const { status } = response;
40 | const resp = await response.clone().json();
41 |
42 | if (resp.status === 'error' && resp.err) {
43 | notification.error({
44 | message: `请求错误 ${status}`,
45 | description: resp.err.reason || resp.err.code,
46 | });
47 | } else {
48 | notification.error({
49 | message: `请求错误 ${status}`,
50 | description: errorText,
51 | });
52 | }
53 | // 如果是 401,则跳转到登录
54 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers
55 | if (response.status === 401) {
56 | history.push('/auth/login');
57 | }
58 | } else if (!response) {
59 | notification.error({
60 | description: '您的网络发生异常,无法连接服务器',
61 | message: '网络异常',
62 | });
63 | }
64 | } catch (ie) {
65 | notification.error({
66 | description: '响应数据异常',
67 | message: '服务器异常',
68 | });
69 | }
70 |
71 | return response;
72 | };
73 |
74 | /** 配置 request 请求时的默认参数 */
75 | export let umiRequest = extend({
76 | errorHandler, // 默认错误处理
77 | credentials: 'same-origin', // 默认请求是否带上cookie
78 | });
79 |
80 | /** 动态设置请求头 */
81 | export function setRequestHeader(headers: Record) {
82 | umiRequest = extend({
83 | errorHandler, // 默认错误处理
84 | headers,
85 | credentials: 'same-origin', // 默认请求是否带上cookie
86 | });
87 | }
88 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/NoticeIcon/NoticeList.less:
--------------------------------------------------------------------------------
1 | @import '~antd/es/style/themes/default.less';
2 |
3 | .list {
4 | max-height: 400px;
5 | overflow: auto;
6 | &::-webkit-scrollbar {
7 | display: none;
8 | }
9 | .item {
10 | padding-right: 24px;
11 | padding-left: 24px;
12 | overflow: hidden;
13 | cursor: pointer;
14 | transition: all 0.3s;
15 |
16 | .meta {
17 | width: 100%;
18 | }
19 |
20 | .avatar {
21 | margin-top: 4px;
22 | background: #fff;
23 | }
24 | .iconElement {
25 | font-size: 32px;
26 | }
27 |
28 | &.read {
29 | opacity: 0.4;
30 | }
31 | &:last-child {
32 | border-bottom: 0;
33 | }
34 | &:hover {
35 | background: @primary-1;
36 | }
37 | .title {
38 | margin-bottom: 8px;
39 | font-weight: normal;
40 | }
41 | .description {
42 | font-size: 12px;
43 | line-height: @line-height-base;
44 | }
45 | .datetime {
46 | margin-top: 4px;
47 | font-size: 12px;
48 | line-height: @line-height-base;
49 | }
50 | .extra {
51 | float: right;
52 | margin-top: -1.5px;
53 | margin-right: 0;
54 | color: @text-color-secondary;
55 | font-weight: normal;
56 | }
57 | }
58 | .loadMore {
59 | padding: 8px 0;
60 | color: @primary-6;
61 | text-align: center;
62 | cursor: pointer;
63 | &.loadedAll {
64 | color: rgba(0, 0, 0, 0.25);
65 | cursor: unset;
66 | }
67 | }
68 | }
69 |
70 | .notFound {
71 | padding: 73px 0 88px;
72 | color: @text-color-secondary;
73 | text-align: center;
74 | img {
75 | display: inline-block;
76 | height: 76px;
77 | margin-bottom: 16px;
78 | }
79 | }
80 |
81 | .bottomBar {
82 | height: 46px;
83 | color: @text-color;
84 | line-height: 46px;
85 | text-align: center;
86 | border-top: 1px solid @border-color-split;
87 | border-radius: 0 0 @border-radius-base @border-radius-base;
88 | transition: all 0.3s;
89 | div {
90 | display: inline-block;
91 | width: 50%;
92 | cursor: pointer;
93 | transition: all 0.3s;
94 | user-select: none;
95 | &:hover {
96 | color: @heading-color;
97 | }
98 | &:only-child {
99 | width: 100%;
100 | }
101 | &:not(:only-child):last-child {
102 | border-left: 1px solid @border-color-split;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/components/GlobalHeader/RightContent.tsx:
--------------------------------------------------------------------------------
1 | import { QuestionCircleOutlined } from '@ant-design/icons';
2 | import { Tooltip } from 'antd';
3 | import * as React from 'react';
4 |
5 | import { getGlobalUser } from '@/apis';
6 | import { formatMessage } from '@/i18n';
7 |
8 | import HeaderSearch from '../HeaderSearch';
9 | import SelectLang from '../LangSelector';
10 |
11 | import { NoticeIconView } from './NoticeIconView';
12 | import { UserDropdown as User } from './UserDropdown';
13 |
14 | import styles from './index.less';
15 |
16 | export type SiderTheme = 'light' | 'dark';
17 | export interface RightContentProps {
18 | theme?: SiderTheme;
19 | layout?: 'sidemenu' | 'topmenu';
20 | }
21 |
22 | export const RightContent: React.SFC = props => {
23 | const { theme, layout } = props;
24 | let className = styles.right;
25 |
26 | if (theme === 'dark' && layout === 'topmenu') {
27 | className = `${styles.right} ${styles.dark}`;
28 | }
29 |
30 | return (
31 |
32 |
{
49 | console.log('input', value);
50 | }}
51 | onPressEnter={value => {
52 | console.log('enter', value);
53 | }}
54 | />
55 |
60 |
66 |
67 |
68 |
69 |
70 |
76 |
77 |
78 | );
79 | };
80 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/components/InstallableAppView/index.scss:
--------------------------------------------------------------------------------
1 | .product-container {
2 | width: 80px;
3 | height: 81px;
4 | display: inline-flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 |
9 | &:hover {
10 | .product-button {
11 | visibility: visible;
12 | }
13 | }
14 |
15 | .product-icon {
16 | width: 40px;
17 | height: 40px;
18 | background-size: cover;
19 | }
20 |
21 | .product-name {
22 | width: 100%;
23 | height: 17px;
24 | line-height: 17px;
25 | font-size: 12px;
26 | color: #4a4a4a;
27 | letter-spacing: 0.43px;
28 | overflow: hidden;
29 | text-overflow: ellipsis;
30 | white-space: nowrap;
31 | text-align: center;
32 |
33 | &:hover {
34 | overflow: visible;
35 | }
36 | }
37 |
38 | .product-button {
39 | width: 38px;
40 | height: 18px;
41 | line-height: 18px;
42 | border: 1px solid #2e72b8;
43 | border-radius: 2px;
44 | font-size: 12px;
45 | font-weight: 200;
46 | cursor: pointer;
47 | transition: all 0.1s ease;
48 | text-align: center;
49 | visibility: hidden;
50 | }
51 |
52 | .button-run {
53 | color: #2e72b8;
54 | background-color: #fff;
55 |
56 | &:hover {
57 | color: #fff;
58 | background-color: rgba(46, 114, 184, 0.8);
59 | }
60 | }
61 |
62 | .button-download {
63 | color: #fff;
64 | background-color: rgba(46, 114, 184, 0.9);
65 | }
66 |
67 | .button-disabled {
68 | justify-content: center;
69 | cursor: not-allowed;
70 | color: rgba(0, 0, 0, 0.4);
71 | font-weight: 400;
72 | background-color: #f5f5f5;
73 | border-color: #d9d9d9;
74 | text-align: center;
75 | padding: 0;
76 | }
77 |
78 | .button-downloading {
79 | padding: 0;
80 | border: none;
81 | visibility: visible;
82 | }
83 |
84 | .ant-progress-bg {
85 | border-radius: 3px;
86 | background-color: rgba(46, 114, 184, 0.9);
87 | }
88 |
89 | .ant-progress-inner {
90 | border-radius: 3px;
91 | background-color: #9b9b9b;
92 | }
93 |
94 | .ant-progress-text {
95 | margin: 0;
96 | width: auto;
97 | position: absolute;
98 | left: 50%;
99 | top: 50%;
100 | transform: translate(-50%, -50%);
101 | font-size: 10px;
102 | color: #fff;
103 | }
104 |
105 | .ant-progress-outer {
106 | margin: 0;
107 | padding: 0;
108 | }
109 |
110 | .ant-progress {
111 | line-height: 1;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/apps/home/containers/Support/index.tsx:
--------------------------------------------------------------------------------
1 | import { HeartOutlined } from '@ant-design/icons';
2 | import classnames from 'classnames';
3 | import { shell } from 'electron';
4 | import React, { Component } from 'react';
5 |
6 | import './index.scss';
7 |
8 | interface SupportState {
9 | expandButton: boolean;
10 | }
11 |
12 | enum QQGroup {
13 | JL,
14 | SG,
15 | }
16 |
17 | export class Support extends Component<{}, SupportState> {
18 | state = {
19 | expandButton: false,
20 | };
21 |
22 | getQQ(group: QQGroup): string {
23 | const KEY = 'kefu_qq' + group;
24 | let qq = localStorage.getItem(KEY);
25 | if (!qq) {
26 | let qqList: string[] | null;
27 | switch (group) {
28 | case QQGroup.JL:
29 | qqList = ['3014437365'];
30 | break;
31 | case QQGroup.SG:
32 | qqList = ['79157865', '1007008187'];
33 | break;
34 | default:
35 | qqList = null;
36 | }
37 | if (!qqList || qqList.length === 0) {
38 | return '';
39 | }
40 | const randomIndex = Math.floor(Math.random() * qqList.length);
41 | qq = qqList[randomIndex];
42 | localStorage.setItem(KEY, qq);
43 | }
44 | return qq;
45 | }
46 |
47 | handleOpenQQ = (group: QQGroup): void => {
48 | const qq = this.getQQ(group);
49 | if (!qq) {
50 | alert('客服不在线');
51 | return;
52 | }
53 | console.log(qq);
54 | shell.openExternal(
55 | `http://wpa.qq.com/msgrd?v=3&uin=${qq}&site=qq&menu=yes`,
56 | );
57 | };
58 |
59 | render() {
60 | const { expandButton } = this.state;
61 | const sgc = classnames('consult-button', { 'sg-button': expandButton });
62 | const jlc = classnames('consult-button', { 'jl-button': expandButton });
63 | return (
64 |
65 |
66 |
67 | -
68 |
69 |
70 |
71 |
72 |
73 |
74 |
this.setState({ expandButton: !expandButton })}
77 | style={{ opacity: expandButton ? 0.3 : 1 }}
78 | >
79 |
80 |
在线咨询
81 |
82 |
this.handleOpenQQ(QQGroup.SG)}>
83 | QQ 1
84 |
85 |
this.handleOpenQQ(QQGroup.JL)}>
86 | QQ 2
87 |
88 |
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/auth/permissions.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import PromiseComp from '../../skeleton/containers/PromiseComp';
4 |
5 | import { getAuthority } from './authority';
6 |
7 | export type IAuthorityType =
8 | | undefined
9 | | string
10 | | string[]
11 | | Promise
12 | | ((currentAuthority?: string[]) => IAuthorityType);
13 |
14 | /**
15 | * 通用权限检查方法
16 | * Common check permissions method
17 | * @param { 权限判定 | Permission judgment } authority
18 | * @param { 你的权限 | Your permission description } currentAuthority
19 | * @param { 通过的组件 | Passing components } target
20 | * @param { 未通过的组件 | no pass components } Exception
21 | */
22 | export const check = (
23 | authority: IAuthorityType,
24 | currentAuthority: string[],
25 | target: T,
26 | Exception: K,
27 | ): T | K | React.ReactNode => {
28 | // 没有判定权限.默认查看所有
29 | // Retirement authority, return target;
30 | if (!authority) {
31 | return target;
32 | }
33 |
34 | // 数组处理
35 | if (Array.isArray(authority)) {
36 | if (Array.isArray(currentAuthority)) {
37 | if (currentAuthority.some(item => authority.includes(item))) {
38 | return target;
39 | }
40 | } else if (authority.includes(currentAuthority)) {
41 | return target;
42 | }
43 | return Exception;
44 | }
45 |
46 | // string 处理
47 | if (typeof authority === 'string') {
48 | if (Array.isArray(currentAuthority)) {
49 | if (currentAuthority.some(item => authority === item)) {
50 | return target;
51 | }
52 | } else if (authority === currentAuthority) {
53 | return target;
54 | }
55 | return Exception;
56 | }
57 |
58 | // Promise 处理
59 | if (authority instanceof Promise) {
60 | return (
61 | ok={target} error={Exception} promise={authority} />
62 | );
63 | }
64 |
65 | // Function 处理
66 | if (typeof authority === 'function') {
67 | try {
68 | // 传入当前的权限判断是否满足要求
69 | const bool = authority(currentAuthority);
70 |
71 | // 函数执行后返回值是 Promise
72 | if (bool instanceof Promise) {
73 | return (
74 | ok={target} error={Exception} promise={bool} />
75 | );
76 | }
77 |
78 | if (bool) {
79 | return target;
80 | }
81 |
82 | return Exception;
83 | } catch (error) {
84 | throw error;
85 | }
86 | }
87 | throw new Error('unsupported parameters');
88 | };
89 |
90 | export function checkPermissions(
91 | authority: IAuthorityType,
92 | target: T,
93 | Exception: K,
94 | ): T | K | React.ReactNode {
95 | return check(authority, getAuthority(), target, Exception);
96 | }
97 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/schema/datetime.ts:
--------------------------------------------------------------------------------
1 | import dayjs, { Dayjs } from 'dayjs';
2 |
3 | export type Dateable = string | number | Dayjs | undefined;
4 |
5 | /** 格式化为标准时间形式 */
6 | export function formatDate(m: Dateable) {
7 | if (!m || !dayjs(m).isValid()) {
8 | return '-';
9 | }
10 | return dayjs(m).format('YYYY-MM-DD');
11 | }
12 |
13 | /** 格式化为标准时间形式 */
14 | export function formatDatetime(m: Dateable) {
15 | if (!m || !dayjs(m).isValid()) {
16 | return '-';
17 | }
18 | return dayjs(m).format('YYYY-MM-DD HH:mm:ss');
19 | }
20 |
21 | /** 简略些时间格式 */
22 | export function formatDatetimeAsShort(m: Dateable) {
23 | if (!m) {
24 | return '-';
25 | }
26 |
27 | return dayjs(m).format('MM/DD HH:mm');
28 | }
29 |
30 | export function formatTime(m: Dateable) {
31 | if (!m) {
32 | return '-';
33 | }
34 |
35 | return dayjs(m).format('HH:mm');
36 | }
37 |
38 | /**
39 | * 获取两个日期间的 Duration
40 | * @param m1 日期较小值
41 | * @param m2 日期较大值
42 | * @param len number 可选参数,保留时间描述位数
43 | * @param strip boolean 可选参数,剩余时间
44 | */
45 |
46 | export function formatDurationWithRange(
47 | m1: Dateable,
48 | m2: Dateable,
49 | options: { len?: number; strip?: boolean } = {},
50 | ) {
51 | if (!m1 || !m2) {
52 | return '-';
53 | }
54 |
55 | return formatDuration(dayjs(m2).valueOf() - dayjs(m1).valueOf(), options);
56 | }
57 |
58 | const MILLISECONDS_SECOND = 1000;
59 | const MILLISECONDS_MINUTE = MILLISECONDS_SECOND * 60;
60 | const MILLISECONDS_HOUR = MILLISECONDS_MINUTE * 60;
61 | const MILLISECONDS_DAY = MILLISECONDS_HOUR * 24;
62 |
63 | /**
64 | * 将某个时间差格式化展示为字符串
65 | */
66 | export function formatDuration(
67 | // 这里的 duration 指的是毫秒
68 | duration: number,
69 | { len = 10, strip = false }: { len?: number; strip?: boolean } = {},
70 | ) {
71 | if (!duration) {
72 | return '-';
73 | }
74 |
75 | let str = '';
76 |
77 | let usedBit = 0;
78 |
79 | const days = Math.floor(duration / MILLISECONDS_DAY);
80 | const hours = Math.floor((duration % MILLISECONDS_DAY) / MILLISECONDS_HOUR);
81 | const minutes = Math.floor(
82 | (duration % MILLISECONDS_HOUR) / MILLISECONDS_MINUTE,
83 | );
84 | const seconds = Math.floor(
85 | (duration % MILLISECONDS_MINUTE) / MILLISECONDS_SECOND,
86 | );
87 |
88 | if (days !== 0 && usedBit < len) {
89 | str = `${days}d`;
90 | usedBit++;
91 | }
92 |
93 | if (hours !== 0 && usedBit < len) {
94 | str = `${str} ${hours}h`;
95 | usedBit++;
96 | }
97 |
98 | if (minutes !== 0 && usedBit < len) {
99 | str = `${str} ${minutes}m`;
100 | usedBit++;
101 | }
102 |
103 | if (seconds !== 0 && usedBit < len) {
104 | str = `${str} ${seconds}s`;
105 | }
106 |
107 | return strip ? str.replace(' ', '') : str;
108 | }
109 |
--------------------------------------------------------------------------------
/boilerplates/fast-vite-nestjs-electron/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # ⚡Vite + Electron + Nestjs Template
6 |
7 | This template is used to build [vite](https://vitejs.dev/) + [electron](https://www.electronjs.org/) + [nestjs](https://nestjs.com/) projects. Build with [Doubleshot](https://github.com/Doubleshotjs/doubleshot), crazy fast!
8 |
9 | 🎉 [Doubleshot](https://github.com/Doubleshotjs/doubleshot) is a whole new set of tools to help you quickly build and start a node backend or electron main process.
10 |
11 | This is a vue version of the template, you can also use:
12 | - [React template](https://github.com/ArcherGu/vite-react-nestjs-electron)
13 | - [Svelte.js template](https://github.com/ArcherGu/vite-svelte-nestjs-electron)
14 |
15 | ## Introduce
16 | This is a template based on my repo: [fast-vite-electron](https://github.com/ArcherGu/fast-vite-electron). In the main process, I integrated nestjs. In the main process, you can build your code just as you would write a nestjs backend. Desktop clients built from this template can quickly split the electron when you need to switch to B/S.
17 |
18 | ## Features
19 |
20 | - 🔨 [vite-plugin-doubleshot](https://github.com/archergu/doubleshot/tree/main/packages/plugin-vite#readme) to run/build electron main process or node backend.
21 |
22 |
23 | - 🛻 An electron ipc transport for [nestjs](https://nestjs.com/) that provides simple ipc communication.
24 |
25 |
26 | - 🪟 An electron module for [nestjs](https://nestjs.com/) to launch electron windows.
27 |
28 |
29 | - ⏩ Quick start and build, powered by [tsup](https://tsup.egoist.sh/) and [electron-builder](https://www.electron.build/) integrated in [@doubleshot/builder](https://github.com/Doubleshotjs/doubleshot/tree/main/packages/builder)
30 |
31 | ## How to use
32 |
33 | - Click the [Use this template](https://github.com/ArcherGu/fast-vite-electron/generate) button (you must be logged in) or just clone this repo.
34 | - In the project folder:
35 | ```bash
36 | # install dependencies
37 | yarn # npm install
38 |
39 | # run in developer mode
40 | yarn dev # npm run dev
41 |
42 | # build
43 | yarn build # npm run build
44 | ```
45 |
46 | ## Note for PNPM
47 |
48 | In order to use with `pnpm`, you'll need to adjust your `.npmrc` to use any one the following approaches in order for your dependencies to be bundled correctly (ref: [#6389](https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422)):
49 | ```
50 | node-linker=hoisted
51 | ```
52 | ```
53 | public-hoist-pattern=*
54 | ```
55 | ```
56 | shamefully-hoist=true
57 | ```
58 |
59 | ## Relative
60 |
61 | My blog post:
62 |
63 | - [极速 DX Vite + Electron + esbuild](https://archergu.me/posts/vite-electron-esbuild)
64 | - [用装饰器给 Electron 提供一个基础 API 框架](https://archergu.me/posts/electron-decorators)
65 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/containers/PromiseComp/index.tsx:
--------------------------------------------------------------------------------
1 | import { Spin } from 'antd';
2 | import isEqual from 'lodash/isEqual';
3 | import * as React from 'react';
4 |
5 | import { isComponentClass } from '../../types/comp';
6 |
7 | interface PromiseRenderProps {
8 | ok: T;
9 | error: K;
10 | promise: Promise;
11 | }
12 |
13 | interface PromiseRenderState {
14 | component: React.ComponentClass | React.FunctionComponent;
15 | }
16 |
17 | export default class PromiseComp extends React.Component<
18 | PromiseRenderProps,
19 | PromiseRenderState
20 | > {
21 | state: PromiseRenderState = {
22 | component: () => null,
23 | };
24 |
25 | componentDidMount() {
26 | this.setRenderComponent(this.props);
27 | }
28 |
29 | shouldComponentUpdate = (
30 | nextProps: PromiseRenderProps,
31 | nextState: PromiseRenderState,
32 | ) => {
33 | const { component } = this.state;
34 | if (!isEqual(nextProps, this.props)) {
35 | this.setRenderComponent(nextProps);
36 | }
37 | if (nextState.component !== component) return true;
38 | return false;
39 | };
40 |
41 | // set render Component : ok or error
42 | setRenderComponent(props: PromiseRenderProps) {
43 | const ok = this.checkIsInstantiation(props.ok);
44 | const error = this.checkIsInstantiation(props.error);
45 | props.promise
46 | .then(() => {
47 | this.setState({
48 | component: ok,
49 | });
50 | return true;
51 | })
52 | .catch(() => {
53 | this.setState({
54 | component: error,
55 | });
56 | });
57 | }
58 |
59 | // Determine whether the incoming component has been instantiated
60 | // AuthorizedRoute is already instantiated
61 | // Authorized render is already instantiated, children is no instantiated
62 | // Secured is not instantiated
63 | checkIsInstantiation = (
64 | target: React.ReactNode | React.ComponentClass,
65 | ): React.FunctionComponent => {
66 | if (isComponentClass(target)) {
67 | const Target = target as React.ComponentClass;
68 | return (props: any) => ;
69 | }
70 | if (React.isValidElement(target)) {
71 | return (props: any) => React.cloneElement(target, props);
72 | }
73 | return () => target as React.ReactNode & null;
74 | };
75 |
76 | render() {
77 | const { component: Component } = this.state;
78 | const { ok, error, promise, ...rest } = this.props;
79 |
80 | return Component ? (
81 |
82 | ) : (
83 |
92 |
93 |
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/typings/shim.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.less' {
2 | const styles: Record;
3 | export = styles;
4 | }
5 |
6 | declare module '*.svg' {
7 | import React from 'react';
8 | const Component: React.ComponentType;
9 | export = styles;
10 | }
11 |
12 | declare module '@antv/data-set';
13 |
14 | declare module 'html-webpack-harddisk-plugin' {
15 | import { Plugin } from 'webpack';
16 |
17 | class HtmlWebpackHarddiskPlugin extends Plugin {}
18 |
19 | export = HtmlWebpackHarddiskPlugin;
20 | }
21 |
22 | declare module 'html-webpack-inline-source-plugin' {
23 | import { Plugin } from 'webpack';
24 |
25 | class HtmlWebpackInlineSourcePlugin extends Plugin {}
26 |
27 | export = HtmlWebpackInlineSourcePlugin;
28 | }
29 |
30 | declare module 'lazy-compile-webpack-plugin' {
31 | import { Plugin } from 'webpack';
32 |
33 | namespace LazyCompileWebpackPlugin {
34 | interface Options {
35 | refreshAfterCompile?: boolean;
36 | }
37 | }
38 |
39 | class LazyCompileWebpackPlugin extends Plugin {
40 | constructor(options?: LazyCompileWebpackPlugin.Options);
41 | }
42 |
43 | export = LazyCompileWebpackPlugin;
44 | }
45 |
46 | declare module 'webpack-theme-color-replacer' {
47 | import { Plugin } from 'webpack';
48 |
49 | namespace ThemeColorReplacer {
50 | interface Options {
51 | fileName: string;
52 | matchColors: string[];
53 | changeSelector(selector: string): string;
54 | }
55 | }
56 |
57 | class ThemeColorReplacer extends Plugin {
58 | static varyColor: {
59 | lighten: (color: string, radio: number) => string;
60 | darken: (color: string, radio: number) => string;
61 | };
62 |
63 | constructor(options: ThemeColorReplacer.Options);
64 | }
65 |
66 | export = ThemeColorReplacer;
67 | }
68 |
69 | declare module 'webpack-theme-color-replacer/client' {
70 | namespace WebpackThemColorReplacerClient {
71 | interface ChangeColorOptions {
72 | newColors: string[];
73 | changeUrl(cssUrl: string): string;
74 | }
75 | }
76 |
77 | interface WebpackThemColorReplacerClient {
78 | varyColor: {
79 | lighten: (color: string, radio: number) => string;
80 | darken: (color: string, radio: number) => string;
81 | };
82 | changer: {
83 | changeColor(
84 | options: WebpackThemColorReplacerClient.ChangeColorOptions,
85 | PromiseConstructor: typeof Promise,
86 | ): Promise;
87 | };
88 | }
89 |
90 | const client: WebpackThemColorReplacerClient;
91 |
92 | export = client;
93 | }
94 |
95 | declare module 'dayjs/locale/zh-cn';
96 | declare module 'dayjs/locale/en';
97 | declare module 'rc-tween-one/lib/plugin/ChildrenPlugin';
98 | declare module 'dayjs-ext/plugin/timeZone';
99 | declare module 'react-hls-player';
100 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-core/src/models/InstallableApp.ts:
--------------------------------------------------------------------------------
1 | import electron, { ipcRenderer } from 'electron';
2 | import * as S from 'ueact-utils';
3 |
4 | import { runFunc, searchLocationFunc } from '../funcs';
5 |
6 | /** 应用定义 */
7 | export class InstallableApp extends S.Base {
8 | // 是否隐藏
9 | hidden: boolean;
10 |
11 | // 全名
12 | name: string;
13 |
14 | // 默认快捷方式名
15 | lnk: string;
16 |
17 | // id
18 | key: string;
19 |
20 | // 版本
21 | version: string;
22 |
23 | // 简称
24 | label: string;
25 |
26 | // 下载链接
27 | url: string;
28 |
29 | // 图标
30 | icon: string;
31 |
32 | // exe 文件名称
33 | exe: string;
34 |
35 | // 安装包 md5,用于验证版本
36 | md5: string;
37 |
38 | order: number;
39 |
40 | regeditLocation: string;
41 |
42 | timer: NodeJS.Timer;
43 |
44 | // 本机路径
45 | location = '';
46 |
47 | downloadProgess = 0;
48 |
49 | downloading = false;
50 |
51 | get isInstalled() {
52 | return !!this.location;
53 | }
54 |
55 | setProgress = (_: any, percent: number) => {
56 | if (percent >= 100 || percent < 0) {
57 | this.downloading = false;
58 | this.downloadProgess = 0;
59 | } else {
60 | this.downloading = true;
61 | this.downloadProgess = percent;
62 | }
63 | };
64 |
65 | async getLocation(): Promise {
66 | const searchLocation: searchLocationFunc = electron.remote.getGlobal(
67 | 'searchLocation',
68 | );
69 | this.location = await searchLocation(this.regeditLocation, this.exe);
70 | }
71 |
72 | run(): void {
73 | if (!this.location) {
74 | return;
75 | }
76 | const run: runFunc = electron.remote.getGlobal('run');
77 | run(this.location);
78 | }
79 |
80 | install() {
81 | if (!this.url) {
82 | alert('抱歉, 产品即将上线.');
83 | return;
84 | }
85 | const cachePath = localStorage.getItem(`installer-${this.key}`);
86 | // this.downloading = true;
87 | ipcRenderer.on(`download-progress-${this.key}`, this.setProgress);
88 | ipcRenderer.once(`download-complete-${this.key}`, () => {
89 | ipcRenderer.removeListener(
90 | `download-progress-${this.key}`,
91 | this.setProgress,
92 | );
93 | this.downloading = false;
94 | });
95 | ipcRenderer.once(`get-location-${this.key}`, (_: any, location: string) => {
96 | this.location = location;
97 | });
98 | ipcRenderer.send('begin-download', this, cachePath);
99 | }
100 |
101 | constructor(data: InstallableApp) {
102 | super(data);
103 |
104 | if (!this.hidden) {
105 | this.getLocation();
106 |
107 | this.timer = setInterval(() => {
108 | this.getLocation();
109 | }, 1000 * 5);
110 |
111 | ipcRenderer.on(`get-installer-${this.key}`, (_: any, path: string) => {
112 | localStorage.setItem(`installer-${this.key}`, path);
113 | });
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/boilerplates/ts-webpack-react-electron/packages/rte-host-app/src/skeleton/env/sw-notify.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'antd';
2 | import { Button, message, notification } from 'antd';
3 |
4 | import { formatMessage } from '@/i18n';
5 |
6 | declare global {
7 | interface Window {
8 | g_config: {
9 | pwa: false;
10 | };
11 | }
12 | }
13 |
14 | // if pwa is true
15 | if (window.g_config && window.g_config.pwa) {
16 | // Notify user if offline now
17 | window.addEventListener('sw.offline', () => {
18 | message.warning(formatMessage({ id: 'app.pwa.offline' }));
19 | });
20 |
21 | // Pop up a prompt on the page asking the user if they want to use the latest version
22 | window.addEventListener('sw.updated', (event: Event) => {
23 | const e = event as CustomEvent;
24 | const reloadSW = async () => {
25 | // Check if there is sw whose state is waiting in ServiceWorkerRegistration
26 | // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
27 | const worker = e.detail && e.detail.waiting;
28 | if (!worker) {
29 | return true;
30 | }
31 | // Send skip-waiting event to waiting SW with MessageChannel
32 | await new Promise((resolve, reject) => {
33 | const channel = new MessageChannel();
34 | channel.port1.onmessage = msgEvent => {
35 | if (msgEvent.data.error) {
36 | reject(msgEvent.data.error);
37 | } else {
38 | resolve(msgEvent.data);
39 | }
40 | };
41 | worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
42 | });
43 | // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
44 | window.location.reload(true);
45 | return true;
46 | };
47 | const key = `open${Date.now()}`;
48 | const btn = (
49 |
58 | );
59 | notification.open({
60 | message: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
61 | description: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
62 | btn,
63 | key,
64 | onClose: async () => {},
65 | });
66 | });
67 | } else if ('serviceWorker' in navigator) {
68 | // unregister service worker
69 | const { serviceWorker } = navigator;
70 | if (serviceWorker.getRegistrations) {
71 | serviceWorker.getRegistrations().then(sws => {
72 | sws.forEach(sw => {
73 | sw.unregister();
74 | });
75 | });
76 | }
77 | serviceWorker.getRegistration().then(sw => {
78 | if (sw) sw.unregister();
79 | });
80 |
81 | // remove all caches
82 | if (window.caches && window.caches.keys) {
83 | caches.keys().then(keys => {
84 | keys.forEach(key => {
85 | caches.delete(key);
86 | });
87 | });
88 | }
89 | }
90 |
--------------------------------------------------------------------------------