├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── actions
│ └── index.js
├── utils
│ ├── useConnect.js
│ ├── baseUrl.js
│ ├── loadable.js
│ ├── connect.js
│ ├── renderRoutes.js
│ ├── asyncComponent.js
│ ├── recursion-router.js
│ ├── request.js
│ └── index.js
├── stylus
│ ├── index.less
│ ├── common.less
│ ├── loading.less
│ └── reset.less
├── store
│ ├── actionTypes.js
│ ├── index.js
│ ├── actionCreator.js
│ └── reducer.js
├── pages
│ ├── Home
│ │ ├── index.less
│ │ ├── children
│ │ │ ├── One.js
│ │ │ ├── Two.js
│ │ │ └── Three.js
│ │ └── index.js
│ ├── User
│ │ ├── Home
│ │ │ └── index.js
│ │ ├── Goods
│ │ │ ├── GoodsList
│ │ │ │ └── index.js
│ │ │ └── GoodsClassify
│ │ │ │ └── index.js
│ │ ├── Permission
│ │ │ ├── RoleManage
│ │ │ │ └── index.js
│ │ │ ├── UserManage
│ │ │ │ └── index.js
│ │ │ └── MenuManage
│ │ │ │ └── index.js
│ │ ├── OrderManage
│ │ │ ├── ReturnGoods
│ │ │ │ └── index.js
│ │ │ ├── ProductManage
│ │ │ │ ├── ReviewManage
│ │ │ │ │ └── index.js
│ │ │ │ └── ProductionList
│ │ │ │ │ └── index.js
│ │ │ └── OrderList
│ │ │ │ └── index.js
│ │ ├── index.less
│ │ └── index.js
│ ├── NotFound
│ │ ├── index.less
│ │ └── index.js
│ └── Login
│ │ ├── service.js
│ │ ├── index.less
│ │ └── index.js
├── App.test.js
├── index.css
├── setupProxy.js
├── App.js
├── index.js
├── common
│ ├── SecondLevelComponent.js
│ ├── ThirdLevelComponent.js
│ ├── Etable
│ │ └── index.js
│ └── BaseForm
│ │ └── index.js
├── config
│ └── menuConfig.js
├── serviceWorker.js
└── router.js
├── config
├── jest
│ ├── cssTransform.js
│ └── fileTransform.js
├── pnpTs.js
├── webpack.dll.conf.js
├── modules.js
├── paths.js
├── env.js
├── webpackDevServer.config.js
└── webpack.config.js
├── .gitignore
├── README.md
├── scripts
├── test.js
├── start.js
└── build.js
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/loveRandy/react-admin/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as all from '../store/actionCreator'
2 |
3 | export default all
--------------------------------------------------------------------------------
/src/utils/useConnect.js:
--------------------------------------------------------------------------------
1 | // import React from 'react'
2 | // import conncet from './connect'
3 |
4 | // export default @connect
--------------------------------------------------------------------------------
/src/stylus/index.less:
--------------------------------------------------------------------------------
1 | //重置样式
2 | @import './reset.less';
3 | //公用样式
4 | @import './common.less';
5 | //loading样式
6 | @import './loading.less';
--------------------------------------------------------------------------------
/src/store/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const AUTH_CHANGE = 'auth_change';
2 | export const PERMISSION_CHANGE = 'permission_change';
3 | export const CURRENT_CHANGE = 'current_change';
--------------------------------------------------------------------------------
/src/pages/Home/index.less:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 32px;
3 | background: rgba(255, 255, 255, 0.2);
4 | margin: 16px;
5 | }
6 | .ant-breadcrumb > span:last-child a{
7 | color:#000;
8 | }
--------------------------------------------------------------------------------
/src/stylus/common.less:
--------------------------------------------------------------------------------
1 | .display_flex{
2 | display:flex;
3 | }
4 | .flex_one{
5 | flex:1;
6 | }
7 | .flex_center{
8 | display:flex;
9 | justify-content: center;
10 | align-items: center;
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/Home/children/One.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class One extends React.Component{
4 |
5 | render(){
6 | return (
7 |
one
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/Home/children/Two.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Two extends React.Component{
4 |
5 | render(){
6 | return (
7 | Two
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/Home/children/Three.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Three extends React.Component{
4 |
5 | render(){
6 | return (
7 | Three
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/User/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | class Home extends React.Component{
4 |
5 | render(){
6 | return (
7 | 首页
8 | )
9 | }
10 | }
11 | export default Home
--------------------------------------------------------------------------------
/src/pages/User/Goods/GoodsList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class GoodsList extends React.Component{
4 |
5 | render(){
6 | return (
7 | GoodsList
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/User/Permission/RoleManage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class RoleManage extends React.Component{
4 |
5 | render(){
6 | return (
7 | RoleManage
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/User/Goods/GoodsClassify/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class GoodsClassify extends React.Component{
4 |
5 | render(){
6 | return (
7 | GoodsClassify
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/User/OrderManage/ReturnGoods/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class ReturnGoods extends React.Component{
4 |
5 | render(){
6 | return (
7 | ReturnGoods
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/NotFound/index.less:
--------------------------------------------------------------------------------
1 | .loading {
2 | display: flex;
3 | justify-content: center;
4 | /*x轴对齐方式*/
5 | align-items: center;
6 | /*y轴对滴方式*/
7 | height: calc(100vh - 250px);
8 | font-size:40px;
9 | /**屏幕高度百分百*/
10 |
11 | }
--------------------------------------------------------------------------------
/src/pages/User/OrderManage/ProductManage/ReviewManage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class ReviewManage extends React.Component{
4 |
5 | render(){
6 | return (
7 | ReviewManage
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Redirect } from 'react-router-dom'
3 |
4 | export default class Home extends React.Component {
5 | render(){
6 | return (
7 |
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/pages/User/OrderManage/ProductManage/ProductionList/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class ProductionList extends React.Component{
4 |
5 | render(){
6 | return (
7 | ProductionList
8 | )
9 | }
10 | }
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/utils/baseUrl.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const proxyTargetMap = {
4 | prod: '/',
5 | randy: '/randy',
6 | peter: '/peter'
7 | }
8 | const API_TYPE = process.env.API_TYPE?process.env.API_TYPE:'randy'
9 | const baseUrl = process.env.NODE_ENV === 'production' ? '/' : proxyTargetMap[API_TYPE]
10 | export default baseUrl
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/config/jest/cssTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // This is a custom Jest transformer turning style imports into empty objects.
4 | // http://facebook.github.io/jest/docs/en/webpack.html
5 |
6 | module.exports = {
7 | process() {
8 | return 'module.exports = {};';
9 | },
10 | getCacheKey() {
11 | // The output is always the same.
12 | return 'cssTransform';
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/src/utils/loadable.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Loadable from 'react-loadable';
3 |
4 | //通用的过场组件
5 | const loadingComponent =()=>{
6 | return (
7 | loading
8 | )
9 | }
10 |
11 | //过场组件默认采用通用的,若传入了loading,则采用传入的过场组件
12 | export default (loader,loading = loadingComponent)=>{
13 | return Loadable({
14 | loader,
15 | loading
16 | });
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
--------------------------------------------------------------------------------
/src/pages/Login/service.js:
--------------------------------------------------------------------------------
1 | import request from '../../utils/request'
2 | // 登陆
3 | export function login( data) {
4 | return request({
5 | url: '/user/login',
6 | method: 'post',
7 | data,
8 | })
9 | }
10 | // 2.获取商户支持的支付方式
11 | export function queryByOwenerIdAndOwnerType( params) {
12 | return request({
13 | url: '/api/productSubscribe/queryByOwenerIdAndOwnerType',
14 | method: 'get',
15 | params,
16 | })
17 | }
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore,applyMiddleware ,compose} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import reducer from './reducer';
4 |
5 | const composeEnhancers =window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
6 | const enhancer = composeEnhancers(
7 | applyMiddleware(thunk)
8 | );
9 |
10 | const store = createStore(reducer, enhancer);
11 | export default store;
--------------------------------------------------------------------------------
/src/pages/User/index.less:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 32px;
3 | background: rgba(255, 255, 255, 0.2);
4 | margin: 16px;
5 | }
6 | .ant-breadcrumb > span:last-child a{
7 | color:#000;
8 | }
9 | .logoutIcon{
10 | float: right;
11 | margin-right: 20px;
12 | font-size: 24px;
13 | color: #bbb;
14 | cursor: pointer;
15 | }
16 | .loginUser{
17 | float: right;
18 | margin-right: 10px;
19 | font-size: 16px;
20 | color: #1890ff;
21 | }
--------------------------------------------------------------------------------
/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const proxy = require('http-proxy-middleware')
2 | module.exports = function (app) {
3 | // ...You can now register proxies as you wish!
4 | app.use(proxy('/randy', {
5 | target: 'http://47.105.71.81:3306',
6 | secure: false,
7 | changeOrigin: true,
8 | pathRewrite: {
9 | "^/randy": ""
10 | },
11 | }));
12 | app.use(proxy('/peter', {
13 | target: 'http://172.19.5.34:9531',
14 | secure: false,
15 | changeOrigin: true,
16 | pathRewrite: {
17 | "^/peter": ""
18 | },
19 | }));
20 | };
--------------------------------------------------------------------------------
/src/utils/connect.js:
--------------------------------------------------------------------------------
1 | // import actions from '../actions'
2 | import * as action from '../store/actionCreator'
3 | import { connect } from 'react-redux'
4 | // import { bindActionCreators } from 'redux'
5 | export default connect(
6 | // state => ({state}),
7 | state => ({
8 | state
9 | }),
10 |
11 |
12 |
13 | // dispatch => {
14 | // return bindActionCreators({
15 | // action,
16 | // dispatch
17 | // })
18 |
19 | // }
20 | dispatch =>{
21 | return {
22 | ...action,
23 | dispatch
24 | }
25 | }
26 |
27 | )
28 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import renderRoutes from './utils/renderRoutes'
3 | import routes from "./router.js";
4 | import { HashRouter as Router, Switch } from "react-router-dom";
5 | import connect from './utils/connect'
6 |
7 | @connect
8 | class App extends React.Component{
9 | render(){
10 | const authPath = '/login' // 默认未登录的时候返回的页面,可以自行设置
11 | let authed = this.props.state.authed || localStorage.getItem('authed') // 如果登陆之后可以利用redux修改该值
12 | return (
13 |
14 |
15 | {renderRoutes(routes, authed, authPath)}
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | export default App
23 |
--------------------------------------------------------------------------------
/config/pnpTs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { resolveModuleName } = require('ts-pnp');
4 |
5 | exports.resolveModuleName = (
6 | typescript,
7 | moduleName,
8 | containingFile,
9 | compilerOptions,
10 | resolutionHost
11 | ) => {
12 | return resolveModuleName(
13 | moduleName,
14 | containingFile,
15 | compilerOptions,
16 | resolutionHost,
17 | typescript.resolveModuleName
18 | );
19 | };
20 |
21 | exports.resolveTypeReferenceDirective = (
22 | typescript,
23 | moduleName,
24 | containingFile,
25 | compilerOptions,
26 | resolutionHost
27 | ) => {
28 | return resolveModuleName(
29 | moduleName,
30 | containingFile,
31 | compilerOptions,
32 | resolutionHost,
33 | typescript.resolveTypeReferenceDirective
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/src/utils/renderRoutes.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Redirect, Switch } from 'react-router-dom'
3 | const renderRoutes = (routes, authed, authPath = '/login', extraProps = {}, switchProps = {}) => routes ? (
4 |
5 | {routes.map((route, i) => (
6 | {
12 | //如果是不需要权限 或者 已登录 或者 访问路径是/login,则直接返回当前组件
13 | if (!route.requiresAuth || authed || route.path === authPath) {
14 | return
15 | }
16 | //否则重定向到/login
17 | return
18 | }}
19 | />
20 | ))}
21 |
22 | ) : null
23 |
24 | export default renderRoutes
--------------------------------------------------------------------------------
/src/stylus/loading.less:
--------------------------------------------------------------------------------
1 | .ajax-loading {
2 | display: none;
3 | .loading {
4 | position: fixed;
5 | top: 50%;
6 | left: 50%;
7 | transform: translate(-50%, -50%);
8 | padding: 0 40px;
9 | height: 80px;
10 | line-height: 80px;
11 | background: rgba(0, 0, 0, 0.75);
12 | border-radius: 6px;
13 | text-align: center;
14 | z-index: 9999;
15 | font-size: 16px;
16 | color: #fff;
17 | img {
18 | width: 32px;
19 | vertical-align: middle;
20 | }
21 | span {
22 | margin-left: 12px;
23 | }
24 | }
25 | .overlay {
26 | position: fixed;
27 | left: 0;
28 | right: 0;
29 | top: 0;
30 | bottom: 0;
31 | z-index: 9998;
32 | background: rgb(255, 255, 255);
33 | opacity: 0.1;
34 | }
35 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | // import './index.css';
5 |
6 | import './stylus/index.less'
7 | import App from './App';
8 | import * as serviceWorker from './serviceWorker';
9 | import { Provider } from 'react-redux'
10 | import store from './store'
11 | import {HashRouter, Route} from 'react-router-dom'
12 |
13 | const Main = () =>{
14 | return (
15 |
16 |
17 |
18 |
19 |
20 | )
21 | }
22 |
23 | ReactDOM.render(, document.getElementById('root'));
24 |
25 | // If you want your app to work offline and load faster, you can change
26 | // unregister() to register() below. Note this comes with some pitfalls.
27 | // Learn more about service workers: https://bit.ly/CRA-PWA
28 | serviceWorker.unregister();
29 |
--------------------------------------------------------------------------------
/src/pages/NotFound/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Spin } from 'antd'
3 | import './index.less'
4 |
5 | export default class NotFound extends React.Component {
6 | state= {
7 | status:true
8 | }
9 |
10 | componentDidMount(){
11 | setTimeout(()=>{
12 | this.setState({
13 | status:false
14 | })
15 | },1000)
16 | }
17 |
18 | componentWillUnmount(){
19 | // 卸载异步操作设置状态
20 | this.setState = (state, callback) => {
21 | return;
22 | }
23 | }
24 |
25 | render(){
26 | if(this.state.status){
27 | return (
28 |
29 |
30 |
31 | )
32 | }
33 | return (
34 |
35 | NotFound 404
36 |
37 | )
38 | }
39 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 线上预览地址
2 | ### vue版本请移步
3 |
4 | #### 项目简介
5 | 根据不同的的角色展示不同的列表,项目里涵盖了后台管理系统里90%的需求,没有多余的东西,如果有自己的需求单独加上即可,如富文本,拖拽网上都有现成的
6 | #### 脚手架版本:
7 | *create-react-app@2
8 | 这个版本用webpack.config.js替换了webpack.config.prod.js和webpack.config.dev.js
9 |
10 | #### 用到react相关的生态链模块:
11 | * `react`
12 | * `react-dom`
13 | * `react-router-dom`
14 | * `redux`:
15 | * `react-redux`
16 | * `redux-actions`
17 | * `redux-thunk`
18 | * `antd`
19 |
20 | #### 项目要点
21 | * `less配置、antd按需加载`
22 | * `路由懒加载`
23 | * `根据权限生成动态路由`
24 | * `使用connect简化redux使用`
25 | * `全局数据请求拦截处理及loading`
26 | * `多个代理配置`
27 | * `常用表单封装、tabel封装`
28 | * `抽离第三方库文件dll`
29 |
30 | ### 项目启动步骤
31 | 1. 安装包
32 | cnpm/npm install
33 | 2. 开发运行
34 | npm run start
35 | 3. 生产打包
36 | npm run dll (仅需运行一次)
37 | npm run build
38 |
39 |
40 | ### 个人博客系统:www.randy168.com
41 |
--------------------------------------------------------------------------------
/src/utils/asyncComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Spin } from "antd";
3 |
4 | const asyncComponent = importComponent => {
5 | return class extends Component {
6 | constructor() {
7 | super();
8 | this.state = {
9 | component: null
10 | };
11 | }
12 | componentDidMount() {
13 | importComponent().then(cmp => {
14 | this.setState({ component: cmp.default });
15 | });
16 | }
17 | render() {
18 | const styleObj = {
19 | display: "flex",
20 | justifyContent: "center",
21 | alignItems: "center",
22 | height: "100vh",
23 | fontSize: "40px"
24 | };
25 | const C = this.state.component;
26 | return C ? (
27 |
28 | ) : (
29 |
30 |
31 |
32 | );
33 | }
34 | };
35 | };
36 |
37 | export default asyncComponent;
38 |
--------------------------------------------------------------------------------
/src/stylus/reset.less:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | /* HTML5 display-role reset for older browsers */
22 | article, aside, details, figcaption, figure,
23 | footer, header, hgroup, menu, nav, section {
24 | display: block;
25 | }
26 | body {
27 | line-height: 1;
28 | }
29 | ol, ul {
30 | list-style: none;
31 | }
32 | blockquote, q {
33 | quotes: none;
34 | }
35 | blockquote:before, blockquote:after,
36 | q:before, q:after {
37 | content: '';
38 | content: none;
39 | }
40 | table {
41 | border-collapse: collapse;
42 | border-spacing: 0;
43 | }
--------------------------------------------------------------------------------
/src/pages/Login/index.less:
--------------------------------------------------------------------------------
1 | .wrapper_login{
2 | width:100%;
3 | height:100vh;
4 | background:#2d3a4b;
5 | .login-form-login {
6 | width: 400px;
7 | .login-title{
8 | font-size: 26px;
9 | color: #eee;
10 | margin: 0 auto 40px auto;
11 | text-align: center;
12 | font-weight: 700;
13 | }
14 | .login-form-button {
15 | width: 100%;
16 | height:40px;
17 | }
18 | #normal_login_username{
19 | background: transparent;
20 | border: 1px solid hsla(0,0%,100%,.1);
21 | color: #fff;
22 | }
23 | #normal_login_password{
24 | background: transparent;
25 | border: 1px solid hsla(0,0%,100%,.1);
26 | color: #fff;
27 | }
28 | #normal_login_password::placeholder{
29 | color:#889aa4;
30 | }
31 | #normal_login_username::placeholder{
32 | color:#889aa4;
33 | }
34 | .ant-input-affix-wrapper{
35 | height:46px;
36 | }
37 | i{
38 | color: #889aa4;
39 | }
40 | .loginTip{
41 | color:#fff;
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/config/webpack.dll.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const {CleanWebpackPlugin} = require('clean-webpack-plugin')
4 | // dll文件存放的目录
5 | const dllPath = '../public/vendor'
6 |
7 | module.exports = {
8 | entry: {
9 | // 需要提取的库文件
10 | vendor: ['react', 'react-redux', 'redux', 'react-router-dom', 'redux-actions','redux-thunk','axios','antd']
11 | },
12 | output: {
13 | path: path.join(__dirname, dllPath),
14 | // 保证每次打包出来的文件名都是唯一的
15 | filename: `[name].dll.${Math.ceil(Math.random() * 10000)}.js`,
16 | // vendor.dll.js中暴露出的全局变量名
17 | // 保持与 webpack.DllPlugin 中名称一致
18 | library: '[name]_[hash]'
19 | },
20 | plugins: [
21 | // 清除之前的dll文件
22 | new CleanWebpackPlugin({
23 | root: path.join(__dirname, dllPath)
24 | }),
25 | // 设置环境变量
26 | new webpack.DefinePlugin({
27 | 'process.env': {
28 | NODE_ENV: 'production'
29 | }
30 | }),
31 | // manifest.json 描述动态链接库包含了哪些内容
32 | new webpack.DllPlugin({
33 | path: path.join(__dirname, dllPath, '[name]-manifest.json'),
34 | // 保持与 output.library 中名称一致
35 | name: '[name]_[hash]',
36 | context: process.cwd()
37 | })
38 | ]
39 | }
--------------------------------------------------------------------------------
/config/jest/fileTransform.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const camelcase = require('camelcase');
5 |
6 | // This is a custom Jest transformer turning file imports into filenames.
7 | // http://facebook.github.io/jest/docs/en/webpack.html
8 |
9 | module.exports = {
10 | process(src, filename) {
11 | const assetFilename = JSON.stringify(path.basename(filename));
12 |
13 | if (filename.match(/\.svg$/)) {
14 | // Based on how SVGR generates a component name:
15 | // https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6
16 | const pascalCaseFileName = camelcase(path.parse(filename).name, {
17 | pascalCase: true,
18 | });
19 | const componentName = `Svg${pascalCaseFileName}`;
20 | return `const React = require('react');
21 | module.exports = {
22 | __esModule: true,
23 | default: ${assetFilename},
24 | ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
25 | return {
26 | $$typeof: Symbol.for('react.element'),
27 | type: 'svg',
28 | ref: ref,
29 | key: null,
30 | props: Object.assign({}, props, {
31 | children: ${assetFilename}
32 | })
33 | };
34 | }),
35 | };`;
36 | }
37 |
38 | return `module.exports = ${assetFilename};`;
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/src/utils/recursion-router.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @param {Array} userRouter 后台返回的用户权限json
4 | * @param {Array} allRouter 前端配置好的所有动态路由的集合
5 | * @return {Array} realRoutes 过滤后的路由
6 | */
7 |
8 | //根据后台数据筛选出路由表
9 | export function recursionRouter(userRouter = [], allRouter = []) {
10 | const realRoutes = allRouter
11 | .filter(item => userRouter.includes(item.pathName))
12 | .map(item => ({
13 | ...item,
14 | children: item.children
15 | ? recursionRouter(userRouter, item.children)
16 | : null
17 | }))
18 | return realRoutes
19 | }
20 |
21 | //重定向到children的第一个路由
22 | export function recursionRouterTwo(userRouter = [], allRouter = []) {
23 | const realRoutes = allRouter
24 | .filter(item => userRouter.includes(item.path))
25 | .map(item =>{
26 | return {
27 | ...item,
28 | redirect:item.children?item.children[0].path:null,
29 | children: item.children
30 | ? recursionRouterTwo(userRouter, item.children)
31 | : null
32 | }
33 | })
34 | return realRoutes
35 |
36 | }
37 |
38 | export function recursionRouterThree(userRouter = [], allRouter = []) {
39 | let list = []
40 | allRouter.forEach((item,index) =>{
41 | if(item.path === userRouter[0]){
42 | list.push(item)
43 | }
44 | })
45 | return list
46 |
47 |
48 |
49 | }
--------------------------------------------------------------------------------
/src/common/SecondLevelComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Switch } from "react-router-dom";
3 | import NotFound from '../pages/NotFound'
4 | import connect from '../utils/connect'
5 | @connect
6 | class SecondLevelComponent extends React.Component{
7 |
8 | render(){
9 |
10 | const permissionList = this.props.state.permissionList
11 | const path = this.props.match.path
12 | const currentList = permissionList.filter(item =>{
13 | return item.path === path
14 | })
15 | let list = []
16 | if( currentList.length>0 && currentList[0].children ){
17 | list = currentList[0].children.map((item,index) => {
18 | return (
19 |
26 | )
27 | })
28 | return (
29 |
30 | {
31 | list
32 | }
33 |
34 |
35 | )
36 | }else{
37 | return (
38 | ...
39 | )
40 | }
41 |
42 |
43 | }
44 | }
45 | export default SecondLevelComponent
--------------------------------------------------------------------------------
/scripts/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Do this as the first thing so that any code reading it knows the right env.
4 | process.env.BABEL_ENV = 'test';
5 | process.env.NODE_ENV = 'test';
6 | process.env.PUBLIC_URL = '';
7 |
8 | // Makes the script crash on unhandled rejections instead of silently
9 | // ignoring them. In the future, promise rejections that are not handled will
10 | // terminate the Node.js process with a non-zero exit code.
11 | process.on('unhandledRejection', err => {
12 | throw err;
13 | });
14 |
15 | // Ensure environment variables are read.
16 | require('../config/env');
17 |
18 |
19 | const jest = require('jest');
20 | const execSync = require('child_process').execSync;
21 | let argv = process.argv.slice(2);
22 |
23 | function isInGitRepository() {
24 | try {
25 | execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
26 | return true;
27 | } catch (e) {
28 | return false;
29 | }
30 | }
31 |
32 | function isInMercurialRepository() {
33 | try {
34 | execSync('hg --cwd . root', { stdio: 'ignore' });
35 | return true;
36 | } catch (e) {
37 | return false;
38 | }
39 | }
40 |
41 | // Watch unless on CI or explicitly running all tests
42 | if (
43 | !process.env.CI &&
44 | argv.indexOf('--watchAll') === -1
45 | ) {
46 | // https://github.com/facebook/create-react-app/issues/5210
47 | const hasSourceControl = isInGitRepository() || isInMercurialRepository();
48 | argv.push(hasSourceControl ? '--watch' : '--watchAll');
49 | }
50 |
51 |
52 | jest.run(argv);
53 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | randy
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |

31 |
加载中,请稍后...
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/common/ThirdLevelComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, Switch } from "react-router-dom";
3 | import NotFound from '../pages/NotFound'
4 | import connect from '../utils/connect'
5 | @connect
6 |
7 | class ThirdLevelComponent extends React.Component{
8 |
9 | render(){
10 | const permissionList = this.props.state.permissionList
11 | const path = this.props.match.path
12 | const item = permissionList.find(item =>{
13 | return path.indexOf(item.path) !== -1
14 | })
15 | const currentList = item.children.filter(item =>{
16 | return item.path === path
17 | })
18 | let list = []
19 | if( currentList.length>0 && currentList[0].children ){
20 | list = currentList[0].children.map((item,index) => {
21 | return (
22 |
29 | )
30 | })
31 | return (
32 |
33 |
34 | {
35 | list
36 | }
37 |
38 |
39 |
40 |
41 | )
42 | }else{
43 | return (
44 | ...
45 | )
46 | }
47 | }
48 | }
49 | export default ThirdLevelComponent
--------------------------------------------------------------------------------
/src/config/menuConfig.js:
--------------------------------------------------------------------------------
1 | const menuList = [
2 | // {
3 | // title: '首页',
4 | // key: '/home'
5 | // },
6 | {
7 | title: '订单管理',
8 | key: '/user/order',
9 | children: [
10 | {
11 | title: '订单列表',
12 | key: '/user/order/list',
13 | },
14 | {
15 | title: '退货管理',
16 | key: '/user/order/returnGoods',
17 | },
18 | {
19 | title: '生产管理',
20 | key: '/user/order/product',
21 | children:[
22 | {
23 | title: '生产列表',
24 | key: '/user/order/product/list',
25 | },
26 | {
27 | title: '审核管理',
28 | key: '/user/order/product/review',
29 | }
30 | ]
31 | }
32 | ]
33 | },
34 | {
35 | title: '产品管理',
36 | key: '/user/goods',
37 | children:[
38 | {
39 | title: '产品列表',
40 | key: '/user/goods/list',
41 | },
42 | {
43 | title: '产品分类',
44 | key: '/user/goods/classify',
45 | }
46 | ]
47 |
48 | },
49 | {
50 | title: '权限管理',
51 | key: '/user/permission',
52 | children:[
53 | {
54 | title: '用户管理',
55 | key: '/user/permission/user',
56 | },
57 | {
58 | title: '角色管理',
59 | key: '/user/permission/role',
60 | },
61 | {
62 | title: '菜单管理',
63 | key: '/user/permission/menu',
64 | }
65 | ]
66 | }
67 | ];
68 | export default menuList;
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import baseURL from './baseUrl'
3 | import { getLocal } from '../utils'
4 | import { authChangeAction } from '../store/actionCreator'
5 | import store from '../store'
6 |
7 | //创建axios实例
8 | const service = axios.create({
9 | baseURL: baseURL, // api的base_url
10 | timeout: 200000, // 请求超时时间
11 | withCredentials: true // 选项表明了是否是跨域请求
12 | })
13 | service.interceptors.request.use(config => {
14 | // 请求头添加token
15 | if (getLocal('authed')) {
16 | config.headers.Authorization = `Bearer ${getLocal('authed')}`
17 | }
18 | const flag = (config.data && config.data.loading !==false) || (config.params && config.params.loading !== false)
19 | if(flag){
20 | let loading
21 | loading = document.getElementById('ajaxLoading')
22 | loading.style.display = 'block'
23 | }
24 | return config;
25 | }, err => {
26 | console.log('请求失败')
27 | return Promise.reject(err)
28 | })
29 |
30 |
31 |
32 | //拦截响应
33 | service.interceptors.response.use(config => {
34 | if(config.data && config.data.loading !==false){
35 | let loading
36 | loading = document.getElementById('ajaxLoading')
37 | loading.style.display = 'none'
38 | }
39 | return config;
40 | }, err => {
41 | console.log('响应失败')
42 | return Promise.reject(err)
43 | })
44 |
45 |
46 |
47 | // respone拦截器
48 | service.interceptors.response.use(
49 | response => {
50 | const res = response.data
51 | if (res.code !== 1) {
52 | res.code = res.data.code
53 | res.message = res.response.data.msg
54 | return Promise.reject('error')
55 | } else {
56 | return response.data
57 | }
58 | },
59 | error => {
60 | const { status } = error.response
61 | switch (status) {
62 | case 401:
63 | store.dispatch(authChangeAction(null))
64 | break;
65 |
66 | default:
67 | break;
68 | }
69 | return Promise.reject(error)
70 | }
71 | )
72 | export default service
73 |
--------------------------------------------------------------------------------
/src/store/actionCreator.js:
--------------------------------------------------------------------------------
1 | import { AUTH_CHANGE,PERMISSION_CHANGE, CURRENT_CHANGE } from './actionTypes'
2 | import { createActions } from 'redux-actions';
3 | import { recursionRouter } from '../utils/recursion-router'
4 | import routes from '../router'
5 | import request from '../utils/request'
6 | import { filterRoutes } from '../utils'
7 | import { recursionRouterThree } from '../utils/recursion-router'
8 |
9 | // export const doAuthChangeAction = (res) => {
10 | // return {
11 | // type:AUTH_CHANGE,
12 | // authStatus:res
13 | // }
14 | // }
15 | export const doAuthChangeAction = createActions(
16 | {
17 | [AUTH_CHANGE]:(res) => {
18 | return {
19 | authStatus:res
20 | }
21 | },
22 | [PERMISSION_CHANGE]:(permissionList,currentList,avatar,name) => {
23 | return {
24 | permissionList,
25 | currentList,
26 | avatar,
27 | name
28 | }
29 | },
30 | [CURRENT_CHANGE]:(list) => {
31 | return {
32 | currentList:list
33 | }
34 | }
35 | }
36 | )
37 |
38 | export const authChangeAction = (token) =>{
39 | return (dispatch) =>{
40 | const action = doAuthChangeAction.authChange(token)
41 | dispatch(action)
42 | }
43 | }
44 |
45 | export const permissionAction = (path) =>{
46 | return (dispatch) =>{
47 | request({
48 | url: '/user/info',
49 | method: 'get',
50 | })
51 | .then(res =>{
52 | const allList = routes[2].children
53 | res.data.data.push('index')//把首页丢进去
54 | const permissionList = recursionRouter(res.data.data,allList)
55 |
56 | const defaultOpenKeys = filterRoutes(path)
57 | const currentList = recursionRouterThree(defaultOpenKeys,permissionList)
58 | const action = doAuthChangeAction.permissionChange(permissionList,currentList,res.data.avatar,res.data.name)
59 | dispatch(action)
60 |
61 |
62 | })
63 | }
64 | }
65 |
66 | export const currentAction = (list) =>{
67 | return (dispatch) =>{
68 | const action = doAuthChangeAction.currentChange(list)
69 | dispatch(action)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { AUTH_CHANGE, PERMISSION_CHANGE,CURRENT_CHANGE } from './actionTypes'
2 | import { handleActions } from 'redux-actions'
3 | import Index from '../pages/User/Home'
4 | const defaultState = {
5 | authed: false,
6 | permissionList:[
7 | {
8 | path: '/user/index',
9 | pathName:'index',
10 | name:'首页',
11 | component:Index,
12 | icon:'pie-chart'
13 | }
14 | ],
15 | currentList:[],
16 | avatar:'',
17 | name:''
18 | };
19 |
20 | export const statusReducer = handleActions(
21 | {
22 | [AUTH_CHANGE]:(state, action)=> {
23 | const newState = JSON.parse(JSON.stringify(state))
24 | newState.authed = action.payload.authStatus
25 | if(newState.authed !== null){
26 | localStorage.setItem('authed',newState.authed)
27 | }else{
28 | newState.permissionList = [
29 | {
30 | path: '/user/index',
31 | pathName:'index',
32 | name:'首页',
33 | component:Index,
34 | icon:'pie-chart'
35 | }
36 | ]
37 | localStorage.removeItem('authed')
38 | }
39 |
40 | return newState;
41 | },
42 | [PERMISSION_CHANGE]:(state, action)=> {
43 | const newState = JSON.parse(JSON.stringify(state))
44 | newState.permissionList = action.payload.permissionList
45 | newState.currentList = action.payload.currentList
46 | newState.avatar = action.payload.avatar
47 | newState.name = action.payload.name
48 | return newState;
49 | },
50 | [CURRENT_CHANGE]:(state, action)=> {
51 | const newState = JSON.parse(JSON.stringify(state))
52 | newState.currentList = action.payload.currentList
53 | return newState;
54 | }
55 |
56 | },defaultState)
57 |
58 | export default statusReducer
59 |
60 | // export default (state = defaultState, action) => {
61 | // if(action.type === AUTH_CHANGE){
62 | // const newState = JSON.parse(JSON.stringify(state))
63 | // newState.authed = action.authStatus
64 | // return newState;
65 | // }
66 |
67 | // return state;
68 | // };
69 |
--------------------------------------------------------------------------------
/src/common/Etable/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Table } from 'antd'
3 | class Etable extends React.Component {
4 |
5 | onSelectChange = (selectedRowKeys,selectedRows)=> {
6 | this.props.updateSelectedItem(selectedRowKeys,selectedRows,this.props.that)
7 | }
8 | onRowClick = (index,record)=>{
9 | /**
10 | * @param {* 勾选的类型} type radio是单选
11 | */
12 | const type = this.props.type
13 | if(type=== 'radio'){
14 | this.onSelectChange([index],[record])
15 | }else{
16 | let selectedRowKeys = [...this.props.rowSelection.selectedRowKeys]
17 | let selectedRows = [...this.props.rowSelection.selectedRows]
18 | if(selectedRowKeys.includes(index)){
19 | var selectIndex = selectedRowKeys.findIndex(item=>{
20 | return index===item
21 | })
22 | selectedRowKeys.splice(selectIndex,1)
23 | selectedRows.splice(selectIndex,1)
24 | }else{
25 | selectedRowKeys.push(index)
26 | selectedRows.push(record)
27 | }
28 | this.onSelectChange(selectedRowKeys,selectedRows)
29 | }
30 |
31 |
32 | }
33 | render(){
34 |
35 | //是否分页
36 | let pagination = this.props.pagination
37 | if(!pagination || pagination === false){
38 | pagination = false
39 | }
40 |
41 | //是否需要勾选
42 | let rowSelection = this.props.rowSelection
43 | rowSelection.type = this.props.type==='radio'?'radio':'checkbox'
44 | let onRow
45 | if(!rowSelection){
46 | rowSelection = null
47 | }else{
48 | rowSelection.onChange = this.onSelectChange;
49 | onRow = (record,index) =>{
50 | return {
51 | onClick:()=>{
52 | this.onRowClick(index,record)
53 | }
54 | }
55 | }
56 | }
57 |
58 |
59 | return (
60 |
67 | )
68 | }
69 |
70 | }
71 |
72 | export default Etable
--------------------------------------------------------------------------------
/config/modules.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const paths = require('./paths');
6 | const chalk = require('react-dev-utils/chalk');
7 |
8 | /**
9 | * Get the baseUrl of a compilerOptions object.
10 | *
11 | * @param {Object} options
12 | */
13 | function getAdditionalModulePaths(options = {}) {
14 | const baseUrl = options.baseUrl;
15 |
16 | // We need to explicitly check for null and undefined (and not a falsy value) because
17 | // TypeScript treats an empty string as `.`.
18 | if (baseUrl == null) {
19 | // If there's no baseUrl set we respect NODE_PATH
20 | // Note that NODE_PATH is deprecated and will be removed
21 | // in the next major release of create-react-app.
22 |
23 | const nodePath = process.env.NODE_PATH || '';
24 | return nodePath.split(path.delimiter).filter(Boolean);
25 | }
26 |
27 | const baseUrlResolved = path.resolve(paths.appPath, baseUrl);
28 |
29 | // We don't need to do anything if `baseUrl` is set to `node_modules`. This is
30 | // the default behavior.
31 | if (path.relative(paths.appNodeModules, baseUrlResolved) === '') {
32 | return null;
33 | }
34 |
35 | // Allow the user set the `baseUrl` to `appSrc`.
36 | if (path.relative(paths.appSrc, baseUrlResolved) === '') {
37 | return [paths.appSrc];
38 | }
39 |
40 | // Otherwise, throw an error.
41 | throw new Error(
42 | chalk.red.bold(
43 | "Your project's `baseUrl` can only be set to `src` or `node_modules`." +
44 | ' Create React App does not support other values at this time.'
45 | )
46 | );
47 | }
48 |
49 | function getModules() {
50 | // Check if TypeScript is setup
51 | const hasTsConfig = fs.existsSync(paths.appTsConfig);
52 | const hasJsConfig = fs.existsSync(paths.appJsConfig);
53 |
54 | if (hasTsConfig && hasJsConfig) {
55 | throw new Error(
56 | 'You have both a tsconfig.json and a jsconfig.json. If you are using TypeScript please remove your jsconfig.json file.'
57 | );
58 | }
59 |
60 | let config;
61 |
62 | // If there's a tsconfig.json we assume it's a
63 | // TypeScript project and set up the config
64 | // based on tsconfig.json
65 | if (hasTsConfig) {
66 | config = require(paths.appTsConfig);
67 | // Otherwise we'll check if there is jsconfig.json
68 | // for non TS projects.
69 | } else if (hasJsConfig) {
70 | config = require(paths.appJsConfig);
71 | }
72 |
73 | config = config || {};
74 | const options = config.compilerOptions || {};
75 |
76 | const additionalModulePaths = getAdditionalModulePaths(options);
77 |
78 | return {
79 | additionalModulePaths: additionalModulePaths,
80 | hasTsConfig,
81 | };
82 | }
83 |
84 | module.exports = getModules();
85 |
--------------------------------------------------------------------------------
/src/pages/Login/index.js:
--------------------------------------------------------------------------------
1 | import { Form, Icon, Input, Button } from 'antd'
2 | import React from 'react'
3 | import { Redirect } from 'react-router-dom'
4 | import connect from '../../utils/connect'
5 | import { login } from './service'
6 | import './index.less'
7 | @connect
8 | class NormalLoginForm extends React.Component {
9 | handleSubmit = e => {
10 | const _this = this
11 | e.preventDefault();
12 | this.props.form.validateFields((err, values) => {
13 | if (!err) {
14 | _this.authChange(values)
15 | }else{
16 | console.log(err)
17 | }
18 | })
19 | }
20 |
21 | authChange = (values)=>{
22 | const { dispatch, authChangeAction } = this.props
23 | login(values).then(res =>{
24 | const action = authChangeAction(res.data.token)
25 | dispatch(action)
26 | })
27 | }
28 |
29 | render() {
30 | if(this.props.state.authed ||localStorage.getItem('authed')){
31 | return (
32 |
33 | )
34 | }
35 | const { getFieldDecorator } = this.props.form;
36 | return (
37 |
38 |
41 | {getFieldDecorator('username', {
42 | rules: [{ required: true, message: 'Please input your username!' }],
43 | })(
44 | }
46 | placeholder="username"
47 | />,
48 | )}
49 |
50 |
51 | {getFieldDecorator('password', {
52 | rules: [{ required: true, message: 'Please input your Password!' }],
53 | })(
54 | }
56 | type="password"
57 | placeholder="password"
58 | />,
59 | )}
60 |
61 |
62 |
65 |
66 |
67 | 用户为admin的时候,能够看到所有的权限列表,其余账号只能看到部分
68 |
69 |
70 |
71 | );
72 | }
73 | }
74 |
75 | const WrappedNormalLoginForm = Form.create({ name: 'normal_login' })(NormalLoginForm)
76 | export default WrappedNormalLoginForm
77 |
--------------------------------------------------------------------------------
/config/paths.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const url = require('url');
6 |
7 | // Make sure any symlinks in the project folder are resolved:
8 | // https://github.com/facebook/create-react-app/issues/637
9 | const appDirectory = fs.realpathSync(process.cwd());
10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
11 |
12 | const envPublicUrl = process.env.PUBLIC_URL;
13 |
14 | function ensureSlash(inputPath, needsSlash) {
15 | const hasSlash = inputPath.endsWith('/');
16 | if (hasSlash && !needsSlash) {
17 | return inputPath.substr(0, inputPath.length - 1);
18 | } else if (!hasSlash && needsSlash) {
19 | return `${inputPath}/`;
20 | } else {
21 | return inputPath;
22 | }
23 | }
24 |
25 | const getPublicUrl = appPackageJson =>
26 | envPublicUrl || require(appPackageJson).homepage;
27 |
28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer
29 | // "public path" at which the app is served.
30 | // Webpack needs to know it to put the right