├── .gitignore ├── src ├── pages │ └── goods │ │ ├── index.scss │ │ ├── stores │ │ ├── goodsStore.ts │ │ ├── goodsListStore.ts │ │ └── goodsDetailStore.ts │ │ ├── goodsDetail │ │ ├── index.scss │ │ └── index.tsx │ │ ├── actions │ │ ├── goodsDetailAction.ts │ │ ├── goodsListAction.ts │ │ └── goodsAction.ts │ │ ├── goodsList │ │ ├── index.scss │ │ └── index.tsx │ │ └── index.tsx ├── components │ └── AComponent │ │ ├── index.scss │ │ ├── stores │ │ └── aComponentStore.ts │ │ ├── actions │ │ └── aComponentAction.ts │ │ └── index.tsx ├── mobx │ ├── index.js │ ├── action.js │ ├── store.js │ ├── utils.js │ ├── meta.js │ ├── provider.js │ ├── zone.js │ └── app.js ├── mobxDependence.ts ├── routes │ └── routes.js ├── index.tsx ├── mockData │ └── apis.ts └── typings │ └── index.d.ts ├── icomoon ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg ├── Read Me.txt ├── demo-files │ ├── demo.js │ └── demo.css ├── variables.scss ├── style.css ├── style.scss └── demo.html ├── tools ├── templates │ ├── index.scss.tpl │ ├── stores │ │ └── store.ts.tpl │ ├── actions │ │ └── action.ts.tpl │ └── index.tsx.tpl ├── utils │ └── io.js ├── rm-page.js ├── add-page.js └── sync-mobx.js ├── .idea ├── vcs.xml ├── misc.xml └── encodings.xml ├── index.html ├── .babelrc ├── tsconfig.json ├── package.json ├── README.md └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /src/pages/goods/index.scss: -------------------------------------------------------------------------------- 1 | $prefixClass: 'page-goods'; 2 | 3 | .#{$prefixClass} { 4 | } 5 | -------------------------------------------------------------------------------- /icomoon/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruozhou/mobx-example/HEAD/icomoon/fonts/icomoon.eot -------------------------------------------------------------------------------- /icomoon/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruozhou/mobx-example/HEAD/icomoon/fonts/icomoon.ttf -------------------------------------------------------------------------------- /icomoon/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luruozhou/mobx-example/HEAD/icomoon/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/components/AComponent/index.scss: -------------------------------------------------------------------------------- 1 | $prefixClass: 'component-a-component'; 2 | 3 | .#{$prefixClass} { 4 | } 5 | -------------------------------------------------------------------------------- /tools/templates/index.scss.tpl: -------------------------------------------------------------------------------- 1 | $prefixClass: '${type}$-${splitDashName}$'; 2 | 3 | .#{$prefixClass} { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/goods/stores/goodsStore.ts: -------------------------------------------------------------------------------- 1 | import { action, observable, computed } from 'mobx'; 2 | import { mStore } from '../../../mobx/store'; 3 | 4 | @mStore 5 | export default class PageStore {} 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/mobx/index.js: -------------------------------------------------------------------------------- 1 | export { mStore } from './store'; 2 | export { mAction } from './action'; 3 | export { instanceType } from './meta'; 4 | export { provider } from './provider'; //放最后,顺序不能乱,否则有循环依赖问题 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/pages/goods/goodsDetail/index.scss: -------------------------------------------------------------------------------- 1 | $prefixClass: 'page-goods__goods-detail'; 2 | 3 | .#{$prefixClass} { 4 | padding: 20px; 5 | margin-left: 200px; 6 | img { 7 | width: 100px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tools/templates/stores/store.ts.tpl: -------------------------------------------------------------------------------- 1 | import { action, observable,computed } from 'mobx' 2 | import { mStore } from '../../../mobx/store' 3 | 4 | @mStore 5 | export default class ${uppercaseName}$Store { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/components/AComponent/stores/aComponentStore.ts: -------------------------------------------------------------------------------- 1 | import { action, observable, computed } from 'mobx'; 2 | import { mStore } from '../../../mobx/store'; 3 | 4 | @mStore 5 | export default class AComponentStore {} 6 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mobx demo 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/goods/actions/goodsDetailAction.ts: -------------------------------------------------------------------------------- 1 | import { mAction } from '../../../mobx/action'; 2 | import { IRootAction, IRootStore } from '../../../typings'; 3 | 4 | @mAction 5 | export default class GoodsDetailAction { 6 | constructor( 7 | public stores: IRootStore['goods'], 8 | public actions: IRootAction['goods'] 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /tools/templates/actions/action.ts.tpl: -------------------------------------------------------------------------------- 1 | import { mAction } from '../../../mobx/action' 2 | import {IRootAction, IRootStore} from "../../../typings"; 3 | 4 | @mAction 5 | export default class ${uppercaseName}$Action { 6 | constructor(public stores: IRootStore["${rootPageName}$"], public actions: IRootStore["${rootPageName}$"]) { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/AComponent/actions/aComponentAction.ts: -------------------------------------------------------------------------------- 1 | import { mAction } from '../../../mobx/action'; 2 | import { IRootAction, IRootStore } from '../../../typings'; 3 | 4 | @mAction 5 | export default class AComponentAction { 6 | constructor( 7 | public stores: IRootStore['AComponent'], 8 | public actions: IRootStore['AComponent'] 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "stage-0", 5 | "react" 6 | ], 7 | "plugins": [ 8 | [ 9 | "transform-runtime", 10 | { 11 | "helpers": false, 12 | "polyfill": false, 13 | "regenerator": true, 14 | "moduleName": "babel-runtime" 15 | } 16 | ], 17 | "react-hot-loader/babel" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/mobxDependence.ts: -------------------------------------------------------------------------------- 1 | import './components/AComponent/stores/aComponentStore'; 2 | import './pages/goods/stores/goodsStore'; 3 | import './pages/goods/stores/goodsDetailStore'; 4 | import './pages/goods/stores/goodsListStore'; 5 | import './components/AComponent/actions/aComponentAction'; 6 | import './pages/goods/actions/goodsAction'; 7 | import './pages/goods/actions/goodsDetailAction'; 8 | import './pages/goods/actions/goodsListAction'; 9 | -------------------------------------------------------------------------------- /src/mobx/action.js: -------------------------------------------------------------------------------- 1 | import { actionNameSymbol, appActionKeySymbol, actionTypeSymbol } from './meta'; 2 | import { App } from './app'; 3 | 4 | export let mAction = (config = {}) => target => { 5 | target[actionNameSymbol] = config.name || target.name || null; 6 | target[actionTypeSymbol] = config.type; 7 | App[appActionKeySymbol] = App[appActionKeySymbol] || []; 8 | App[appActionKeySymbol].push({ 9 | target, 10 | name: config.name, 11 | page: config.page, 12 | }); 13 | return target; 14 | }; 15 | -------------------------------------------------------------------------------- /src/mobx/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | storeNameSymbol, 3 | appStoreKeySymbol, 4 | instanceType, 5 | storeTypeSymbol, 6 | } from './meta'; 7 | import { App } from './app'; 8 | 9 | export let mStore = (config = { type: instanceType.singleton }) => target => { 10 | target[storeNameSymbol] = config.name || target.name || null; 11 | target[storeTypeSymbol] = config.type; 12 | App[appStoreKeySymbol] = App[appStoreKeySymbol] || []; 13 | App[appStoreKeySymbol].push({ target, name: config.name, page: config.page }); 14 | return target; 15 | }; 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "jsx": "react", 6 | "sourceMap": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "strictNullChecks": false, 10 | "outDir": "dist", 11 | "strict": false, 12 | "allowSyntheticDefaultImports": true, 13 | "watch": true, 14 | "types": ["webpack-env"], 15 | "lib": ["es2017", "dom"] 16 | }, 17 | "exclude": ["dist", "node_modules"], 18 | "include": ["./src/**/*.ts", "./src/**/*.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/goods/actions/goodsListAction.ts: -------------------------------------------------------------------------------- 1 | import { mAction } from '../../../mobx/action'; 2 | import { IRootAction, IRootStore } from '../../../typings'; 3 | 4 | @mAction 5 | export default class GoodsListAction { 6 | constructor( 7 | public stores: IRootStore['goods'], 8 | public actions: IRootAction['goods'] 9 | ) {} 10 | 11 | async setCurrentGoods(goods) { 12 | this.stores.goodsDetailStore.setLoading(true); 13 | await this.stores.goodsDetailStore.getCurrentGoodsDetail(goods.id); 14 | this.stores.goodsDetailStore.setLoading(false); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/goods/stores/goodsListStore.ts: -------------------------------------------------------------------------------- 1 | import { action, observable, computed } from 'mobx'; 2 | import { mStore } from '../../../mobx/store'; 3 | import { getGoodsList } from '../../../mockData/apis'; 4 | 5 | interface IGoods { 6 | id: number; 7 | name: string; 8 | } 9 | 10 | @mStore 11 | export default class GoodsListStore { 12 | @observable 13 | goodsList: IGoods[] = []; 14 | 15 | async getGoodsList() { 16 | let data: IGoods[] = await getGoodsList(); 17 | this.setGoodsList(data); 18 | } 19 | 20 | @action 21 | setGoodsList(goodsList: IGoods[]) { 22 | this.goodsList = goodsList; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tools/utils/io.js: -------------------------------------------------------------------------------- 1 | const FS = require("fs"); 2 | const Path = require("path"); 3 | exports.listFiles = function listFiles(path, regex = /./) { 4 | let names = FS.readdirSync(path); 5 | let files = []; 6 | let dirs = []; 7 | for (let name of names) { 8 | let targetPath = Path.join(path, name); 9 | if (FS.statSync(targetPath).isFile()) { 10 | if (!!targetPath.match(regex)) { 11 | files.push(targetPath); 12 | } 13 | } 14 | else { 15 | dirs.push(targetPath); 16 | } 17 | } 18 | for (let dir of dirs) { 19 | files.push.apply(files,listFiles(dir, regex)); 20 | } 21 | return files; 22 | } 23 | -------------------------------------------------------------------------------- /src/routes/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useRouterHistory, Router, Route } from 'react-router'; 3 | import createHistory from 'history/lib/createBrowserHistory'; 4 | 5 | let history = useRouterHistory(createHistory)(); 6 | 7 | let pages = { 8 | Goods: () => import('../pages/goods/index'), 9 | }; 10 | 11 | const routes = ( 12 | 13 | 14 | 15 | ); 16 | 17 | function toGetter(loader) { 18 | return (state, callback) => { 19 | loader().then(x => { 20 | x = x.__esModule ? x.default : x; 21 | callback(null, x); 22 | }); 23 | }; 24 | } 25 | 26 | export default routes; 27 | -------------------------------------------------------------------------------- /icomoon/Read Me.txt: -------------------------------------------------------------------------------- 1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures. 2 | 3 | To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts 4 | 5 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects. 6 | 7 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection. 8 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { provider } from './mobx/provider'; 4 | import routes from './routes/routes.js'; 5 | import './mobxDependence'; 6 | 7 | @provider 8 | class App extends React.Component { 9 | static defaultProps = { 10 | prefixCls: 'app-container', 11 | }; 12 | 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | componentDidMount() {} 18 | 19 | render() { 20 | let { prefixCls } = this.props; 21 | return ( 22 |
23 |
console.log('this is a mobx project')} /> 24 | {routes} 25 |
26 | ); 27 | } 28 | } 29 | 30 | render(, document.getElementById('app')); 31 | -------------------------------------------------------------------------------- /src/pages/goods/actions/goodsAction.ts: -------------------------------------------------------------------------------- 1 | import { mAction } from '../../../mobx/action'; 2 | import { IRootStore, IRootAction } from '../../../typings/index'; 3 | 4 | @mAction 5 | export default class GoodsAction { 6 | constructor( 7 | public stores: IRootStore['goods'], 8 | public actions: IRootAction['goods'] 9 | ) {} 10 | 11 | async init() { 12 | await this.stores.goodsListStore.getGoodsList(); 13 | let firstGoods = 14 | this.stores.goodsListStore.goodsList && 15 | this.stores.goodsListStore.goodsList[0]; 16 | if (firstGoods) { 17 | this.stores.goodsDetailStore.setLoading(true); 18 | await this.stores.goodsDetailStore.getCurrentGoodsDetail(firstGoods.id); 19 | this.stores.goodsDetailStore.setLoading(false); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/pages/goods/goodsList/index.scss: -------------------------------------------------------------------------------- 1 | $prefixClass: 'page-goods__goods-list'; 2 | 3 | .#{$prefixClass} { 4 | width: 200px; 5 | color: hsla(0, 0%, 100%, 0.7); 6 | background-color: #363d4a; 7 | height: 100%; 8 | float: left; 9 | padding-bottom: 10000px; 10 | margin-bottom: -10000px; 11 | ul { 12 | li { 13 | &.active { 14 | background-color: #2b303b; 15 | color: #fff; 16 | border-left-color: #118bfb; 17 | } 18 | &:hover { 19 | color: #fff; 20 | text-decoration: none; 21 | } 22 | border-left-style: solid; 23 | border-left-color: transparent; 24 | display: block; 25 | padding: 10px 0; 26 | cursor: pointer; 27 | padding-left: 37px; 28 | border-left-width: 3px; 29 | font-size: 14px; 30 | line-height: 20px; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/pages/goods/stores/goodsDetailStore.ts: -------------------------------------------------------------------------------- 1 | import { action, observable, computed } from 'mobx'; 2 | import { mStore } from '../../../mobx/store'; 3 | import { getGoodsDetail } from '../../../mockData/apis'; 4 | 5 | interface IGoodsDetail { 6 | id: number; 7 | name: string; 8 | desc: string; 9 | img: string; 10 | } 11 | 12 | @mStore 13 | export default class GoodsDetailStore { 14 | @observable 15 | currentGoods: IGoodsDetail = null; 16 | @observable 17 | loading: boolean = false; 18 | 19 | async getCurrentGoodsDetail(id: number) { 20 | let data: IGoodsDetail = await getGoodsDetail(id); 21 | this.setCurrentGoodsDetail(data); 22 | } 23 | 24 | @action 25 | setCurrentGoodsDetail(currentGoods: IGoodsDetail) { 26 | this.currentGoods = currentGoods; 27 | } 28 | 29 | @action 30 | setLoading(loading: boolean) { 31 | this.loading = loading; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/mockData/apis.ts: -------------------------------------------------------------------------------- 1 | const data = new Array(100).fill(null).map((v, index) => { 2 | return { 3 | id: index + 1, 4 | name: `商品${index + 1}`, 5 | desc: `这是商品${index + 1}的一行描述`, 6 | img: 7 | 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1550313171590&di=d510947d9c53bd07bf243816eb3956aa&imgtype=0&src=http%3A%2F%2Fb-ssl.duitang.com%2Fuploads%2Fitem%2F201508%2F04%2F20150804221338_AGu3y.jpeg', 8 | }; 9 | }); 10 | 11 | export async function getGoodsList() { 12 | await wait(500); 13 | const list = data.map(r => { 14 | return { 15 | id: r.id, 16 | name: r.name, 17 | }; 18 | }); 19 | return list; 20 | } 21 | 22 | export async function getGoodsDetail(id) { 23 | await wait(500); 24 | const item = data.find(r => r.id === id); 25 | return item; 26 | } 27 | 28 | export function wait(ms) { 29 | return new Promise(r => setTimeout(r, ms)); 30 | } 31 | -------------------------------------------------------------------------------- /src/mobx/utils.js: -------------------------------------------------------------------------------- 1 | export const defineReadOnlyProperty = ( 2 | target, 3 | name, 4 | value, 5 | message = 'property is readonly' 6 | ) => { 7 | Object.defineProperty(target, name, { 8 | configurable: true, 9 | enumerable: true, 10 | get() { 11 | return value; 12 | }, 13 | set() { 14 | throw Error(message); 15 | }, 16 | }); 17 | }; 18 | 19 | export const defineHiddenProperty = (target, name, value) => { 20 | Object.defineProperty(target, name, { 21 | configurable: true, 22 | enumerable: false, 23 | writable: false, 24 | value, 25 | }); 26 | }; 27 | 28 | export function firstToLowercase(str) { 29 | return str.charAt(0).toLowerCase() + str.substr(1); 30 | } 31 | 32 | export function isPromiseLike(p) { 33 | if ( 34 | p && 35 | typeof p === 'object' && 36 | typeof p.then === 'function' && 37 | typeof p.catch === 'function' 38 | ) { 39 | return true; 40 | } 41 | return false; 42 | } 43 | -------------------------------------------------------------------------------- /src/components/AComponent/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 | } 23 | 24 | @inject(injector) 25 | @observer 26 | export default class AComponent extends React.Component { 27 | static defaultProps = { 28 | prefixCls: 'component-a-component', 29 | }; 30 | 31 | constructor(props) { 32 | super(props); 33 | } 34 | 35 | componentDidMount() {} 36 | 37 | render() { 38 | let { prefixCls } = this.props; 39 | return
一个通用组件
; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /icomoon/demo-files/demo.js: -------------------------------------------------------------------------------- 1 | if (!('boxShadow' in document.body.style)) { 2 | document.body.setAttribute('class', 'noBoxShadow') 3 | } 4 | 5 | document.body.addEventListener('click', function(e) { 6 | var target = e.target 7 | if ( 8 | target.tagName === 'INPUT' && 9 | target.getAttribute('class').indexOf('liga') === -1 10 | ) { 11 | target.select() 12 | } 13 | }) 14 | ;(function() { 15 | var fontSize = document.getElementById('fontSize'), 16 | testDrive = document.getElementById('testDrive'), 17 | testText = document.getElementById('testText') 18 | function updateTest() { 19 | testDrive.innerHTML = testText.value || String.fromCharCode(160) 20 | if (window.icomoonLiga) { 21 | window.icomoonLiga(testDrive) 22 | } 23 | } 24 | function updateSize() { 25 | testDrive.style.fontSize = fontSize.value + 'px' 26 | } 27 | fontSize.addEventListener('change', updateSize, false) 28 | testText.addEventListener('input', updateTest, false) 29 | testText.addEventListener('change', updateTest, false) 30 | updateSize() 31 | })() 32 | -------------------------------------------------------------------------------- /src/mobx/meta.js: -------------------------------------------------------------------------------- 1 | export const storeNameSymbol = getSymbol('store-name'); 2 | export const storeTypeSymbol = getSymbol('store-type'); 3 | export const storeInstanceSymbol = getSymbol('store-instance'); //多实例缓存 4 | export const singleInstanceSymbol = getSymbol('single-instance'); //单实例缓存 5 | export const getInstanceFuncSymbol = getSymbol('get-instance-func'); //多实例根据key获取实例 6 | export const actionNameSymbol = getSymbol('action-name'); 7 | export const actionTypeSymbol = getSymbol('action-type'); 8 | export const appStoreKeySymbol = getSymbol('app-store-key'); 9 | export const appActionKeySymbol = getSymbol('app-action-key'); 10 | 11 | export const asyncManagerSymbol = getSymbol('async-manager'); 12 | export const storeWrappedSymbol = getSymbol('store-has-wrapped'); 13 | export const actionWrappedSymbol = getSymbol('action-has-wrapped'); 14 | 15 | export const instanceType = { 16 | singleton: getSymbol('singleton'), 17 | multi: getSymbol('multi'), 18 | }; 19 | 20 | function getSymbol(name) { 21 | return typeof Symbol !== 'undefined' ? Symbol(name) : `__symbol-${name}__`; 22 | } 23 | -------------------------------------------------------------------------------- /tools/templates/index.tsx.tpl: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import {inject, observer} from 'mobx-react' 3 | import './index.scss' 4 | import {IRootStore,IRootAction} from '${relDir}$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({rootStore, rootAction}:{ 15 | rootStore:IRootStore 16 | rootAction:IRootAction 17 | }) { 18 | return { 19 | 20 | } 21 | } 22 | 23 | @inject(injector) 24 | @observer 25 | export default class ${uppercaseName}$ extends React.Component { 26 | static defaultProps = { 27 | prefixCls: '${type}$-${splitDashName}$' 28 | } 29 | 30 | constructor(props) { 31 | super(props) 32 | } 33 | 34 | componentDidMount() { 35 | 36 | } 37 | 38 | render() { 39 | let {prefixCls} = this.props 40 | return ( 41 |
42 | 43 |
44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/pages/goods/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 | import GoodsList from './goodsList'; 6 | import GoodsDetail from './goodsDetail'; 7 | 8 | type injectorReturnType = ReturnType; 9 | 10 | interface Props extends Partial { 11 | prefixCls?: string; 12 | 13 | [k: string]: any; 14 | } 15 | 16 | function injector({ 17 | rootStore, 18 | rootAction, 19 | }: { 20 | rootStore: IRootStore; 21 | rootAction: IRootAction; 22 | }) { 23 | return { 24 | pageAction: rootAction.goods, 25 | }; 26 | } 27 | 28 | @inject(injector) 29 | @observer 30 | export default class Goods extends React.Component { 31 | static defaultProps = { 32 | prefixCls: 'page-goods', 33 | }; 34 | 35 | constructor(props) { 36 | super(props); 37 | } 38 | 39 | async componentDidMount() { 40 | let { pageAction } = this.props; 41 | await pageAction.goodsAction.init(); 42 | } 43 | 44 | render() { 45 | let { prefixCls } = this.props; 46 | return ( 47 |
48 | 49 | 50 |
51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/pages/goods/goodsDetail/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 GoodsDetail extends React.Component { 30 | static defaultProps = { 31 | prefixCls: 'page-goods__goods-detail', 32 | }; 33 | 34 | constructor(props) { 35 | super(props); 36 | } 37 | 38 | componentDidMount() {} 39 | 40 | render() { 41 | let { prefixCls, pageStore } = this.props; 42 | 43 | let currentGoods = pageStore.goodsDetailStore.currentGoods; 44 | 45 | if (pageStore.goodsDetailStore.loading) { 46 | return

loading...

; 47 | } 48 | return ( 49 |
50 |

{currentGoods && currentGoods.name}

51 |

{currentGoods && currentGoods.desc}

52 | 53 |
54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/mobx/provider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'mobx-react'; 3 | import { createApp } from './app'; 4 | 5 | export const provider = WrapComponentOrElement => { 6 | let app = createApp(); 7 | 8 | if (React.isValidElement(WrapComponentOrElement)) { 9 | let componentName = getDisplayName(WrapComponentOrElement.type); 10 | 11 | return ( 12 | 17 | {WrapComponentOrElement} 18 | 19 | ); 20 | } else { 21 | return class WrapperComponent extends Component { 22 | static displayName = `Provider(${getDisplayName( 23 | WrapComponentOrElement 24 | )})`; 25 | 26 | constructor(props) { 27 | super(props); 28 | } 29 | 30 | componentWillReceiveProps(nextProps) {} 31 | 32 | render() { 33 | const props = this.props; 34 | return ( 35 | 40 | 41 | {this.props.children} 42 | 43 | 44 | ); 45 | } 46 | }; 47 | } 48 | }; 49 | 50 | export function getDisplayName(WrappedComponent) { 51 | return WrappedComponent.displayName || WrappedComponent.name || 'Component'; 52 | } 53 | -------------------------------------------------------------------------------- /src/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | import AComponentStore from '../components/AComponent/stores/aComponentStore'; 2 | import GoodsStore from '../pages/goods/stores/goodsStore'; 3 | import GoodsDetailStore from '../pages/goods/stores/goodsDetailStore'; 4 | import GoodsListStore from '../pages/goods/stores/goodsListStore'; 5 | import AComponentAction from '../components/AComponent/actions/aComponentAction'; 6 | import GoodsAction from '../pages/goods/actions/goodsAction'; 7 | import GoodsDetailAction from '../pages/goods/actions/goodsDetailAction'; 8 | import GoodsListAction from '../pages/goods/actions/goodsListAction'; 9 | import { IStoresToProps, IReactComponent, IWrappedComponent } from 'mobx-react'; 10 | 11 | export interface IRootStore { 12 | AComponent: { 13 | aComponentStore: AComponentStore; 14 | }; 15 | goods: { 16 | goodsStore: GoodsStore; 17 | goodsDetailStore: GoodsDetailStore; 18 | goodsListStore: GoodsListStore; 19 | }; 20 | } 21 | 22 | export interface IRootAction { 23 | AComponent: { 24 | aComponentAction: AComponentAction; 25 | }; 26 | goods: { 27 | goodsAction: GoodsAction; 28 | goodsDetailAction: GoodsDetailAction; 29 | goodsListAction: GoodsListAction; 30 | }; 31 | } 32 | 33 | export interface IInject { 34 | rootStore: IRootStore; 35 | rootAction: IRootAction; 36 | } 37 | 38 | declare module 'mobx-react' { 39 | export type IValueMapSelf = IStoresToProps; 40 | 41 | export function inject( 42 | fn: IStoresToProps 43 | ): (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 |
17 |
18 | 19 | 20 | 21 | icon-error 22 |
23 |
24 | 25 | 26 |
27 |
28 | liga: 29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | icon-link2 38 |
39 |
40 | 41 | 42 |
43 |
44 | liga: 45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | icon-add-edit 54 |
55 |
56 | 57 | 58 |
59 |
60 | liga: 61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | icon-photo-mask 70 |
71 |
72 | 73 | 74 |
75 |
76 | liga: 77 | 78 |
79 |
80 |
81 |
82 | 83 | 84 | 85 | icon-skill 86 |
87 |
88 | 89 | 90 |
91 |
92 | liga: 93 | 94 |
95 |
96 |
97 |
98 | 99 | 100 | 101 | icon-phone 102 |
103 |
104 | 105 | 106 |
107 |
108 | liga: 109 | 110 |
111 |
112 |
113 |
114 | 115 | 116 | 117 | icon-people 118 |
119 |
120 | 121 | 122 |
123 |
124 | liga: 125 | 126 |
127 |
128 |
129 |
130 | 131 | 132 | 133 | icon-email 134 |
135 |
136 | 137 | 138 |
139 |
140 | liga: 141 | 142 |
143 |
144 |
145 |
146 | 147 | 148 | 149 | icon-warn 150 |
151 |
152 | 153 | 154 |
155 |
156 | liga: 157 | 158 |
159 |
160 |
161 |
162 | 163 | 164 | 165 | icon-city 166 |
167 |
168 | 169 | 170 |
171 |
172 | liga: 173 | 174 |
175 |
176 |
177 |
178 | 179 | 180 | 181 | icon-responsibility 182 |
183 |
184 | 185 | 186 |
187 |
188 | liga: 189 | 190 |
191 |
192 |
193 |
194 | 195 | 196 | 197 | icon-card 198 |
199 |
200 | 201 | 202 |
203 |
204 | liga: 205 | 206 |
207 |
208 |
209 |
210 | 211 | 212 | 213 | icon-edu 214 |
215 |
216 | 217 | 218 |
219 |
220 | liga: 221 | 222 |
223 |
224 |
225 |
226 | 227 | 228 | 229 | icon-competition 230 |
231 |
232 | 233 | 234 |
235 |
236 | liga: 237 | 238 |
239 |
240 |
241 |
242 | 243 | 244 | 245 | icon-paper 246 |
247 |
248 | 249 | 250 |
251 |
252 | liga: 253 | 254 |
255 |
256 |
257 |
258 | 259 | 260 | 261 | icon-interview 262 |
263 |
264 | 265 | 266 |
267 |
268 | liga: 269 | 270 |
271 |
272 |
273 |
274 | 275 | 276 | 277 | icon-uniE910 278 |
279 |
280 | 281 | 282 |
283 |
284 | liga: 285 | 286 |
287 |
288 |
289 |
290 | 291 | 292 | 293 | icon-uniE911 294 |
295 |
296 | 297 | 298 |
299 |
300 | liga: 301 | 302 |
303 |
304 |
305 |
306 | 307 | 308 | 309 | icon-uniE912 310 |
311 |
312 | 313 | 314 |
315 |
316 | liga: 317 | 318 |
319 |
320 |
321 |
322 | 323 | 324 | 325 | icon-intention 326 |
327 |
328 | 329 | 330 |
331 |
332 | liga: 333 | 334 |
335 |
336 |
337 |
338 | 339 | 340 | 341 | icon-requirement 342 |
343 |
344 | 345 | 346 |
347 |
348 | liga: 349 | 350 |
351 |
352 |
353 |
354 | 355 | 356 | 357 | icon-honor 358 |
359 |
360 | 361 | 362 |
363 |
364 | liga: 365 | 366 |
367 |
368 |
369 |
370 | 371 | 372 | 373 | icon-uniE916 374 |
375 |
376 | 377 | 378 |
379 |
380 | liga: 381 | 382 |
383 |
384 |
385 |
386 | 387 | 388 | 389 | icon-uniE917 390 |
391 |
392 | 393 | 394 |
395 |
396 | liga: 397 | 398 |
399 |
400 |
401 |
402 | 403 | 404 | 405 | icon-dot 406 |
407 |
408 | 409 | 410 |
411 |
412 | liga: 413 | 414 |
415 |
416 |
417 |
418 | 419 | 420 | 421 | icon-wechat 422 |
423 |
424 | 425 | 426 |
427 |
428 | liga: 429 | 430 |
431 |
432 |
433 |
434 | 435 | 436 | 437 | icon-internship 438 |
439 |
440 | 441 | 442 |
443 |
444 | liga: 445 | 446 |
447 |
448 |
449 |
450 | 451 | 452 | 453 | icon-phone2 454 |
455 |
456 | 457 | 458 |
459 |
460 | liga: 461 | 462 |
463 |
464 |
465 |
466 | 467 | 468 | 469 | icon-add 470 |
471 |
472 | 473 | 474 |
475 |
476 | liga: 477 | 478 |
479 |
480 |
481 |
482 | 483 | 484 | 485 | icon-apply-reord 486 |
487 |
488 | 489 | 490 |
491 |
492 | liga: 493 | 494 |
495 |
496 |
497 |
498 | 499 | 500 | 501 | icon-apply-reord2 502 |
503 |
504 | 505 | 506 |
507 |
508 | liga: 509 | 510 |
511 |
512 |
513 |
514 | 515 | 516 | 517 | icon-position 518 |
519 |
520 | 521 | 522 |
523 |
524 | liga: 525 | 526 |
527 |
528 |
529 |
530 | 531 | 532 | 533 | icon-project 534 |
535 |
536 | 537 | 538 |
539 |
540 | liga: 541 | 542 |
543 |
544 |
545 |
546 | 547 | 548 | 549 | icon-email2 550 |
551 |
552 | 553 | 554 |
555 |
556 | liga: 557 | 558 |
559 |
560 |
561 |
562 | 563 | 564 | 565 | icon-language 566 |
567 |
568 | 569 | 570 |
571 |
572 | liga: 573 | 574 |
575 |
576 |
577 |
578 | 579 | 580 | 581 | icon-certificate 582 |
583 |
584 | 585 | 586 |
587 |
588 | liga: 589 | 590 |
591 |
592 |
593 |
594 | 595 | 596 | 597 | icon-work-show 598 |
599 |
600 | 601 | 602 |
603 |
604 | liga: 605 | 606 |
607 |
608 |
609 |
610 | 611 | 612 | 613 | icon-Shape-Copy-3 614 |
615 |
616 | 617 | 618 |
619 |
620 | liga: 621 | 622 |
623 |
624 |
625 |
626 | 627 | 628 | 629 | icon-finger-post 630 |
631 |
632 | 633 | 634 |
635 |
636 | liga: 637 | 638 |
639 |
640 |
641 |
642 | 643 | 644 | 645 | icon-file-signet 646 |
647 |
648 | 649 | 650 |
651 |
652 | liga: 653 | 654 |
655 |
656 |
657 |
658 | 659 | 660 | 661 | icon-paper-plane 662 |
663 |
664 | 665 | 666 |
667 |
668 | liga: 669 | 670 |
671 |
672 |
673 |
674 | 675 | 676 | 677 | icon-shield 678 |
679 |
680 | 681 | 682 |
683 |
684 | liga: 685 | 686 |
687 |
688 |
689 |
690 | 691 | 692 | 693 | icon-left 694 |
695 |
696 | 697 | 698 |
699 |
700 | liga: 701 | 702 |
703 |
704 |
705 |
706 | 707 | 708 | 709 | icon-search 710 |
711 |
712 | 713 | 714 |
715 |
716 | liga: 717 | 718 |
719 |
720 |
721 |
722 | 723 | 724 | 725 | icon-link 726 |
727 |
728 | 729 | 730 |
731 |
732 | liga: 733 | 734 |
735 |
736 |
737 |
738 | 739 | 740 | 741 | icon-gender 742 |
743 |
744 | 745 | 746 |
747 |
748 | liga: 749 | 750 |
751 |
752 |
753 |
754 | 755 | 756 | 757 | icon-download 758 |
759 |
760 | 761 | 762 |
763 |
764 | liga: 765 | 766 |
767 |
768 |
769 |
770 | 771 | 772 | 773 | icon-return 774 |
775 |
776 | 777 | 778 |
779 |
780 | liga: 781 | 782 |
783 |
784 |
785 |
786 | 787 | 788 | 789 | icon-addJob 790 |
791 |
792 | 793 | 794 |
795 |
796 | liga: 797 | 798 |
799 |
800 |
801 |
802 | 803 | 804 | 805 | icon-attachment 806 |
807 |
808 | 809 | 810 |
811 |
812 | liga: 813 | 814 |
815 |
816 |
817 |
818 | 819 | 820 | 821 | icon-Interview 822 |
823 |
824 | 825 | 826 |
827 |
828 | liga: 829 | 830 |
831 |
832 |
833 |
834 | 835 | 836 | 837 | icon-log 838 |
839 |
840 | 841 | 842 |
843 |
844 | liga: 845 | 846 |
847 |
848 |
849 |
850 | 851 | 852 | 853 | icon-remarks 854 |
855 |
856 | 857 | 858 |
859 |
860 | liga: 861 | 862 |
863 |
864 |
865 |
866 | 867 | 868 | 869 | icon-tel-off 870 |
871 |
872 | 873 | 874 |
875 |
876 | liga: 877 | 878 |
879 |
880 |
881 |
882 | 883 | 884 | 885 | icon-tel-on 886 |
887 |
888 | 889 | 890 |
891 |
892 | liga: 893 | 894 |
895 |
896 |
897 |
898 | 899 | 900 | 901 | icon-edit 902 |
903 |
904 | 905 | 906 |
907 |
908 | liga: 909 | 910 |
911 |
912 |
913 |
914 | 915 | 916 | 917 | icon-pencil 918 |
919 |
920 | 921 | 922 |
923 |
924 | liga: 925 | 926 |
927 |
928 |
929 |
930 | 931 | 932 | 933 | icon-arrow-down 934 |
935 |
936 | 937 | 938 |
939 |
940 | liga: 941 | 942 |
943 |
944 |
945 |
946 | 947 | 948 | 949 | icon-description 950 |
951 |
952 | 953 | 954 |
955 |
956 | liga: 957 | 958 |
959 |
960 |
961 |
962 | 963 | 964 | 965 | icon-lock 966 |
967 |
968 | 969 | 970 |
971 |
972 | liga: 973 | 974 |
975 |
976 |
977 |
978 | 979 | 980 | 981 | icon-Combined-Shape-Copy-2 982 |
983 |
984 | 985 | 986 |
987 |
988 | liga: 989 | 990 |
991 |
992 |
993 |
994 | 995 | 996 | 997 | icon-right 998 |
999 |
1000 | 1001 | 1002 |
1003 |
1004 | liga: 1005 | 1006 |
1007 |
1008 |
1009 |
1010 | 1011 | 1012 | 1013 | icon-location-fill 1014 |
1015 |
1016 | 1017 | 1018 |
1019 |
1020 | liga: 1021 | 1022 |
1023 |
1024 |
1025 |
1026 | 1027 | 1028 | 1029 | icon-education 1030 |
1031 |
1032 | 1033 | 1034 |
1035 |
1036 | liga: 1037 | 1038 |
1039 |
1040 |
1041 |
1042 | 1043 | 1044 | 1045 | icon-workYear 1046 |
1047 |
1048 | 1049 | 1050 |
1051 |
1052 | liga: 1053 | 1054 |
1055 |
1056 |
1057 |
1058 | 1059 | 1060 | 1061 | icon-close 1062 |
1063 |
1064 | 1065 | 1066 |
1067 |
1068 | liga: 1069 | 1070 |
1071 |
1072 |
1073 |
1074 | 1075 | 1076 | 1077 | icon-record 1078 |
1079 |
1080 | 1081 | 1082 |
1083 |
1084 | liga: 1085 | 1086 |
1087 |
1088 |
1089 |
1090 | 1091 | 1092 | 1093 | icon-close-o 1094 |
1095 |
1096 | 1097 | 1098 |
1099 |
1100 | liga: 1101 | 1102 |
1103 |
1104 |
1105 |
1106 | 1107 | 1108 | 1109 | icon-gift 1110 |
1111 |
1112 | 1113 | 1114 |
1115 |
1116 | liga: 1117 | 1118 |
1119 |
1120 |
1121 |
1122 | 1123 | 1124 | 1125 | icon-mobile 1126 |
1127 |
1128 | 1129 | 1130 |
1131 |
1132 | liga: 1133 | 1134 |
1135 |
1136 |
1137 |
1138 | 1139 | 1140 | 1141 | icon-notification 1142 |
1143 |
1144 | 1145 | 1146 |
1147 |
1148 | liga: 1149 | 1150 |
1151 |
1152 |
1153 |
1154 | 1155 | 1156 | 1157 | icon-checkmark 1158 |
1159 |
1160 | 1161 | 1162 |
1163 |
1164 | liga: 1165 | 1166 |
1167 |
1168 |
1169 |
1170 | 1171 | 1172 | 1173 | icon-info-new 1174 |
1175 |
1176 | 1177 | 1178 |
1179 |
1180 | liga: 1181 | 1182 |
1183 |
1184 |
1185 | 1186 | 1187 |
1188 |

Font Test Drive

1189 | 1194 | 1196 |
  1197 |
1198 |
1199 | 1200 |
1201 |

Generated by IcoMoon

1202 |
1203 | 1204 | 1205 | 1206 | 1207 | -------------------------------------------------------------------------------- /icomoon/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | --------------------------------------------------------------------------------