(target: T) => T & IWrappedComponent;
44 | }
45 |
--------------------------------------------------------------------------------
/src/pages/goods/goodsList/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import './index.scss';
4 | import { IRootStore, IRootAction } from '../../../typings/index';
5 |
6 | type injectorReturnType = ReturnType;
7 |
8 | interface Props extends Partial {
9 | prefixCls?: string;
10 |
11 | [k: string]: any;
12 | }
13 |
14 | function injector({
15 | rootStore,
16 | rootAction,
17 | }: {
18 | rootStore: IRootStore;
19 | rootAction: IRootAction;
20 | }) {
21 | return {
22 | pageStore: rootStore.goods,
23 | pageAction: rootAction.goods,
24 | };
25 | }
26 |
27 | @inject(injector)
28 | @observer
29 | export default class GoodsList extends React.Component {
30 | static defaultProps = {
31 | prefixCls: 'page-goods__goods-list',
32 | };
33 |
34 | constructor(props) {
35 | super(props);
36 | }
37 |
38 | componentDidMount() {}
39 |
40 | onSelectGoods = goods => {
41 | let { pageAction, pageStore } = this.props;
42 |
43 | // pageStore.goodsDetailStore.setLoading(true); //如果在组件内直接调用store的方法,会给出无效的提示
44 | pageAction.goodsListAction.setCurrentGoods(goods);
45 | };
46 |
47 | render() {
48 | let { prefixCls, pageStore } = this.props;
49 | return (
50 |
51 |
52 | {pageStore.goodsListStore.goodsList.map(goods => {
53 | return (
54 | -
64 | {goods.name}
65 |
66 | );
67 | })}
68 |
69 |
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/icomoon/variables.scss:
--------------------------------------------------------------------------------
1 | $icomoon-font-path: 'fonts' !default;
2 |
3 | $icon-error: '\e900';
4 | $icon-link2: '\e901';
5 | $icon-add-edit: '\e902';
6 | $icon-photo-mask: '\e903';
7 | $icon-skill: '\e904';
8 | $icon-phone: '\e905';
9 | $icon-people: '\e906';
10 | $icon-email: '\e907';
11 | $icon-warn: '\e908';
12 | $icon-city: '\e909';
13 | $icon-responsibility: '\e90a';
14 | $icon-card: '\e90b';
15 | $icon-edu: '\e90c';
16 | $icon-competition: '\e90d';
17 | $icon-paper: '\e90e';
18 | $icon-interview: '\e90f';
19 | $icon-uniE910: '\e910';
20 | $icon-uniE911: '\e911';
21 | $icon-uniE912: '\e912';
22 | $icon-intention: '\e913';
23 | $icon-requirement: '\e914';
24 | $icon-honor: '\e915';
25 | $icon-uniE916: '\e916';
26 | $icon-uniE917: '\e917';
27 | $icon-dot: '\e918';
28 | $icon-internship: '\e91a';
29 | $icon-phone2: '\e91b';
30 | $icon-add: '\e91c';
31 | $icon-apply-reord: '\e91d';
32 | $icon-apply-reord2: '\e91e';
33 | $icon-position: '\e91f';
34 | $icon-project: '\e920';
35 | $icon-email2: '\e921';
36 | $icon-language: '\e922';
37 | $icon-certificate: '\e923';
38 | $icon-work-show: '\e924';
39 | $icon-Shape-Copy-3: '\e925';
40 | $icon-finger-post: '\e926';
41 | $icon-file-signet: '\e927';
42 | $icon-paper-plane: '\e928';
43 | $icon-shield: '\e929';
44 | $icon-left: '\e92a';
45 | $icon-search: '\e92b';
46 | $icon-link: '\e92c';
47 | $icon-gender: '\e92d';
48 | $icon-download: '\e92e';
49 | $icon-return: '\e92f';
50 | $icon-addJob: '\e930';
51 | $icon-attachment: '\e931';
52 | $icon-Interview: '\e932';
53 | $icon-log: '\e933';
54 | $icon-remarks: '\e934';
55 | $icon-tel-off: '\e935';
56 | $icon-tel-on: '\e936';
57 | $icon-edit: '\e937';
58 | $icon-pencil: '\e938';
59 | $icon-arrow-down: '\e939';
60 | $icon-description: '\e93a';
61 | $icon-lock: '\e93b';
62 | $icon-Combined-Shape-Copy-2: '\e93c';
63 | $icon-right: '\e93d';
64 | $icon-location-fill: '\e93e';
65 | $icon-education: '\e93f';
66 | $icon-workYear: '\e940';
67 | $icon-close: '\e941';
68 | $icon-record: '\e942';
69 | $icon-close-o: '\e943';
70 | $icon-gift: '\e944';
71 | $icon-mobile: '\e958';
72 | $icon-notification: '\ea08';
73 | $icon-checkmark: '\ea10';
74 | $icon-wechat: '\e919';
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myproject",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --history-api-fallback",
8 | "server": "node server.js",
9 | "precommit": "lint-staged",
10 | "format": "prettier --single-quote --trailing-comma es5 --write \"src/**/*.{js,jsx,ts,tsx,scss,css}\"",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "lint-staged": {
14 | "src/**/*": [
15 | "npm run format",
16 | "git add"
17 | ]
18 | },
19 | "author": "",
20 | "license": "ISC",
21 | "basePath": "./src",
22 | "devDependencies": {
23 | "node-sass": "^4.9.4",
24 | "postcss-loader": "^3.0.0",
25 | "sass-loader": "^7.0.3",
26 | "chalk": "^2.4.1",
27 | "commander": "^2.19.0",
28 | "fs-extra": "^7.0.1",
29 | "invariant": "^2.2.4",
30 | "@types/node": "^10.12.0",
31 | "@types/react": "^16.4.18",
32 | "@types/react-dom": "^16.0.9",
33 | "@types/webpack-env": "^1.13.6",
34 | "babel-core": "^6.26.3",
35 | "babel-loader": "^7.1.5",
36 | "babel-plugin-transform-runtime": "^6.23.0",
37 | "babel-preset-env": "^1.7.0",
38 | "babel-preset-react": "^6.24.1",
39 | "babel-preset-stage-0": "^6.24.1",
40 | "clean-webpack-plugin": "^0.1.19",
41 | "css-loader": "^1.0.0",
42 | "eslint": "^5.8.0",
43 | "eslint-loader": "^2.1.1",
44 | "autoprefixer": "^8.6.2",
45 | "eslint-plugin-typescript": "^0.13.0",
46 | "express": "^4.16.4",
47 | "file-loader": "^2.0.0",
48 | "fork-ts-checker-webpack-plugin": "^0.4.14",
49 | "html-webpack-plugin": "^3.2.0",
50 | "husky": "^1.1.3",
51 | "lint-staged": "^8.0.4",
52 | "prettier": "1.14.3",
53 | "react-hot-loader": "^4.3.11",
54 | "style-loader": "^0.23.1",
55 | "ts-loader": "^5.2.2",
56 | "typescript": "^3.1.6",
57 | "typescript-eslint-parser": "^20.1.1",
58 | "webpack": "^4.23.1",
59 | "webpack-bundle-analyzer": "^3.0.3",
60 | "webpack-cli": "^3.1.2",
61 | "webpack-dev-middleware": "^3.4.0",
62 | "webpack-dev-server": ">=3.1.11",
63 | "webpack-merge": "^4.1.4",
64 | "webpack-visualizer-plugin": "^0.1.11"
65 | },
66 | "dependencies": {
67 | "lodash": "^4.17.11",
68 | "proptypes": "^1.1.0",
69 | "react": "^16.7.0",
70 | "react-dom": "^16.7.0",
71 | "mobx": "^5.8.0",
72 | "mobx-react": "^5.4.3",
73 | "react-router": "^3.2.0",
74 | "zone.js": "^0.8.28"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mobx-example
2 | the best practice for mobx
3 |
4 | # 上手指南
5 |
6 | ## 概览
7 |
8 | 技术栈
9 |
10 | - TypeScript (3.x)
11 | - react 16.x
12 | - mobx
13 |
14 | 快速运行
15 |
16 | ```bash
17 | $ node -v
18 | v8.1.2
19 | $ npm install
20 | $ npm start
21 | ```
22 |
23 | 打开浏览器访问 [http://localhost:8080](http://localhost:8080/)
24 |
25 | ### 目录结构
26 |
27 | 项目根目录为 `./src`(可在`package.json`内配置) 页面目录为 `pages`, 跨页面通用业务组件目录为 `components`。两个目录仅仅业务含义不同,但`store`和`action`的组织方式一致。
28 |
29 |
30 | 以 pages目录为例(components下的组件同理)
31 | ```text
32 | |--pages
33 | |--aPage
34 | |--index.tsx
35 | |--index.scss
36 | |--aComponet
37 | |--index.tsx
38 | |--index.scss
39 | |--actions
40 | |--nameaAction.ts
41 | |--namebAction.ts
42 | |--stores
43 | |--nameaStore.ts
44 | |--namebStore.ts
45 | |--components
46 | 同pages结构
47 | ```
48 |
49 | store或者action的所在页面名称和文件名称暗示了组件内获取对应实例的路径
50 | 如 在组件文件`index.tsx`里,
51 | ```javascript
52 | @inject(({rootStore, rootAction}) => {
53 | return {
54 | storeA: rootStore.aPage.nameaStore,
55 | actionA: rootAction.aPage.nameaAction
56 | }
57 | })
58 | ```
59 |
60 | 实现上述功能的核心代码在`mobx`目录内。
61 | * 1、mStore 和mAction装饰器收集需要注册的store和action的class
62 | * 2、provider根据收集到的store和action按照页面划分结构,并注入到根组件中。
63 | * 3、各级子组件通过 mobx-react提供的inject来获取需要的store和action
64 | * 4、action和store按需实例化,action实例化时会传入当前页面的store和action,也可以通过第3、4个参数拿到rootStore 和rootAction
65 | * 借助`zone.js`确保`store`的方法调用只能限制在`action`内
66 |
67 | ## 开发
68 |
69 |
70 | ### 脚手架
71 |
72 | #### 添加前端页面或者组件
73 |
74 | ```sh
75 | node tools/add-page.js [page-path]
76 | -m 创建mobx相关文件 xxStore,xxAction
77 | -c 创建通用组件,所有文件会生成到components目录下,其他没区别
78 | 更多命令通过 node tools/add-page.js -h 查看
79 | # 如
80 | node tools/add-page.js home -m
81 | node tools/add-page.js home/aComponent -m
82 | ```
83 |
84 | #### 删除前端页面目录或者组件目录
85 | ```sh
86 | node tools/rm-page.js [page-path]
87 | # 如
88 | node tools/rm-page.js home
89 | node tools/rm-page.js home/aComponent
90 | ```
91 | 以上两个命令除了生成或者删除对应文件,还会更新`mobxDependence.js`文件对所有store和action文件的引用.
92 | 如果是手动添加删除,需要手动去引入或删除对应store和action文件的引用。
93 |
94 |
95 | #### 类型声明
96 |
97 | 在用脚手架创建或删除组件时,均会更新`typings/index.d.ts`,以便获得更好的代码提示。
98 |
99 | #### ts transformer plugin
100 | 所在目录 `./transformer`
101 |
102 | 可以发现组件里`store`和`action`的装饰器并未显式的指定本身的访问路径(当然也可以手动指定),这正是这个`ts`插件所发挥的作用,通过`store(action)`所在的目录和文件名暗示`store(action)`在`rootStore`(`rootAction`)的访问路径。
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/icomoon/demo-files/demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | font-family: sans-serif;
5 | font-size: 1em;
6 | line-height: 1.5;
7 | color: #555;
8 | background: #fff;
9 | }
10 | h1 {
11 | font-size: 1.5em;
12 | font-weight: normal;
13 | }
14 | small {
15 | font-size: 0.66666667em;
16 | }
17 | a {
18 | color: #e74c3c;
19 | text-decoration: none;
20 | }
21 | a:hover,
22 | a:focus {
23 | box-shadow: 0 1px #e74c3c;
24 | }
25 | .bshadow0,
26 | input {
27 | box-shadow: inset 0 -2px #e7e7e7;
28 | }
29 | input:hover {
30 | box-shadow: inset 0 -2px #ccc;
31 | }
32 | input,
33 | fieldset {
34 | font-family: sans-serif;
35 | font-size: 1em;
36 | margin: 0;
37 | padding: 0;
38 | border: 0;
39 | }
40 | input {
41 | color: inherit;
42 | line-height: 1.5;
43 | height: 1.5em;
44 | padding: 0.25em 0;
45 | }
46 | input:focus {
47 | outline: none;
48 | box-shadow: inset 0 -2px #449fdb;
49 | }
50 | .glyph {
51 | font-size: 16px;
52 | width: 15em;
53 | padding-bottom: 1em;
54 | margin-right: 4em;
55 | margin-bottom: 1em;
56 | float: left;
57 | overflow: hidden;
58 | }
59 | .liga {
60 | width: 80%;
61 | width: calc(100% - 2.5em);
62 | }
63 | .talign-right {
64 | text-align: right;
65 | }
66 | .talign-center {
67 | text-align: center;
68 | }
69 | .bgc1 {
70 | background: #f1f1f1;
71 | }
72 | .fgc1 {
73 | color: #999;
74 | }
75 | .fgc0 {
76 | color: #000;
77 | }
78 | p {
79 | margin-top: 1em;
80 | margin-bottom: 1em;
81 | }
82 | .mvm {
83 | margin-top: 0.75em;
84 | margin-bottom: 0.75em;
85 | }
86 | .mtn {
87 | margin-top: 0;
88 | }
89 | .mtl,
90 | .mal {
91 | margin-top: 1.5em;
92 | }
93 | .mbl,
94 | .mal {
95 | margin-bottom: 1.5em;
96 | }
97 | .mal,
98 | .mhl {
99 | margin-left: 1.5em;
100 | margin-right: 1.5em;
101 | }
102 | .mhmm {
103 | margin-left: 1em;
104 | margin-right: 1em;
105 | }
106 | .mls {
107 | margin-left: 0.25em;
108 | }
109 | .ptl {
110 | padding-top: 1.5em;
111 | }
112 | .pbs,
113 | .pvs {
114 | padding-bottom: 0.25em;
115 | }
116 | .pvs,
117 | .pts {
118 | padding-top: 0.25em;
119 | }
120 | .unit {
121 | float: left;
122 | }
123 | .unitRight {
124 | float: right;
125 | }
126 | .size1of2 {
127 | width: 50%;
128 | }
129 | .size1of1 {
130 | width: 100%;
131 | }
132 | .clearfix:before,
133 | .clearfix:after {
134 | content: ' ';
135 | display: table;
136 | }
137 | .clearfix:after {
138 | clear: both;
139 | }
140 | .hidden-true {
141 | display: none;
142 | }
143 | .textbox0 {
144 | width: 3em;
145 | background: #f1f1f1;
146 | padding: 0.25em 0.5em;
147 | line-height: 1.5;
148 | height: 1.5em;
149 | }
150 | #testDrive {
151 | display: block;
152 | padding-top: 24px;
153 | line-height: 1.5;
154 | }
155 | .fs0 {
156 | font-size: 16px;
157 | }
158 | .fs1 {
159 | font-size: 32px;
160 | }
161 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const HtmlWebpackPlugin = require('html-webpack-plugin')
3 | const CleanWebpackPlugin = require('clean-webpack-plugin')
4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
5 | .BundleAnalyzerPlugin
6 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
7 | const webpack = require('webpack')
8 | let createMobxTransformer = require('./build/transformer/createMobxTransformer')
9 |
10 | module.exports = {
11 | mode: 'development',
12 | entry: {
13 | app: './src/index.tsx'
14 | },
15 | devtool: 'cheap-module-eval-source-map',
16 | mode:"development",
17 | output: {
18 | filename: '[name].bundle.js',
19 | chunkFilename: '[name].chunk.js',
20 | path: path.resolve(__dirname, 'dist'),
21 | publicPath: '/',
22 | pathinfo: false
23 | },
24 | optimization: {
25 | runtimeChunk: 'single',
26 | splitChunks: {
27 | cacheGroups: {
28 | vendor: {
29 | test: /[\\/]node_modules[\\/]/,
30 | name: 'vendors',
31 | chunks: 'all'
32 | }
33 | }
34 | }
35 | },
36 | plugins: [
37 | new CleanWebpackPlugin(['dist']),
38 | new HtmlWebpackPlugin({
39 | title: 'Output Management',
40 | template: './index.html'
41 | }),
42 | // new BundleAnalyzerPlugin(),
43 | new webpack.HotModuleReplacementPlugin(),
44 | new webpack.DefinePlugin({
45 | 'process.env.NODE_ENV': JSON.stringify('development')
46 | }),
47 | new webpack.HashedModuleIdsPlugin(),
48 | new ForkTsCheckerWebpackPlugin()
49 | ],
50 | resolve: {
51 | extensions: ['.tsx', '.ts', '.js'],
52 | },
53 | module: {
54 | rules: [
55 | {
56 | test: /\.tsx?$/,
57 | use: [
58 | {
59 | loader: 'ts-loader',
60 | options: {
61 | transpileOnly: true,
62 | experimentalWatchApi: true,
63 | getCustomTransformers: () => {
64 | return ({
65 | before: [createMobxTransformer()]
66 | })
67 | }
68 | }
69 | }
70 | ],
71 | exclude: /node_modules/
72 | },
73 | { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ },
74 | {
75 | test: /\.(sa|sc|c)ss$/,
76 | use: [
77 | "style-loader",
78 | "css-loader",
79 | {
80 | loader:"postcss-loader",
81 | options: {
82 | plugins: [require('autoprefixer')('last 100 versions')]
83 | }
84 | },
85 | "sass-loader"
86 | ],
87 | },
88 | {
89 | test: /\.css$/,
90 | use: ['style-loader', 'css-loader']
91 | },
92 | {
93 | test: /\.(png|svg|jpg|gif)$/,
94 | use: ['file-loader']
95 | },
96 | {
97 | test: /\.(woff|woff2|eot|ttf|otf)$/,
98 | use: ['file-loader']
99 | }
100 | ]
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/mobx/zone.js:
--------------------------------------------------------------------------------
1 | import 'zone.js';
2 | import {
3 | asyncManagerSymbol,
4 | storeWrappedSymbol,
5 | actionWrappedSymbol,
6 | } from './meta';
7 | import { defineHiddenProperty } from './utils';
8 |
9 | export default class AsyncManager {
10 | static singleton = null;
11 |
12 | static getInstance() {
13 | if (!this.singleton) {
14 | new this();
15 | }
16 |
17 | return this.singleton;
18 | }
19 |
20 | zone;
21 |
22 | constructor() {
23 | if (AsyncManager.singleton !== null) return AsyncManager.singleton;
24 |
25 | this.init();
26 | AsyncManager.singleton = this;
27 | }
28 |
29 | init() {
30 | this.zone = Zone.root.fork({
31 | name: asyncManagerSymbol,
32 | });
33 | }
34 |
35 | wrapStore(storeInstance) {
36 | if (process.env.NODE_ENV !== 'production') {
37 | if (this.storeHasWrapped(storeInstance)) {
38 | return;
39 | }
40 |
41 | let propKeys = this.getFunctionKeys(storeInstance);
42 | propKeys.forEach(prop => {
43 | if (typeof storeInstance[prop] === 'function') {
44 | storeInstance[prop] = this.wrapStoreFunc(
45 | storeInstance[prop],
46 | storeInstance,
47 | prop
48 | );
49 | }
50 | });
51 | defineHiddenProperty(storeInstance, storeWrappedSymbol, true);
52 | }
53 | }
54 |
55 | wrapStoreFunc = (fn, context, prop) => {
56 | return (...args) => {
57 | if (Zone.current !== this.zone) {
58 | console.error(
59 | `store ${context.constructor &&
60 | context.constructor.name} 的方法${prop}必须在action里调用`
61 | );
62 | return;
63 | }
64 | return fn.apply(context, args);
65 | };
66 | };
67 |
68 | storeHasWrapped(storeInstance) {
69 | return storeInstance && storeInstance[storeWrappedSymbol] === true;
70 | }
71 |
72 | actionHasWrapped(actionInstance) {
73 | return actionInstance && actionInstance[actionWrappedSymbol] === true;
74 | }
75 |
76 | wrapAction(actionInstance) {
77 | if (process.env.NODE_ENV !== 'production') {
78 | if (this.actionHasWrapped(actionInstance)) {
79 | return;
80 | }
81 | let propKeys = this.getFunctionKeys(actionInstance);
82 | propKeys.forEach(prop => {
83 | if (typeof actionInstance[prop] === 'function') {
84 | actionInstance[prop] = this.wrapActionFunc(actionInstance, prop);
85 | }
86 | });
87 |
88 | defineHiddenProperty(actionInstance, actionWrappedSymbol, true);
89 | }
90 | }
91 |
92 | wrapActionFunc(action, prop) {
93 | let origin = action[prop];
94 | return (...args) => {
95 | return this.zone.run(origin, action, args);
96 | };
97 | }
98 |
99 | getFunctionKeys(obj) {
100 | //获取实例和原型上的属性
101 | let objProto = Object.getPrototypeOf(obj) || obj.__proto__;
102 | let propKeys = [
103 | ...Object.keys(obj),
104 | ...Object.getOwnPropertyNames(objProto),
105 | ];
106 | propKeys = Array.from(new Set(propKeys));
107 | propKeys = propKeys.filter(key => key !== 'constructor');
108 | return propKeys;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/tools/rm-page.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | let program = require("commander");
4 | let FS = require('fs-extra');
5 | let Path = require('path');
6 | let ChildProcess = require('child_process');
7 | let Chalk = require('chalk');
8 |
9 | let packageJson = FS.readJSONSync(Path.join(process.cwd(),'package.json'),{encoding:"utf-8"});
10 | let basePath =packageJson.basePath;
11 |
12 | if(!basePath){
13 | console.log(`you should set the key ${Chalk.red("basePath")} in your ${Chalk.green("package.json")} file. For example, ${Chalk.red('"basePath":"./src"')}`)
14 | return;
15 | }
16 |
17 | program
18 | .version("0.0.1")
19 | .option('[] ', 'the path of page or component to remove')
20 | .option('-c, --component', `will remove files from ${Chalk.greenBright(Path.join(basePath, 'components'))} directory`)
21 |
22 | program.on('--help', function () {
23 | console.log(" This is a quick tool to remove a page or component. \n It will remove index.tsx,index.scss file and stores and actions \n");
24 | console.log('');
25 |
26 | console.log(' Examples:');
27 |
28 | console.log('');
29 |
30 | console.log(` $ node tools/rm-page.js offerList ${Chalk.grey('// remove a page')}`);
31 |
32 | console.log(` $ node tools/rm-page.js offerList -c ${Chalk.grey('// remove a component for common')}`);
33 |
34 | console.log(` $ node tools/rm-page.js offerList/tableList ${Chalk.grey('// remove a component inner page offerList')}`);
35 |
36 | console.log('');
37 | });
38 |
39 | program.parse(process.argv);
40 |
41 | if (program.args.length < 1)
42 | throw new Error("missing param");
43 |
44 | let pageOrComPath = program.args[0];
45 |
46 |
47 | //跨页面级通用组件独自一个目录
48 | if (program.component) {
49 | basePath = Path.join(basePath, 'components')
50 | } else {
51 | basePath = Path.join(basePath, 'pages')
52 | }
53 |
54 |
55 | let splitPath = pageOrComPath.split('/').filter(Boolean);
56 | let rootPageName = splitPath[0]; //页面目录
57 | let fileDirectoryName = splitPath[splitPath.length - 1]; //页面内最深层目录
58 |
59 | let pagePath = Path.join(process.cwd(), basePath, pageOrComPath)
60 | let rootPagePath = Path.join(process.cwd(), basePath, rootPageName)
61 |
62 | let storeName = rootPagePath + `/stores/${fisrtToLowercase(fileDirectoryName)}Store.ts`
63 | let actionName = rootPagePath + `/actions/${fisrtToLowercase(fileDirectoryName)}Action.ts`
64 |
65 | removeFileOrDirectory(pagePath)
66 | removeFileOrDirectory(storeName)
67 | removeFileOrDirectory(actionName)
68 |
69 | ChildProcess.execFile('node', [Path.join(__dirname, './sync-mobx.js')], function (error, stdout, stderr) {
70 | if (error) {
71 | console.error('stderr', stderr);
72 | throw error;
73 | }
74 |
75 | console.log(Chalk.cyan(stdout))
76 | })
77 |
78 | /**
79 | * 创建文件
80 | * @param path {string} 路径
81 | * @defaultFileContent ?{string} 默认文件内容
82 | * @return {bollean}
83 | */
84 | function removeFileOrDirectory(path) {
85 | if (FS.existsSync(path)) {
86 | FS.removeSync(path);
87 | console.log(Chalk.red('delete file or directory success: ') + path);
88 | return true;
89 | }
90 | }
91 |
92 | function fisrtToLowercase(str) {
93 | return str.charAt(0).toLowerCase() + str.substr(1)
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/tools/add-page.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | let program = require("commander");
4 | let FS = require('fs-extra');
5 | let Path = require('path');
6 | let ChildProcess = require('child_process');
7 | let Chalk = require('chalk');
8 |
9 | let packageJson = FS.readJSONSync(Path.join(process.cwd(),'package.json'),{encoding:"utf-8"});
10 | let basePath =packageJson.basePath;
11 |
12 | if(!basePath){
13 | console.log(`you should set the key ${Chalk.red("basePath")} in your ${Chalk.green("package.json")} file. For example, ${Chalk.red('"basePath":"./src"')}`)
14 | return;
15 | }
16 |
17 | program
18 | .version("0.0.1")
19 | .option('[] ', 'the path of page or component')
20 | .option('-m, --mobx', 'create store and action')
21 | .option('-c, --component', `will create files to ${Chalk.greenBright(Path.join(basePath, 'components'))} directory`)
22 |
23 | program.on('--help', function () {
24 | console.log(" This is a quick tool to create a page. \n It can create index.tsx,index.scss file and add <-m> or <--mobx> param can create stores and actions \n");
25 | console.log('');
26 |
27 | console.log(' Examples:');
28 |
29 | console.log('');
30 |
31 | console.log(` $ node tools/add-page.js offerList ${Chalk.grey('// create a page')}`);
32 |
33 | console.log(` $ node tools/add-page.js offerList -c ${Chalk.grey('// create a component for common')}`);
34 |
35 | console.log(` $ node tools/add-page.js offerList/tableList ${Chalk.grey('// create a component inner page offerList')}`);
36 |
37 | console.log(` $ node tools/add-page.js offerList -m ${Chalk.grey('// create a page with stores and actions')}`);
38 |
39 | console.log('');
40 | });
41 |
42 | program.parse(process.argv);
43 |
44 | if (program.args.length < 1)
45 | throw new Error("missing param");
46 |
47 | let pageOrComPath = program.args[0];
48 |
49 | //跨页面级通用组件独自一个目录
50 | if (program.component) {
51 | basePath = Path.join(basePath, 'components')
52 | } else {
53 | basePath = Path.join(basePath, 'pages')
54 | }
55 |
56 | let componentTpl = FS.readFileSync(__dirname + '/templates/index.tsx.tpl', 'utf-8');
57 | let scssTpl = FS.readFileSync(__dirname + '/templates/index.scss.tpl', 'utf-8');
58 |
59 | let splitPath = pageOrComPath.split('/').filter(Boolean);
60 | let rootPageName = splitPath[0]; //页面目录
61 | let fileDirectoryName = splitPath[splitPath.length - 1]; //页面内最深层目录
62 |
63 | let pagePath = Path.join(process.cwd(), basePath, pageOrComPath)
64 | let rootPagePath = Path.join(process.cwd(), basePath, rootPageName)
65 |
66 |
67 | let pageTplData = {
68 | rootPageName,
69 | uppercaseName: fisrtToUppercase(fileDirectoryName),
70 | lowercaseName: fisrtToLowercase(fileDirectoryName),
71 | splitDashName: toSplitDash(pageOrComPath),
72 | relDir:new Array(splitPath.length+2).join('../'),
73 | type: program.component ? "component" : "page"
74 | };
75 |
76 | componentTpl = replaceVarible(componentTpl)
77 |
78 | scssTpl = replaceVarible(scssTpl)
79 |
80 |
81 | createFile(pagePath + "/index.tsx", componentTpl);
82 | createFile(pagePath + "/index.scss", scssTpl);
83 |
84 | if (program.mobx) {
85 | let storeTpl = FS.readFileSync(__dirname + '/templates/stores/store.ts.tpl', 'utf-8');
86 | let actionTpl = FS.readFileSync(__dirname + '/templates/actions/action.ts.tpl', 'utf-8');
87 |
88 | storeTpl = replaceVarible(storeTpl)
89 | actionTpl = replaceVarible(actionTpl)
90 |
91 | let storeName = rootPagePath + `/stores/${fisrtToLowercase(fileDirectoryName)}Store.ts`
92 | let actionName = rootPagePath + `/actions/${fisrtToLowercase(fileDirectoryName)}Action.ts`
93 | createFile(storeName, storeTpl);
94 | createFile(actionName, actionTpl);
95 |
96 | ChildProcess.execFile('node', [Path.join(__dirname, './sync-mobx.js')], function (error, stdout, stderr) {
97 | if (error) {
98 | console.error('stderr', stderr);
99 | throw error;
100 | }
101 | console.log(Chalk.cyan(stdout))
102 | })
103 | }
104 |
105 |
106 | /**
107 | * 创建文件
108 | * @param path {string} 路径
109 | * @defaultFileContent ?{string} 默认文件内容
110 | * @return {bollean}
111 | */
112 | function createFile(path, defaultFileContent) {
113 | defaultFileContent = defaultFileContent || "";
114 | if (FS.existsSync(path)) {
115 | console.log(Chalk.red('file already exists: ') + path);
116 | return false;
117 | } else {
118 | FS.outputFileSync(path, defaultFileContent);
119 | console.log('created "' + path + '".');
120 | return true;
121 | }
122 | }
123 |
124 | //替换模板变量
125 | function replaceVarible(tpl) {
126 | return tpl.replace(/\$\{(\w+?)\}\$/g, function (m, letiable) {
127 | return pageTplData[letiable];
128 | })
129 | }
130 |
131 | function toSplitDash(str) {
132 | let upperCaseRegex = /[A-Z]+(?=[A-Z][a-z]|$)|[A-Z]/g;
133 | str = str.split('/').filter(Boolean).join('/')
134 | str = str.replace(/\//g, (m, index) => {
135 | return (index ? '__' : '');
136 | })
137 | return str.replace(upperCaseRegex, (m, index) => {
138 | return (index ? '-' : '') + m.toLowerCase();
139 | })
140 | }
141 |
142 | function fisrtToLowercase(str) {
143 | return str.charAt(0).toLowerCase() + str.substr(1)
144 | }
145 |
146 | function fisrtToUppercase(str) {
147 | return str.charAt(0).toUpperCase() + str.substr(1)
148 | }
149 |
--------------------------------------------------------------------------------
/icomoon/style.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src: url('fonts/icomoon.eot?hiv3u1');
4 | src: url('fonts/icomoon.eot?hiv3u1#iefix') format('embedded-opentype'),
5 | url('fonts/icomoon.ttf?hiv3u1') format('truetype'),
6 | url('fonts/icomoon.woff?hiv3u1') format('woff'),
7 | url('fonts/icomoon.svg?hiv3u1#icomoon') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | [class^='icon-'],
13 | [class*=' icon-'] {
14 | /* use !important to prevent issues with browser extensions that change fonts */
15 | font-family: 'icomoon' !important;
16 | speak: none;
17 | font-style: normal;
18 | font-weight: normal;
19 | font-variant: normal;
20 | text-transform: none;
21 | line-height: 1;
22 |
23 | /* Better Font Rendering =========== */
24 | -webkit-font-smoothing: antialiased;
25 | -moz-osx-font-smoothing: grayscale;
26 | }
27 |
28 | .icon-error:before {
29 | content: '\e900';
30 | }
31 | .icon-link2:before {
32 | content: '\e901';
33 | }
34 | .icon-add-edit:before {
35 | content: '\e902';
36 | }
37 | .icon-photo-mask:before {
38 | content: '\e903';
39 | }
40 | .icon-skill:before {
41 | content: '\e904';
42 | }
43 | .icon-phone:before {
44 | content: '\e905';
45 | }
46 | .icon-people:before {
47 | content: '\e906';
48 | }
49 | .icon-email:before {
50 | content: '\e907';
51 | }
52 | .icon-warn:before {
53 | content: '\e908';
54 | }
55 | .icon-city:before {
56 | content: '\e909';
57 | }
58 | .icon-responsibility:before {
59 | content: '\e90a';
60 | }
61 | .icon-card:before {
62 | content: '\e90b';
63 | }
64 | .icon-edu:before {
65 | content: '\e90c';
66 | }
67 | .icon-competition:before {
68 | content: '\e90d';
69 | }
70 | .icon-paper:before {
71 | content: '\e90e';
72 | }
73 | .icon-interview:before {
74 | content: '\e90f';
75 | }
76 | .icon-uniE910:before {
77 | content: '\e910';
78 | }
79 | .icon-uniE911:before {
80 | content: '\e911';
81 | }
82 | .icon-uniE912:before {
83 | content: '\e912';
84 | }
85 | .icon-intention:before {
86 | content: '\e913';
87 | }
88 | .icon-requirement:before {
89 | content: '\e914';
90 | }
91 | .icon-honor:before {
92 | content: '\e915';
93 | }
94 | .icon-uniE916:before {
95 | content: '\e916';
96 | }
97 | .icon-uniE917:before {
98 | content: '\e917';
99 | }
100 | .icon-dot:before {
101 | content: '\e918';
102 | }
103 | .icon-wechat:before {
104 | content: '\e919';
105 | }
106 | .icon-internship:before {
107 | content: '\e91a';
108 | }
109 | .icon-phone2:before {
110 | content: '\e91b';
111 | }
112 | .icon-add:before {
113 | content: '\e91c';
114 | }
115 | .icon-apply-reord:before {
116 | content: '\e91d';
117 | }
118 | .icon-apply-reord2:before {
119 | content: '\e91e';
120 | }
121 | .icon-position:before {
122 | content: '\e91f';
123 | }
124 | .icon-project:before {
125 | content: '\e920';
126 | }
127 | .icon-email2:before {
128 | content: '\e921';
129 | }
130 | .icon-language:before {
131 | content: '\e922';
132 | }
133 | .icon-certificate:before {
134 | content: '\e923';
135 | }
136 | .icon-work-show:before {
137 | content: '\e924';
138 | }
139 | .icon-Shape-Copy-3:before {
140 | content: '\e925';
141 | }
142 | .icon-finger-post:before {
143 | content: '\e926';
144 | }
145 | .icon-file-signet:before {
146 | content: '\e927';
147 | }
148 | .icon-paper-plane:before {
149 | content: '\e928';
150 | }
151 | .icon-shield:before {
152 | content: '\e929';
153 | }
154 | .icon-left:before {
155 | content: '\e92a';
156 | }
157 | .icon-search:before {
158 | content: '\e92b';
159 | }
160 | .icon-link:before {
161 | content: '\e92c';
162 | }
163 | .icon-gender:before {
164 | content: '\e92d';
165 | }
166 | .icon-download:before {
167 | content: '\e92e';
168 | }
169 | .icon-return:before {
170 | content: '\e92f';
171 | }
172 | .icon-addJob:before {
173 | content: '\e930';
174 | }
175 | .icon-attachment:before {
176 | content: '\e931';
177 | }
178 | .icon-Interview:before {
179 | content: '\e932';
180 | }
181 | .icon-log:before {
182 | content: '\e933';
183 | }
184 | .icon-remarks:before {
185 | content: '\e934';
186 | }
187 | .icon-tel-off:before {
188 | content: '\e935';
189 | }
190 | .icon-tel-on:before {
191 | content: '\e936';
192 | }
193 | .icon-edit:before {
194 | content: '\e937';
195 | }
196 | .icon-pencil:before {
197 | content: '\e938';
198 | }
199 | .icon-arrow-down:before {
200 | content: '\e939';
201 | }
202 | .icon-description:before {
203 | content: '\e93a';
204 | }
205 | .icon-lock:before {
206 | content: '\e93b';
207 | }
208 | .icon-Combined-Shape-Copy-2:before {
209 | content: '\e93c';
210 | }
211 | .icon-right:before {
212 | content: '\e93d';
213 | }
214 | .icon-location-fill:before {
215 | content: '\e93e';
216 | }
217 | .icon-education:before {
218 | content: '\e93f';
219 | }
220 | .icon-workYear:before {
221 | content: '\e940';
222 | }
223 | .icon-close:before {
224 | content: '\e941';
225 | }
226 | .icon-record:before {
227 | content: '\e942';
228 | }
229 | .icon-close-o:before {
230 | content: '\e943';
231 | }
232 | .icon-gift:before {
233 | content: '\e944';
234 | }
235 | .icon-mobile:before {
236 | content: '\e958';
237 | }
238 | .icon-notification:before {
239 | content: '\ea08';
240 | }
241 | .icon-checkmark:before {
242 | content: '\ea10';
243 | }
244 | .icon-info-new:before {
245 | content: '\e945';
246 | color: #118bfb;
247 | }
248 |
--------------------------------------------------------------------------------
/tools/sync-mobx.js:
--------------------------------------------------------------------------------
1 | let FS = require('fs-extra')
2 | let Path = require('path')
3 | let Chalk = require('chalk')
4 | let listFile = require('./utils/io').listFiles
5 |
6 | let packageJson = FS.readJSONSync(Path.join(process.cwd(), 'package.json'), {
7 | encoding: "utf-8"
8 | });
9 | let basePath = packageJson.basePath;
10 | const storesPath = listFile(basePath, /(.+\/)*(stores|globalStores)\/(.+)\.(t|j)s$/g)
11 | //所有store的path 数组
12 | const actionsPath = listFile(basePath, /(.+\/)*actions\/(.+)\.(t|j)s$/g)
13 | //所有action的path 数组
14 | const mobxDependencePath = Path.join(process.cwd(), basePath); // ./src
15 | const mobxTypingsPath = Path.join(process.cwd(), basePath, 'typings'); // ./src/typings
16 |
17 |
18 | createDependenceFile()
19 | createTypingsFile()
20 |
21 | function createDependenceFile() {
22 | let content = createDependenceImportContent(mobxDependencePath)
23 |
24 | createFile(Path.join(mobxDependencePath, 'mobxDependence.ts'), content);
25 | }
26 |
27 | function createTypingsFile() {
28 | let importContent = createTypingsImportContent(mobxTypingsPath);
29 | let IRootStoreContent = generatorIRootStore();
30 | let IRootActionContent = generatorIRootAction();
31 | let IInjectContent =
32 | 'export interface IInject {\n' +
33 | ' rootStore:IRootStore\n' +
34 | ' rootAction:IRootAction\n' +
35 | '}'
36 |
37 | let mobxReactModuleDeclare =
38 | 'declare module "mobx-react" {' + '\n' +
39 | ' export type IValueMapSelf = IStoresToProps;' + '\n\n' +
40 | ' export function inject(' + '\n' +
41 | ' fn: IStoresToProps' + '\n' +
42 | ' ): (target: T) => T & IWrappedComponent' + '\n' +
43 | '}'
44 |
45 | let content =
46 | importContent + '\n\n' +
47 | IRootStoreContent + '\n\n' +
48 | IRootActionContent + '\n\n' +
49 | IInjectContent + '\n\n' +
50 | mobxReactModuleDeclare
51 |
52 | createFile(Path.join(mobxTypingsPath, 'index.d.ts'), content);
53 | }
54 |
55 | function generatorIRootStore() {
56 | let pathMap = storesPath.reduce((ret, path) => {
57 | let pathArr = getFileNameFrom(path);
58 | ret[pathArr[0]] = ret[pathArr[0]] || [];
59 | ret[pathArr[0]].push(pathArr[1]);
60 | return ret;
61 | }, {});
62 | let content =
63 | 'export interface IRootStore {\n';
64 | content += Object.keys(pathMap).reduce((ret, pageName) => {
65 | ret += ' ' + pageName + ": {\n";
66 | let storeArr = pathMap[pageName];
67 | storeArr.forEach(storeName => {
68 | ret += ' ' + storeName + ":" + firstToUppercase(storeName) + '\n';
69 | })
70 | ret += " }\n"
71 | return ret;
72 | }, '');
73 |
74 | content += '}';
75 | return content;
76 | }
77 |
78 | function generatorIRootAction() {
79 | let pathMap = actionsPath.reduce((ret, path) => {
80 | let pathArr = getFileNameFrom(path);
81 | ret[pathArr[0]] = ret[pathArr[0]] || [];
82 | ret[pathArr[0]].push(pathArr[1]);
83 | return ret;
84 | }, {});
85 | let content =
86 | 'export interface IRootAction {\n';
87 | content += Object.keys(pathMap).reduce((ret, pageName) => {
88 | ret += ' ' + pageName + ": {\n";
89 | let actionArr = pathMap[pageName];
90 | actionArr.forEach(actionName => {
91 | ret += ' ' + actionName + ":" + firstToUppercase(actionName) + '\n';
92 | })
93 | ret += " }\n"
94 | return ret;
95 | }, '');
96 |
97 | content += '}';
98 | return content;
99 | }
100 |
101 | function createDependenceImportContent(base) {
102 | let content = storesPath.concat(actionsPath).reduce((content, path) => {
103 | let pathArr = getFileNameFrom(path);
104 | let relativePath = Path.relative(base, path);
105 | if (relativePath.indexOf('.') !== 0) {
106 | relativePath = './' + relativePath
107 | }
108 | relativePath = removeExt(relativePath);
109 | let importConent = `import '${relativePath}'`
110 |
111 | content += importConent;
112 | content += '\n';
113 | return content;
114 | }, "")
115 | return content;
116 | }
117 |
118 | function createTypingsImportContent(base) {
119 | let content = storesPath.concat(actionsPath).reduce((content, path) => {
120 | let pathArr = getFileNameFrom(path);
121 | let relativePath = Path.relative(base, path);
122 | if (relativePath.indexOf('.') !== 0) {
123 | relativePath = './' + relativePath
124 | }
125 | relativePath = removeExt(relativePath);
126 | let importConent = `import ${firstToUppercase(pathArr[1])} from '${relativePath}'`
127 |
128 | content += importConent;
129 | content += '\n';
130 | return content;
131 | }, "")
132 | content += `import {IStoresToProps, IReactComponent, IWrappedComponent} from "mobx-react"
133 | `
134 | return content;
135 | }
136 |
137 |
138 | function getFileNameFrom(path) {
139 | let reg = /([^\/]+)\/(?:actions|stores|globalStores)\/(.+)\.(?:t|j)sx?$/
140 | let matched = path.match(reg)
141 | let pageName = matched && matched[1]
142 | let fileName = matched && matched[2]
143 | if (path.indexOf('globalStores') > -1) pageName = 'globalStores'
144 | return [pageName, fileName]
145 | }
146 |
147 | function firstToUppercase(str) {
148 | return str.charAt(0).toUpperCase() + str.substr(1)
149 | }
150 |
151 | function removeExt(path){
152 | let reg = /(.+)\.[^\.]+$/;
153 | return path.replace(reg,'$1')
154 | }
155 |
156 |
157 | /**
158 | * 创建文件
159 | * @param path {string} 路径
160 | * @defaultFileContent ?{string} 默认文件内容
161 | * @return {bollean}
162 | */
163 |
164 | function createFile(path, defaultFileContent) {
165 | defaultFileContent = defaultFileContent || ''
166 | let existed = FS.existsSync(path)
167 | FS.outputFileSync(path, defaultFileContent)
168 | if (existed) {
169 | console.log(Chalk.blue('updated "' + path + '".'))
170 | } else {
171 | console.log(Chalk.blue('created "' + path + '".'))
172 | }
173 | return true
174 | }
175 |
--------------------------------------------------------------------------------
/src/mobx/app.js:
--------------------------------------------------------------------------------
1 | import invariant from 'invariant';
2 | import {
3 | instanceType,
4 | storeTypeSymbol,
5 | storeInstanceSymbol,
6 | getInstanceFuncSymbol,
7 | singleInstanceSymbol,
8 | appActionKeySymbol,
9 | appStoreKeySymbol,
10 | } from './meta';
11 | import { defineReadOnlyProperty, firstToLowercase } from './utils';
12 | import AsyncManager from './zone';
13 |
14 | export class App {
15 | rootStore = {};
16 | rootAction = {};
17 |
18 | constructor() {
19 | this.asyncManager = AsyncManager.getInstance();
20 | this.processStores();
21 | this.processActions();
22 | }
23 |
24 | processStores() {
25 | let scannedStores = (App[appStoreKeySymbol] || []).reduce((ret, item) => {
26 | ret[item.page] = ret[item.page] || {};
27 | ret[item.page][item.name] = item.target;
28 | return ret;
29 | }, {});
30 |
31 | //扫描的文件
32 | Object.keys(scannedStores).forEach(pageKey => {
33 | invariant(
34 | !this.rootStore[pageKey],
35 | `dumplicated page or component name for ${pageKey}`
36 | );
37 |
38 | defineReadOnlyProperty(this.rootStore, pageKey, {});
39 |
40 | let pageStore = scannedStores[pageKey];
41 | this.processPageStore(pageStore, this.rootStore[pageKey]);
42 | });
43 | }
44 |
45 | processPageStore(pageStore, finalPageStore) {
46 | Object.keys(pageStore).forEach(storeKey => {
47 | this.processSingleStore(pageStore[storeKey], storeKey, finalPageStore);
48 | });
49 | }
50 |
51 | processSingleStore(storeClassOrInstance, storeKey, finalPageStore) {
52 | let asyncManager = this.asyncManager;
53 | if (typeof storeClassOrInstance == 'function') {
54 | if (
55 | storeClassOrInstance[storeTypeSymbol] === instanceType.singleton ||
56 | storeClassOrInstance[storeTypeSymbol] === undefined
57 | ) {
58 | Object.defineProperty(finalPageStore, firstToLowercase(storeKey), {
59 | configurable: true,
60 | enumerable: true,
61 | get() {
62 | storeClassOrInstance[singleInstanceSymbol] =
63 | storeClassOrInstance[singleInstanceSymbol] ||
64 | new storeClassOrInstance();
65 |
66 | asyncManager.wrapStore(storeClassOrInstance[singleInstanceSymbol]);
67 |
68 | return storeClassOrInstance[singleInstanceSymbol];
69 | },
70 | set() {
71 | throw Error('can not set store again');
72 | },
73 | });
74 | } else {
75 | this.processMultiInstance(storeClassOrInstance);
76 |
77 | Object.defineProperty(finalPageStore, firstToLowercase(storeKey), {
78 | configurable: true,
79 | enumerable: true,
80 | get() {
81 | return storeClassOrInstance[getInstanceFuncSymbol];
82 | },
83 | set() {
84 | throw Error('can not set store again');
85 | },
86 | });
87 | }
88 | } else {
89 | asyncManager.wrapStore(storeClassOrInstance);
90 | defineReadOnlyProperty(
91 | finalPageStore,
92 | firstToLowercase(storeKey),
93 | storeClassOrInstance
94 | );
95 | }
96 | }
97 |
98 | processMultiInstance(storeClassOrInstance) {
99 | let asyncManager = this.asyncManager;
100 |
101 | storeClassOrInstance[storeInstanceSymbol] =
102 | storeClassOrInstance[storeInstanceSymbol] || {};
103 |
104 | storeClassOrInstance[getInstanceFuncSymbol] =
105 | storeClassOrInstance[getInstanceFuncSymbol] ||
106 | function(uniqueKey) {
107 | storeClassOrInstance[storeInstanceSymbol][uniqueKey] =
108 | storeClassOrInstance[storeInstanceSymbol][uniqueKey] ||
109 | new storeClassOrInstance();
110 |
111 | asyncManager.wrapStore(
112 | storeClassOrInstance[storeInstanceSymbol][uniqueKey]
113 | );
114 |
115 | return storeClassOrInstance[storeInstanceSymbol][uniqueKey];
116 | };
117 | }
118 |
119 | processActions() {
120 | let scannedActions = (App[appActionKeySymbol] || []).reduce((ret, item) => {
121 | ret[item.page] = ret[item.page] || {};
122 | ret[item.page][item.name] = item.target;
123 | return ret;
124 | }, {});
125 | Object.keys(scannedActions).forEach(pageKey => {
126 | defineReadOnlyProperty(this.rootAction, pageKey, {});
127 |
128 | let pageAction = scannedActions[pageKey];
129 | let pageStore = this.rootStore[pageKey];
130 | this.processPageAction(pageAction, this.rootAction[pageKey], pageStore);
131 | });
132 | }
133 |
134 | processPageAction(pageAction, finalPageAction, pageStore) {
135 | Object.keys(pageAction).forEach(actionKey => {
136 | this.processSingleAction(
137 | pageAction[actionKey],
138 | actionKey,
139 | finalPageAction,
140 | pageStore
141 | );
142 | });
143 | }
144 |
145 | processSingleAction(
146 | actionClassOrInstance,
147 | actionKey,
148 | finalPageAction,
149 | pageStore
150 | ) {
151 | let asyncManager = this.asyncManager;
152 |
153 | if (typeof actionClassOrInstance == 'function') {
154 | Object.defineProperty(finalPageAction, firstToLowercase(actionKey), {
155 | configurable: true,
156 | enumerable: true,
157 | get() {
158 | actionClassOrInstance[singleInstanceSymbol] =
159 | actionClassOrInstance[singleInstanceSymbol] ||
160 | new actionClassOrInstance(
161 | pageStore,
162 | finalPageAction,
163 | this.rootStore,
164 | this.rootAction
165 | );
166 |
167 | asyncManager.wrapAction(actionClassOrInstance[singleInstanceSymbol]);
168 |
169 | return actionClassOrInstance[singleInstanceSymbol];
170 | },
171 | set() {
172 | throw Error('can not set action again');
173 | },
174 | });
175 | } else {
176 | asyncManager.wrapAction(actionClassOrInstance);
177 |
178 | defineReadOnlyProperty(
179 | finalPageAction,
180 | firstToLowercase(actionKey),
181 | actionClassOrInstance
182 | );
183 | }
184 | }
185 | }
186 |
187 | let app = null;
188 |
189 | export function createApp() {
190 | app = app || new App();
191 | return app;
192 | }
193 |
--------------------------------------------------------------------------------
/icomoon/style.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 | @font-face {
4 | font-family: 'icomoon';
5 | src: url('#{$icomoon-font-path}/icomoon.eot?wqixoz');
6 | src: url('#{$icomoon-font-path}/icomoon.eot?wqixoz#iefix')
7 | format('embedded-opentype'),
8 | url('#{$icomoon-font-path}/icomoon.ttf?wqixoz') format('truetype'),
9 | url('#{$icomoon-font-path}/icomoon.woff?wqixoz') format('woff'),
10 | url('#{$icomoon-font-path}/icomoon.svg?wqixoz#icomoon') format('svg');
11 | font-weight: normal;
12 | font-style: normal;
13 | }
14 |
15 | [class^='icon-'],
16 | [class*=' icon-'] {
17 | /* use !important to prevent issues with browser extensions that change fonts */
18 | font-family: 'icomoon' !important;
19 | speak: none;
20 | font-style: normal;
21 | font-weight: normal;
22 | font-variant: normal;
23 | text-transform: none;
24 | line-height: 1;
25 |
26 | /* Better Font Rendering =========== */
27 | -webkit-font-smoothing: antialiased;
28 | -moz-osx-font-smoothing: grayscale;
29 | }
30 |
31 | .icon-error {
32 | &:before {
33 | content: $icon-error;
34 | }
35 | }
36 | .icon-link2 {
37 | &:before {
38 | content: $icon-link2;
39 | }
40 | }
41 | .icon-add-edit {
42 | &:before {
43 | content: $icon-add-edit;
44 | }
45 | }
46 | .icon-photo-mask {
47 | &:before {
48 | content: $icon-photo-mask;
49 | }
50 | }
51 | .icon-skill {
52 | &:before {
53 | content: $icon-skill;
54 | }
55 | }
56 | .icon-phone {
57 | &:before {
58 | content: $icon-phone;
59 | }
60 | }
61 | .icon-people {
62 | &:before {
63 | content: $icon-people;
64 | }
65 | }
66 | .icon-email {
67 | &:before {
68 | content: $icon-email;
69 | }
70 | }
71 | .icon-warn {
72 | &:before {
73 | content: $icon-warn;
74 | }
75 | }
76 | .icon-city {
77 | &:before {
78 | content: $icon-city;
79 | }
80 | }
81 | .icon-responsibility {
82 | &:before {
83 | content: $icon-responsibility;
84 | }
85 | }
86 | .icon-card {
87 | &:before {
88 | content: $icon-card;
89 | }
90 | }
91 | .icon-edu {
92 | &:before {
93 | content: $icon-edu;
94 | }
95 | }
96 | .icon-competition {
97 | &:before {
98 | content: $icon-competition;
99 | }
100 | }
101 | .icon-paper {
102 | &:before {
103 | content: $icon-paper;
104 | }
105 | }
106 | .icon-interview {
107 | &:before {
108 | content: $icon-interview;
109 | }
110 | }
111 | .icon-uniE910 {
112 | &:before {
113 | content: $icon-uniE910;
114 | }
115 | }
116 | .icon-uniE911 {
117 | &:before {
118 | content: $icon-uniE911;
119 | }
120 | }
121 | .icon-uniE912 {
122 | &:before {
123 | content: $icon-uniE912;
124 | }
125 | }
126 | .icon-intention {
127 | &:before {
128 | content: $icon-intention;
129 | }
130 | }
131 | .icon-requirement {
132 | &:before {
133 | content: $icon-requirement;
134 | }
135 | }
136 | .icon-honor {
137 | &:before {
138 | content: $icon-honor;
139 | }
140 | }
141 | .icon-uniE916 {
142 | &:before {
143 | content: $icon-uniE916;
144 | }
145 | }
146 | .icon-uniE917 {
147 | &:before {
148 | content: $icon-uniE917;
149 | }
150 | }
151 | .icon-dot {
152 | &:before {
153 | content: $icon-dot;
154 | }
155 | }
156 | .icon-internship {
157 | &:before {
158 | content: $icon-internship;
159 | }
160 | }
161 | .icon-phone2 {
162 | &:before {
163 | content: $icon-phone2;
164 | }
165 | }
166 | .icon-add {
167 | &:before {
168 | content: $icon-add;
169 | }
170 | }
171 | .icon-apply-reord {
172 | &:before {
173 | content: $icon-apply-reord;
174 | }
175 | }
176 | .icon-apply-reord2 {
177 | &:before {
178 | content: $icon-apply-reord2;
179 | }
180 | }
181 | .icon-position {
182 | &:before {
183 | content: $icon-position;
184 | }
185 | }
186 | .icon-project {
187 | &:before {
188 | content: $icon-project;
189 | }
190 | }
191 | .icon-email2 {
192 | &:before {
193 | content: $icon-email2;
194 | }
195 | }
196 | .icon-language {
197 | &:before {
198 | content: $icon-language;
199 | }
200 | }
201 | .icon-certificate {
202 | &:before {
203 | content: $icon-certificate;
204 | }
205 | }
206 | .icon-work-show {
207 | &:before {
208 | content: $icon-work-show;
209 | }
210 | }
211 | .icon-Shape-Copy-3 {
212 | &:before {
213 | content: $icon-Shape-Copy-3;
214 | }
215 | }
216 | .icon-finger-post {
217 | &:before {
218 | content: $icon-finger-post;
219 | }
220 | }
221 | .icon-file-signet {
222 | &:before {
223 | content: $icon-file-signet;
224 | }
225 | }
226 | .icon-paper-plane {
227 | &:before {
228 | content: $icon-paper-plane;
229 | }
230 | }
231 | .icon-shield {
232 | &:before {
233 | content: $icon-shield;
234 | }
235 | }
236 | .icon-left {
237 | &:before {
238 | content: $icon-left;
239 | }
240 | }
241 | .icon-search {
242 | &:before {
243 | content: $icon-search;
244 | }
245 | }
246 | .icon-link {
247 | &:before {
248 | content: $icon-link;
249 | }
250 | }
251 | .icon-gender {
252 | &:before {
253 | content: $icon-gender;
254 | }
255 | }
256 | .icon-download {
257 | &:before {
258 | content: $icon-download;
259 | }
260 | }
261 | .icon-return {
262 | &:before {
263 | content: $icon-return;
264 | }
265 | }
266 | .icon-addJob {
267 | &:before {
268 | content: $icon-addJob;
269 | }
270 | }
271 | .icon-attachment {
272 | &:before {
273 | content: $icon-attachment;
274 | }
275 | }
276 | .icon-Interview {
277 | &:before {
278 | content: $icon-Interview;
279 | }
280 | }
281 | .icon-log {
282 | &:before {
283 | content: $icon-log;
284 | }
285 | }
286 | .icon-remarks {
287 | &:before {
288 | content: $icon-remarks;
289 | }
290 | }
291 | .icon-tel-off {
292 | &:before {
293 | content: $icon-tel-off;
294 | }
295 | }
296 | .icon-tel-on {
297 | &:before {
298 | content: $icon-tel-on;
299 | }
300 | }
301 | .icon-edit {
302 | &:before {
303 | content: $icon-edit;
304 | }
305 | }
306 | .icon-pencil {
307 | &:before {
308 | content: $icon-pencil;
309 | }
310 | }
311 | .icon-arrow-down {
312 | &:before {
313 | content: $icon-arrow-down;
314 | }
315 | }
316 | .icon-description {
317 | &:before {
318 | content: $icon-description;
319 | }
320 | }
321 | .icon-lock {
322 | &:before {
323 | content: $icon-lock;
324 | }
325 | }
326 | .icon-Combined-Shape-Copy-2 {
327 | &:before {
328 | content: $icon-Combined-Shape-Copy-2;
329 | }
330 | }
331 | .icon-right {
332 | &:before {
333 | content: $icon-right;
334 | }
335 | }
336 | .icon-location-fill {
337 | &:before {
338 | content: $icon-location-fill;
339 | }
340 | }
341 | .icon-education {
342 | &:before {
343 | content: $icon-education;
344 | }
345 | }
346 | .icon-workYear {
347 | &:before {
348 | content: $icon-workYear;
349 | }
350 | }
351 | .icon-close {
352 | &:before {
353 | content: $icon-close;
354 | }
355 | }
356 | .icon-record {
357 | &:before {
358 | content: $icon-record;
359 | }
360 | }
361 | .icon-close-o {
362 | &:before {
363 | content: $icon-close-o;
364 | }
365 | }
366 | .icon-gift {
367 | &:before {
368 | content: $icon-gift;
369 | }
370 | }
371 | .icon-mobile {
372 | &:before {
373 | content: $icon-mobile;
374 | }
375 | }
376 | .icon-notification {
377 | &:before {
378 | content: $icon-notification;
379 | }
380 | }
381 | .icon-checkmark {
382 | &:before {
383 | content: $icon-checkmark;
384 | }
385 | }
386 | .icon-wechat {
387 | &:before {
388 | content: $icon-wechat;
389 | color: #666;
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/icomoon/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IcoMoon Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Font Name: icomoon (Glyphs: 73)
13 |
14 |
15 |
Grid Size: Unknown
16 |
32 |
48 |
64 |
80 |
96 |
112 |
128 |
144 |
160 |
176 |
192 |
208 |
224 |
240 |
256 |
272 |
288 |
304 |
320 |
336 |
352 |
368 |
384 |
400 |
416 |
432 |
448 |
464 |
480 |
496 |
512 |
528 |
544 |
560 |
576 |
592 |
608 |
624 |
640 |
656 |
672 |
688 |
704 |
720 |
736 |
752 |
768 |
784 |
800 |
816 |
832 |
848 |
864 |
880 |
896 |
912 |
928 |
944 |
960 |
976 |
992 |
1008 |
1024 |
1040 |
1056 |
1072 |
1088 |
1104 |
1120 |
1136 |
1152 |
1168 |
1184 |
1185 |
1186 |
1187 |
1188 |
Font Test Drive
1189 |
1194 |
1196 |
1197 |
1198 |
1199 |
1200 |
1203 |
1204 |
1205 |
1206 |
1207 |
--------------------------------------------------------------------------------
/icomoon/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------