├── src ├── assets │ ├── theme.less │ ├── images │ │ └── avatar.png │ └── common.less ├── layouts │ ├── BlankLayout.js │ ├── BasicLayout.less │ ├── PageHeaderLayout.less │ ├── PageHeaderLayout.js │ ├── GlobalFooter │ │ ├── index.less │ │ └── index.js │ ├── UserLayout.less │ ├── GlobalSider │ │ └── index.less │ ├── Layout.js │ ├── UserLayout.js │ ├── GlobalHeader │ │ └── index.less │ └── BasicLayout.js ├── modules │ ├── Exception │ │ ├── style.less │ │ ├── 403.js │ │ ├── 404.js │ │ ├── 500.js │ │ └── triggerException.js │ ├── User │ │ ├── UserLogin.less │ │ ├── RegisterResult.less │ │ ├── Login.less │ │ ├── Register.less │ │ ├── RegisterResult.js │ │ ├── Login.js │ │ ├── UserLogin.js │ │ ├── UserLogin.test.js │ │ └── UserRegister.js │ ├── Valid │ │ └── List.js │ ├── Page │ │ ├── Template.js │ │ └── List.js │ ├── Template │ │ └── List.js │ ├── Component │ │ └── List.js │ ├── Scaffold │ │ └── List.js │ ├── Layout │ │ └── List.js │ ├── Inter │ │ └── List.js │ ├── About │ │ └── About.js │ └── Preview │ │ ├── testPreview.js │ │ └── Preview.js ├── components │ ├── CodeArea │ │ ├── index.js │ │ ├── monaco.less │ │ ├── index.scss │ │ └── Monaco.js │ ├── utils │ │ ├── pathTools.js │ │ └── pathTools.test.js │ ├── Exception │ │ ├── demo │ │ │ ├── 404.md │ │ │ ├── 500.md │ │ │ └── 403.md │ │ ├── index.d.ts │ │ ├── typeConfig.js │ │ ├── index.zh-CN.md │ │ ├── index.md │ │ ├── index.en-US.md │ │ ├── index.js │ │ ├── index.css │ │ ├── index.scss │ │ └── index.less │ ├── HeaderSearch │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── index.less │ │ ├── demo │ │ │ └── basic.md │ │ └── index.js │ ├── PageHeader │ │ ├── demo │ │ │ ├── simple.md │ │ │ ├── structure.md │ │ │ ├── image.md │ │ │ └── standard.md │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── index.test.js │ │ └── index.less │ ├── ExtraFieldConfig │ │ └── index.less │ ├── package │ │ ├── schema.js │ │ ├── index.less │ │ ├── index.css │ │ ├── index.js │ │ ├── components │ │ │ ├── SchemaComponents │ │ │ │ ├── FieldInput.js │ │ │ │ └── schemaJson.css │ │ │ ├── MockSelect │ │ │ │ └── index.js │ │ │ └── LocalProvider │ │ │ │ └── index.js │ │ └── utils.js │ ├── SiderMenu │ │ ├── index.less │ │ ├── index.js │ │ ├── SiderMenu.test.js │ │ └── SiderMenu.js │ ├── SimpleTable │ │ └── index.js │ ├── FileTree │ │ └── index.js │ └── MonacoEditor │ │ └── index.js ├── setupProxy.js ├── e2e │ ├── home.e2e.js │ └── login.e2e.js ├── defaultSettings.js ├── App.js ├── utils │ ├── formLayout.js │ ├── jsonp.js │ └── native.js ├── models │ ├── preview.js │ ├── global.js │ ├── valid.js │ ├── interApp.js │ ├── layout.js │ ├── user.js │ ├── inter.js │ ├── component.js │ ├── template.js │ └── app.js ├── index.js ├── common │ ├── urlMaps.js │ └── menu.js └── logo.svg ├── public ├── config.js ├── favicon.png └── manifest.json ├── config ├── jest │ ├── jest-puppeteer.config.js │ ├── cssTransform.js │ └── fileTransform.js ├── deploy.js ├── paths.js └── env.js ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── scripts ├── test.js └── start.js ├── LICENSE ├── .stylelintrc ├── README.md └── .eslintrc /src/assets/theme.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | -------------------------------------------------------------------------------- /public/config.js: -------------------------------------------------------------------------------- 1 | var appName = 'xxx' 2 | var baseUrl = 'http://127.0.0.0:8361' 3 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genany/gen/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genany/gen/HEAD/src/assets/images/avatar.png -------------------------------------------------------------------------------- /src/layouts/BlankLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default props =>
; 4 | -------------------------------------------------------------------------------- /config/jest/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: { 3 | command: 'node server.js', 4 | port: 4444 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout.less: -------------------------------------------------------------------------------- 1 | .main-content { 2 | margin: 24px; 3 | } 4 | 5 | .screen-xs { 6 | .main-content { 7 | margin: 24px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: "red"; 3 | :global(.ant-btn) { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/CodeArea/index.js: -------------------------------------------------------------------------------- 1 | // import CodeArea from './CodeArea' 2 | import Monaco from './Monaco'; 3 | 4 | // export { CodeArea, Monaco as default } 5 | export { Monaco as default }; 6 | -------------------------------------------------------------------------------- /src/modules/User/UserLogin.less: -------------------------------------------------------------------------------- 1 | .login-form { 2 | margin: 0 auto; 3 | max-width: 300px; 4 | } 5 | 6 | .login-form-forgot { 7 | float: right; 8 | } 9 | 10 | .login-form-button { 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.less: -------------------------------------------------------------------------------- 1 | @import '../assets/theme'; 2 | .content { 3 | margin: 24px 24px 0; 4 | } 5 | 6 | @media screen and (max-width: @screen-sm) { 7 | .content { 8 | margin: 24px 0 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | export function urlToList(url) { 3 | const urllist = url.split('/').filter(i => i); 4 | return urllist.map((urlItem, index) => { 5 | return `/${urllist.slice(0, index + 1).join('/')}`; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from '../../components/Exception'; 4 | 5 | export default () => ( 6 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/modules/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from '../../components/Exception'; 4 | 5 | export default () => ( 6 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/modules/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from '../../components/Exception'; 4 | 5 | export default () => ( 6 | 11 | ); 12 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const proxy = require('http-proxy-middleware'); 2 | 3 | module.exports = function(app) { 4 | const proxyUrl = 'http://127.0.0.1:7001'; 5 | app.use( 6 | proxy('/api', { 7 | target: proxyUrl, 8 | pathRewrite: { 9 | '^/api': '' 10 | } 11 | }) 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | 18 | -------------------------------------------------------------------------------- /src/components/Exception/demo/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 404 5 | en-US: 404 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 404 页面。 11 | 12 | ## en-US 13 | 14 | 404 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/Exception/demo/500.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 500 5 | en-US: 500 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 500 页面。 11 | 12 | ## en-US 13 | 14 | 500 page. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | 19 | ReactDOM.render( 20 | 21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/modules/User/RegisterResult.less: -------------------------------------------------------------------------------- 1 | .registerResult { 2 | :global { 3 | .anticon { 4 | font-size: 64px; 5 | } 6 | } 7 | .title { 8 | margin-top: 32px; 9 | font-size: 20px; 10 | line-height: 28px; 11 | } 12 | .actions { 13 | margin-top: 40px; 14 | a + a { 15 | margin-left: 8px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/e2e/home.e2e.js: -------------------------------------------------------------------------------- 1 | import Nightmare from 'nightmare'; 2 | 3 | describe('Homepage', () => { 4 | it('it should have logo text', async () => { 5 | const page = Nightmare().goto('http://localhost:8000'); 6 | const text = await page.wait('h1').evaluate(() => document.body.innerHTML).end(); 7 | expect(text).toContain('

Ant Design Pro

'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | 4 | extends: ['react-app'], 5 | parserOptions: { 6 | ecmaFeatures: { 7 | legacyDecorators: true 8 | } 9 | }, 10 | env: { 11 | browser: true, 12 | node: true, 13 | es6: true, 14 | mocha: true, 15 | jest: true, 16 | jasmine: true 17 | }, 18 | rules: {} 19 | }; 20 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Gen", 3 | "name": "Gen快速生成app", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 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/components/Exception/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IExceptionProps { 3 | type?: '403' | '404' | '500'; 4 | title?: React.ReactNode; 5 | desc?: React.ReactNode; 6 | img?: string; 7 | actions?: React.ReactNode; 8 | linkElement?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class Exception extends React.Component {} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | # roadhog-api-doc ignore 6 | /src/utils/request-temp.js 7 | _roadhog-api-doc 8 | 9 | # production 10 | /dist 11 | /build 12 | 13 | # misc 14 | .DS_Store 15 | npm-debug.log* 16 | yarn-error.log 17 | 18 | /coverage 19 | .idea 20 | yarn.lock 21 | package-lock.json 22 | *bak 23 | jsconfig.json 24 | .prettierrc 25 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface HeaderSearchProps { 3 | placeholder?: string; 4 | dataSource?: Array; 5 | onSearch?: (value: string) => void; 6 | onChange?: (value: string) => void; 7 | onPressEnter?: (value: string) => void; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class HeaderSearch extends React.Component< 12 | HeaderSearchProps, 13 | any 14 | > {} 15 | -------------------------------------------------------------------------------- /src/defaultSettings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | navTheme: 'dark', // theme for nav menu 3 | primaryColor: '#1890FF', // primary color of ant design 4 | layout: 'topmenu', // nav menu position: sidemenu or topmenu 5 | contentWidth: 'Fluid', // layout of content: Fluid or Fixed, only works when layout is topmenu 6 | fixedHeader: false, // sticky header 7 | autoHideHeader: false, // auto hide header 8 | fixSiderbar: false // sticky siderbar 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: HeaderSearch 4 | zh-CN: HeaderSearch 5 | subtitle: 顶部搜索框 6 | cols: 1 7 | order: 8 8 | --- 9 | 10 | 通常作为全局搜索的入口,放置在导航工具条右侧。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | placeholder | 占位文字 | string | - 17 | dataSource | 当前提示内容列表 | string[] | - 18 | onSearch | 选择某项或按下回车时的回调 | function(value) | - 19 | onChange | 输入搜索字符的回调 | function(value) | - 20 | onPressEnter | 按下回车时的回调 | function(value) | - 21 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import PageHeader from '../components/PageHeader'; 4 | import './PageHeaderLayout.less'; 5 | 6 | export default ({ children, wrapperClassName, top, ...restProps }) => ( 7 |
8 | {top} 9 | 10 | {children ?
{children}
: null} 11 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/modules/User/Login.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/_common'; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | .icon { 7 | font-size: 24px; 8 | color: rgba(0, 0, 0, 0.2); 9 | margin-left: 16px; 10 | vertical-align: middle; 11 | cursor: pointer; 12 | transition: color 0.3s; 13 | &:hover { 14 | color: $primary-color; 15 | } 16 | } 17 | .other { 18 | text-align: left; 19 | margin-top: 24px; 20 | line-height: 22px; 21 | .register { 22 | float: right; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/CodeArea/monaco.less: -------------------------------------------------------------------------------- 1 | .code { 2 | padding-top: 4px; 3 | border: 1px solid #d9d9d9; // border-radius: 4px; 4 | position: relative; 5 | border-radius: 4px; 6 | &.fullscreen-open { 7 | padding-top: 0; 8 | position: fixed; 9 | top: 0; 10 | right: 0; 11 | bottom: 0; 12 | left: 0; 13 | z-index: 100000; 14 | } 15 | .fullscreen-btn { 16 | position: absolute; 17 | top: 5px; 18 | right: 10px; 19 | z-index: 100; 20 | cursor: pointer; 21 | line-height: initial; // background: red; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/layouts/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/theme'; 2 | 3 | .footer { 4 | padding: 0 16px; 5 | margin: 48px 0 24px 0; 6 | text-align: center; 7 | .links { 8 | margin-bottom: 8px; 9 | a { 10 | color: @text-color-secondary; 11 | transition: all 0.3s; 12 | &:not(:last-child) { 13 | margin-right: 40px; 14 | } 15 | &:hover { 16 | color: @text-color; 17 | } 18 | } 19 | } 20 | .copyright { 21 | color: @text-color-secondary; 22 | font-size: @font-size-base; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Exception/demo/403.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 403 5 | en-US: 403 6 | --- 7 | 8 | ## zh-CN 9 | 10 | 403 页面,配合自定义操作。 11 | 12 | ## en-US 13 | 14 | 403 page with custom operations. 15 | 16 | ````jsx 17 | import Exception from 'ant-design-pro/lib/Exception'; 18 | import { Button } from 'antd'; 19 | 20 | const actions = ( 21 |
22 | 23 | 24 |
25 | ); 26 | ReactDOM.render( 27 | 28 | , mountNode); 29 | ```` 30 | -------------------------------------------------------------------------------- /src/components/Exception/typeConfig.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | 403: { 3 | img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', 4 | title: '403', 5 | desc: '抱歉,你无权访问该页面', 6 | }, 7 | 404: { 8 | img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', 9 | title: '404', 10 | desc: '抱歉,你访问的页面不存在', 11 | }, 12 | 500: { 13 | img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', 14 | title: '500', 15 | desc: '抱歉,服务器出错了', 16 | }, 17 | }; 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /src/components/utils/pathTools.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from './pathTools'; 2 | 3 | describe('test urlToList', () => { 4 | it('A path', () => { 5 | expect(urlToList('/userinfo')).toEqual(['/userinfo']); 6 | }); 7 | it('Secondary path', () => { 8 | expect(urlToList('/userinfo/2144')).toEqual([ 9 | '/userinfo', 10 | '/userinfo/2144', 11 | ]); 12 | }); 13 | it('Three paths', () => { 14 | expect(urlToList('/userinfo/2144/addr')).toEqual([ 15 | '/userinfo', 16 | '/userinfo/2144', 17 | '/userinfo/2144/addr', 18 | ]); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | title: Simple 4 | --- 5 | 6 | 简单的页头。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const breadcrumbList = [{ 12 | title: '一级菜单', 13 | href: '/', 14 | }, { 15 | title: '二级菜单', 16 | href: '/', 17 | }, { 18 | title: '三级菜单', 19 | }]; 20 | 21 | ReactDOM.render( 22 |
23 | 24 |
25 | , mountNode); 26 | ```` 27 | 28 | 33 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npm.taobao.org 2 | disturl=https://registry.npm.taobao.org/node 3 | nvm_nodejs_org_mirror=https://cdn.npm.taobao.org/dist/node 4 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 5 | fse_binary_host_mirror=https://registry.npm.taobao.org/fsevents 6 | phantomjs_cdnurl=https://npm.taobao.org/dist/phantomjs 7 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver 8 | operadriver_cdnurl=https://npm.taobao.org/mirrors/operadriver 9 | electron_mirror=http://npm.taobao.org/mirrors/electron 10 | puppeteer_download_host=https://cdn.npm.taobao.org/dist 11 | nwjs_urlbase=https://cdn.npm.taobao.org/dist/nwjs/v 12 | flow_binary_mirror=https://github.com/facebook/flow/releases/download/v -------------------------------------------------------------------------------- /src/components/PageHeader/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface PageHeaderProps { 3 | title?: React.ReactNode | string; 4 | logo?: React.ReactNode | string; 5 | action?: React.ReactNode | string; 6 | content?: React.ReactNode; 7 | extraContent?: React.ReactNode; 8 | routes?: Array; 9 | params?: any; 10 | breadcrumbList?: Array<{ title: React.ReactNode; href?: string }>; 11 | tabList?: Array<{ key: string; tab: React.ReactNode }>; 12 | tabActiveKey?: string; 13 | onTabChange?: (key: string) => void; 14 | tabBarExtraContent?: React.ReactNode; 15 | linkElement?: React.ReactNode; 16 | style?: React.CSSProperties; 17 | } 18 | 19 | export default class PageHeader extends React.Component {} 20 | -------------------------------------------------------------------------------- /src/components/Exception/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception 3 | subtitle: 异常 4 | cols: 1 5 | order: 5 6 | --- 7 | 8 | 异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 9 | 10 | ## API 11 | 12 | | 参数 | 说明 | 类型 | 默认值 | 13 | |-------------|------------------------------------------|-------------|-------| 14 | | type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | 15 | | title | 标题 | ReactNode | - | 16 | | desc | 补充描述 | ReactNode | - | 17 | | img | 背景图片地址 | string | - | 18 | | actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | 19 | | linkElement | 定义链接的元素 | string\|ReactElement | 'a' | 20 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .headerSearch { 4 | :global(.anticon-search) { 5 | cursor: pointer; 6 | font-size: 16px; 7 | } 8 | .input { 9 | transition: width .3s, margin-left .3s; 10 | width: 0; 11 | background: transparent; 12 | border-radius: 0; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | border: 0; 18 | padding-left: 0; 19 | padding-right: 0; 20 | box-shadow: none !important; 21 | } 22 | &, 23 | &:hover, 24 | &:focus { 25 | border-bottom: 1px solid @border-color-base; 26 | } 27 | &.show { 28 | width: 210px; 29 | margin-left: 8px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/layouts/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from 'antd'; 3 | import './index.less'; 4 | 5 | class GlobalFooter extends React.Component { 6 | state = {}; 7 | render() { 8 | return ( 9 |
10 | 21 | Copyright 2018 Daycool 22 |
23 | ); 24 | } 25 | } 26 | 27 | export default GlobalFooter; 28 | -------------------------------------------------------------------------------- /src/components/Exception/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Exception 4 | zh-CN: Exception 5 | subtitle: 异常 6 | cols: 1 7 | order: 5 8 | --- 9 | 10 | 异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |-------------|------------------------------------------|-------------|-------| 16 | | type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | desc | 补充描述 | ReactNode | - | 19 | | img | 背景图片地址 | string | - | 20 | | actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | 21 | | linkElement | 定义链接的元素,默认为 `a` | string\|ReactElement | - | 22 | -------------------------------------------------------------------------------- /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 | const jest = require('jest'); 19 | let argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 全局搜索 4 | --- 5 | 6 | 通常放置在导航工具条右侧。(点击搜索图标预览效果) 7 | 8 | ````jsx 9 | import HeaderSearch from 'ant-design-pro/lib/HeaderSearch'; 10 | 11 | ReactDOM.render( 12 |
22 | { 26 | console.log('input', value); // eslint-disable-line 27 | }} 28 | onPressEnter={(value) => { 29 | console.log('enter', value); // eslint-disable-line 30 | }} 31 | /> 32 |
33 | , mountNode); 34 | ```` 35 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { routerRedux, Route, Switch, Link, Redirect } from 'dva/router'; 3 | import { LocaleProvider } from 'antd'; 4 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 5 | 6 | import Layout from './layouts/Layout'; 7 | 8 | import UserLogin from '@/modules/User/UserLogin'; 9 | 10 | // import Authorized from './utils/Authorized' 11 | // import { getQueryPath } from './utils/utils' 12 | 13 | const { ConnectedRouter } = routerRedux; 14 | // const { AuthorizedRoute } = Authorized 15 | 16 | function RouterConfig({ history, app }) { 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | 28 | export default RouterConfig; 29 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/en/webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | const assetFilename = JSON.stringify(path.basename(filename)); 11 | 12 | if (filename.match(/\.svg$/)) { 13 | return `module.exports = { 14 | __esModule: true, 15 | default: ${assetFilename}, 16 | ReactComponent: (props) => ({ 17 | $$typeof: Symbol.for('react.element'), 18 | type: 'svg', 19 | ref: null, 20 | key: null, 21 | props: Object.assign({}, props, { 22 | children: ${assetFilename} 23 | }) 24 | }), 25 | };`; 26 | } 27 | 28 | return `module.exports = ${assetFilename};`; 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/Exception/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception 3 | cols: 1 4 | order: 5 5 | --- 6 | 7 | Exceptions page is used to provide feedback on specific abnormal state. Usually, it contains an explanation of the error status, and provides users with suggestions or operations, to prevent users from feeling lost and confused. 8 | 9 | ## API 10 | 11 | Property | Description | Type | Default 12 | ---------|-------------|------|-------- 13 | type | type of exception, the corresponding default `title`, `desc`, `img` will be given if set, which can be overridden by explicit setting of `title`, `desc`, `img` | Enum {'403', '404', '500'} | - 14 | title | title | ReactNode | - 15 | desc | supplementary description | ReactNode | - 16 | img | the url of background image | string | - 17 | actions | suggested operations, a default 'Home' link will show if not set | ReactNode | - 18 | linkElement | to specify the element of link | string\|ReactElement | 'a' -------------------------------------------------------------------------------- /src/modules/User/Register.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .main { 4 | width: 368px; 5 | margin: 0 auto; 6 | 7 | :global { 8 | .ant-form-item { 9 | margin-bottom: 24px; 10 | } 11 | } 12 | 13 | h3 { 14 | font-size: 16px; 15 | margin-bottom: 20px; 16 | } 17 | 18 | .getCaptcha { 19 | display: block; 20 | width: 100%; 21 | } 22 | 23 | .submit { 24 | width: 50%; 25 | } 26 | 27 | .login { 28 | float: right; 29 | line-height: @btn-height-lg; 30 | } 31 | } 32 | 33 | .success, 34 | .warning, 35 | .error { 36 | transition: color 0.3s; 37 | } 38 | 39 | .success { 40 | color: @success-color; 41 | } 42 | 43 | .warning { 44 | color: @warning-color; 45 | } 46 | 47 | .error { 48 | color: @error-color; 49 | } 50 | 51 | .progress-pass > .progress { 52 | :global { 53 | .ant-progress-bg { 54 | background-color: @warning-color; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/modules/User/RegisterResult.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'antd'; 3 | import { Link } from 'dva/router'; 4 | import Result from '../../components/Result'; 5 | import './RegisterResult.less'; 6 | 7 | const actions = ( 8 |
9 | 10 | 13 | 14 | 15 | 16 | 17 |
18 | ); 19 | 20 | export default ({ location }) => ( 21 | 26 | 你的账户: 27 | {location.state ? location.state.account : 'sdemo@sdemo.cn'} 注册成功 28 |
29 | } 30 | description="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。" 31 | actions={actions} 32 | style={{ marginTop: 56 }} 33 | /> 34 | ); 35 | -------------------------------------------------------------------------------- /src/components/ExtraFieldConfig/index.less: -------------------------------------------------------------------------------- 1 | .code{ 2 | background: red; 3 | .CodeMirror-fullscreen { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | bottom: 0; 9 | height: auto; 10 | z-index: 99999; 11 | } 12 | .CodeMirror{height: 100%;} 13 | .CodeMirror pre { padding-left: 7px; line-height: 1.25; } 14 | .CodeMirror-gutters{ 15 | background: #fff; 16 | border: none; 17 | } 18 | .CodeMirror pre.CodeMirror-placeholder { 19 | color: #919191; 20 | } 21 | .CodeMirror-focused .cm-matchhighlight { 22 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); 23 | background-position: bottom; 24 | background-repeat: repeat-x; 25 | } 26 | .cm-matchhighlight {background-color: lightgreen} 27 | .CodeMirror-selection-highlight-scrollbar {background-color: green} 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/formLayout.js: -------------------------------------------------------------------------------- 1 | export const formItemPageLayout = { 2 | labelCol: { 3 | xs: { span: 24 }, 4 | sm: { span: 6 }, 5 | md: { span: 6 } 6 | }, 7 | wrapperCol: { 8 | xs: { span: 24 }, 9 | sm: { span: 18 }, 10 | md: { span: 18 } 11 | } 12 | }; 13 | 14 | export const formItemLayout = { 15 | labelCol: { 16 | xs: { span: 24 }, 17 | sm: { span: 3 }, 18 | md: { span: 3 } 19 | }, 20 | wrapperCol: { 21 | xs: { span: 24 }, 22 | sm: { span: 14 }, 23 | md: { span: 14 } 24 | } 25 | }; 26 | 27 | export const submitFormLayout = { 28 | wrapperCol: { 29 | xs: { span: 24, offset: 0 }, 30 | sm: { span: 10, offset: 3 } 31 | } 32 | }; 33 | 34 | export const formItemLayoutFull = { 35 | labelCol: { 36 | xs: { span: 24 }, 37 | sm: { span: 3 }, 38 | sm: { span: 3 } 39 | }, 40 | wrapperCol: { 41 | xs: { span: 24 }, 42 | sm: { span: 21 }, 43 | md: { span: 21 } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/package/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = handleSchema; 2 | 3 | function handleType(schema) { 4 | if ( 5 | !schema.type && 6 | schema.properties && 7 | typeof schema.properties === 'object' 8 | ) { 9 | schema.type = 'object'; 10 | } 11 | } 12 | 13 | function handleSchema(schema) { 14 | if (schema && !schema.type && !schema.properties) { 15 | schema.type = 'string'; 16 | } 17 | handleType(schema); 18 | if (schema.type === 'object') { 19 | if (!schema.properties) schema.properties = {}; 20 | handleObject(schema.properties, schema); 21 | } else if (schema.type === 'array') { 22 | if (!schema.items) schema.items = { type: 'string' }; 23 | handleSchema(schema.items); 24 | } else { 25 | return schema; 26 | } 27 | } 28 | 29 | function handleObject(properties) { 30 | for (var key in properties) { 31 | handleType(properties[key]); 32 | if (properties[key].type === 'array' || properties[key].type === 'object') 33 | handleSchema(properties[key]); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /config/deploy.js: -------------------------------------------------------------------------------- 1 | const client = require('deploy-kit') 2 | const path = require('path') 3 | client 4 | .sftp({ 5 | // sever account, address, port 6 | server: 'dev:zc-user!Q@W3e@223.203.221.60', 7 | // deploy all files in the directory 8 | workspace: path.join(__dirname, '..', 'build'), 9 | // ignore the matched files (glob pattern: https://github.com/isaacs/node-glob#glob-primer) 10 | // support array of glob pattern 11 | ignore: '**/*.map', 12 | // where the files are placed on the server 13 | deployTo: '/home/share/pay-admin/', 14 | // you can specify different place for each file 15 | rules: [ 16 | // { 17 | // test: /dist\/(.*)$/, 18 | // // $1, $2... means the parenthesized substring matches 19 | // // [$n] will be replaced with matched string 20 | // dest: 'public/static/[$1]' 21 | // }, 22 | // { 23 | // test: /views\/((?:[^/]+\/)*?[^\/]+).html$/, 24 | // dest: 'app/views/[$1].phtml' 25 | // } 26 | ] 27 | }) 28 | .exec() 29 | -------------------------------------------------------------------------------- /src/e2e/login.e2e.js: -------------------------------------------------------------------------------- 1 | import Nightmare from 'nightmare'; 2 | 3 | describe('Login', () => { 4 | let page; 5 | beforeEach(() => { 6 | page = Nightmare(); 7 | page 8 | .goto('http://localhost:8000/') 9 | .evaluate(() => { 10 | window.localStorage.setItem('antd-pro-authority', 'guest'); 11 | }) 12 | .goto('http://localhost:8000/#/user/login'); 13 | }); 14 | 15 | it('should login with failure', async () => { 16 | await page.type('#userName', 'mockuser') 17 | .type('#password', 'wrong_password') 18 | .click('button[type="submit"]') 19 | .wait('.ant-alert-error') // should display error 20 | .end(); 21 | }); 22 | 23 | it('should login successfully', async () => { 24 | const text = await page.type('#userName', 'admin') 25 | .type('#password', '888888') 26 | .click('button[type="submit"]') 27 | .wait('.ant-layout-sider h1') // should display error 28 | .evaluate(() => document.body.innerHTML) 29 | .end(); 30 | expect(text).toContain('

Ant Design Pro

'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 daycool 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @nav-header-height: 64px; 3 | .top-nav-menu { 4 | line-height: @nav-header-height; 5 | li.ant-menu-item { 6 | height: @nav-header-height; 7 | line-height: @nav-header-height; 8 | } 9 | } 10 | 11 | .drawer .drawer-content { 12 | background: #001529; 13 | } 14 | 15 | :global { 16 | .ant-menu-item, 17 | .ant-menu-submenu-title { 18 | padding: 0 10px; 19 | } 20 | .ant-menu-inline-collapsed { 21 | & > .ant-menu-item .sider-menu-item-img + span, 22 | & 23 | > .ant-menu-item-group 24 | > .ant-menu-item-group-list 25 | > .ant-menu-item 26 | .sider-menu-item-img 27 | + span, 28 | & 29 | > .ant-menu-submenu 30 | > .ant-menu-submenu-title 31 | .sider-menu-item-img 32 | + span { 33 | max-width: 0; 34 | display: inline-block; 35 | opacity: 0; 36 | } 37 | } 38 | .ant-menu-item .sider-menu-item-img + span, 39 | .ant-menu-submenu-title .sider-menu-item-img + span { 40 | transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out; 41 | opacity: 1; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/package/index.less: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /* 设置滚动条的样式 */ 3 | 4 | :global { 5 | ::-webkit-scrollbar { 6 | width: 6px; 7 | } 8 | /* 外层轨道 */ 9 | ::-webkit-scrollbar-track { 10 | -webkit-box-shadow: inset006pxrgba(255, 0, 0, 0.3); 11 | background: rgba(255, 255, 255, 0.1); 12 | } 13 | /* 滚动条滑块 */ 14 | ::-webkit-scrollbar-thumb { 15 | border-radius: 4px; 16 | background: rgba(0, 0, 0, 0.2); 17 | -webkit-box-shadow: inset006pxrgba(0, 0, 0, 0.5); 18 | } 19 | ::-webkit-scrollbar-thumb:window-inactive { 20 | background: rgba(0, 0, 0, 0.2); 21 | } 22 | .json-schema-react-editor { 23 | font-size: 14px; 24 | .array-item-type { 25 | text-align: left; 26 | } 27 | .add-btn { 28 | margin: 8px; 29 | } 30 | .pretty-editor { 31 | border: 1px solid #d9d9d9; 32 | border-radius: 4px; 33 | height: 800px; 34 | } 35 | .import-json-button { 36 | margin: 5px; 37 | } 38 | .certain-category-search { 39 | .ant-select-selection--single { 40 | height: 32px; 41 | } 42 | .ant-select-search__field__wrap { 43 | display: unset; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Drawer } from 'antd'; 3 | import SiderMenu from './SiderMenu'; 4 | 5 | /** 6 | * Recursively flatten the data 7 | * [{path:string},{path:string}] => {path,path2} 8 | * @param menus 9 | */ 10 | const getFlatMenuKeys = menuData => { 11 | let keys = []; 12 | menuData.forEach(item => { 13 | if (item.children) { 14 | keys = keys.concat(getFlatMenuKeys(item.children)); 15 | } 16 | keys.push(item.path); 17 | }); 18 | return keys; 19 | }; 20 | 21 | const SiderMenuWrapper = props => { 22 | const { isMobile, menuData, collapsed, onCollapse } = props; 23 | return isMobile ? ( 24 | onCollapse(true)} 28 | style={{ 29 | padding: 0, 30 | height: '100vh' 31 | }} 32 | > 33 | 38 | 39 | ) : ( 40 | 41 | ); 42 | }; 43 | 44 | export default SiderMenuWrapper; 45 | -------------------------------------------------------------------------------- /src/components/package/index.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | /* 设置滚动条的样式 */ 4 | ::-webkit-scrollbar { 5 | width: 6px; 6 | } 7 | /* 外层轨道 */ 8 | ::-webkit-scrollbar-track { 9 | -webkit-box-shadow: inset006pxrgba(255,0,0,0.3); 10 | 11 | background: rgba(255, 255, 255, 0.1); 12 | } 13 | /* 滚动条滑块 */ 14 | ::-webkit-scrollbar-thumb { 15 | border-radius: 4px; 16 | background: rgba(0,0,0,0.2); 17 | -webkit-box-shadow: inset006pxrgba(0,0,0,0.5); 18 | } 19 | ::-webkit-scrollbar-thumb:window-inactive { 20 | background: rgba(0,0,0,0.2); 21 | } 22 | 23 | .json-schema-react-editor { 24 | font-size: 14px; 25 | } 26 | 27 | .json-schema-react-editor .array-item-type { 28 | text-align: left 29 | } 30 | 31 | 32 | 33 | .json-schema-react-editor .add-btn { 34 | margin: 8px; 35 | } 36 | 37 | .json-schema-react-editor .pretty-editor{ 38 | border: 1px solid #d9d9d9; 39 | border-radius: 4px; 40 | height: 800px; 41 | } 42 | 43 | .json-schema-react-editor .import-json-button{ 44 | margin: 5px; 45 | } 46 | 47 | .json-schema-react-editor .certain-category-search .ant-select-selection--single { 48 | height: 32px; 49 | } 50 | 51 | .json-schema-react-editor .certain-category-search .ant-select-search__field__wrap { 52 | display: unset; 53 | } -------------------------------------------------------------------------------- /src/models/preview.js: -------------------------------------------------------------------------------- 1 | import http from '@/common/request'; 2 | 3 | export default { 4 | namespace: 'preview', 5 | 6 | state: { 7 | pagePreviewLoading: false, 8 | data: {} 9 | }, 10 | 11 | effects: { 12 | *page({ payload, callback }, { call, put }) { 13 | yield put({ 14 | type: 'pagePreviewLoading', 15 | payload: true 16 | }); 17 | const resData = yield call(http.previewPage, payload); 18 | if (resData.code === 200) { 19 | } 20 | yield put({ 21 | type: 'pagePreviewLoading', 22 | payload: false 23 | }); 24 | 25 | if (callback) callback(resData); 26 | }, 27 | *preview({ payload, callback }, { call, put }) { 28 | yield put({ 29 | type: 'pagePreviewLoading', 30 | payload: true 31 | }); 32 | const resData = yield call(http.preview, payload); 33 | yield put({ 34 | type: 'pagePreviewLoading', 35 | payload: false 36 | }); 37 | 38 | if (callback) callback(resData); 39 | } 40 | }, 41 | 42 | reducers: { 43 | pagePreviewLoading(state, action) { 44 | return { 45 | ...state, 46 | pagePreviewLoading: action.payload 47 | }; 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-prettier", 4 | "stylelint-config-standard", 5 | "./node_modules/prettier-stylelint/config.js" 6 | ], 7 | "rules": { 8 | "indentation": 2, 9 | "selector-pseudo-class-no-unknown": null, 10 | "shorthand-property-no-redundant-values": null, 11 | "at-rule-empty-line-before": null, 12 | "at-rule-name-space-after": null, 13 | "comment-empty-line-before": null, 14 | "declaration-bang-space-before": null, 15 | "declaration-empty-line-before": null, 16 | "function-comma-newline-after": null, 17 | "function-name-case": null, 18 | "function-parentheses-newline-inside": null, 19 | "function-max-empty-lines": null, 20 | "function-whitespace-after": null, 21 | "no-missing-end-of-source-newline": null, 22 | "number-leading-zero": null, 23 | "number-no-trailing-zeros": null, 24 | "rule-empty-line-before": null, 25 | "selector-combinator-space-after": null, 26 | "selector-descendant-combinator-no-non-space": null, 27 | "selector-list-comma-newline-after": null, 28 | "selector-pseudo-element-colon-notation": null, 29 | "unit-no-unknown": null, 30 | "no-descending-specificity": null, 31 | "value-list-max-empty-lines": null 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/package/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import App from './App.js'; 5 | import utils from './utils'; 6 | import moox from 'moox'; 7 | import schema from './models/schema'; 8 | import PropTypes from 'prop-types'; 9 | 10 | export default function zz(config = {}) { 11 | if (config.lang) utils.lang = config.lang; 12 | 13 | const Model = moox({ 14 | schema 15 | }); 16 | if (config.format) { 17 | Model.__jsonSchemaFormat = config.format; 18 | } else { 19 | Model.__jsonSchemaFormat = utils.format; 20 | } 21 | 22 | if (config.mock) { 23 | Model.__jsonSchemaMock = config.mock; 24 | } 25 | 26 | const store = Model.getStore(); 27 | 28 | const Component = props => { 29 | schema.state.component = props.component; 30 | return ( 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | Component.propTypes = { 38 | data: PropTypes.string, 39 | template: PropTypes.array, 40 | component: PropTypes.array, 41 | onChange: PropTypes.func, 42 | onChangeComponent: PropTypes.func, 43 | showEditor: PropTypes.bool 44 | }; 45 | return Component; 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button } from 'antd'; 4 | import config from './typeConfig'; 5 | import './index.css'; 6 | 7 | const Exception = ({ 8 | className, 9 | linkElement = 'a', 10 | type, 11 | title, 12 | desc, 13 | img, 14 | actions, 15 | ...rest 16 | }) => { 17 | const pageType = type in config ? type : '404'; 18 | const clsString = classNames('exception', className); 19 | return ( 20 |
21 |
22 |
26 |
27 |
28 |

{title || config[pageType].title}

29 |
{desc || config[pageType].desc}
30 |
31 | {actions || 32 | createElement( 33 | linkElement, 34 | { 35 | to: '/', 36 | href: '/' 37 | }, 38 | 39 | )} 40 |
41 |
42 |
43 | ); 44 | }; 45 | 46 | export default Exception; 47 | -------------------------------------------------------------------------------- /src/components/CodeArea/index.scss: -------------------------------------------------------------------------------- 1 | .code { 2 | position: relative; 3 | border: 1px solid #d9d9d9; 4 | border-radius: 4px; 5 | line-height: 1.5; 6 | .fullscreen-btn { 7 | position: absolute; 8 | top: 5px; 9 | right: 10px; 10 | z-index: 100; 11 | cursor: pointer; 12 | &.fullscreen-open { 13 | position: fixed; 14 | z-index: 100000; 15 | } 16 | } 17 | .CodeMirror-fullscreen { 18 | position: fixed; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | height: auto; 24 | z-index: 99999; 25 | } 26 | .CodeMirror { 27 | height: 100%; 28 | } 29 | .CodeMirror pre { 30 | padding-left: 7px; 31 | line-height: 1.5; 32 | } 33 | .CodeMirror-gutters { 34 | background: #fff; 35 | border: none; 36 | } 37 | .CodeMirror pre.CodeMirror-placeholder { 38 | color: #919191; 39 | } 40 | .CodeMirror-focused .cm-matchhighlight { 41 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==); 42 | background-position: bottom; 43 | background-repeat: repeat-x; 44 | } 45 | .cm-matchhighlight { 46 | background-color: lightgreen; 47 | } 48 | .CodeMirror-selection-highlight-scrollbar { 49 | background-color: green; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 如果您对此项目感兴趣欢迎 [star](https://github.com/genany/gen),如果您对有问题和建议欢迎 [issues](https://github.com/genany/gen/issues/new) 4 | 5 | 6 | # 通过接口生成一切 7 | 8 | 9 | --- 10 | 11 | 我们程序员每天都跟代码打交道,前端程序员coding, 后端程序员coding。 12 | 然而我们经常做重复性工作或者复制粘贴,或者前后端因为接口变动扯皮。 13 | 为了解决这些问题我打算通过接口生成代码。 14 | 15 | ## 为什么通过接口生成一切 16 | ### 任何语言都是在跟数据打交道 17 | 18 | 1. 在web前端html用来展现数据,javascript处理数据 19 | 1. go、java、php、nodejs等后端语言都在处理前端给的数据,或者给前端的数据 20 | 1. mysql、oracle、mongodb、postsql等数据库用来存储数据 21 | 22 | ### 接口是前后端通讯的桥梁 23 | 24 | ### 接口可以约定json格式 25 | 1. json易于人阅读和编写 26 | 2. json易于机器解析和生成 27 | 28 | ## 我理想的目标 29 | 1. 根据接口一键生成一套前可运行后端接口方案 30 | 1. 不改变我的开发方式 31 | 1. 低门槛轻松上手 32 | 1. 可视化配置 33 | 1. 如果是web页面所见即所得 34 | 1. 有丰富的项目脚手架方便二次开发 35 | 1. 生成接口文档 36 | 37 | ## 如何实现这些目标 38 | 39 | 1. 我们把每个文件都看做一个大模版,而这个大模版又有多个小模版组成,小模版我们把它叫做组件,组件又有组件属性,这样就组成了一个component tree,而接口本省又是个data tree, 两者渲染成最终我们想要的文件,这里模版引擎我们用了unjuncks 40 | 1. 接口使用hjson我通过注释表明这个字段对应的组件类型mock数据。 41 | hjson和jsonSchema相互转换,这样我就可以使用jsonSchema进行可视化配置 42 | 1. 通过jsonSchema配置字段对应的组件和定制组件扩展信息 43 | 1. 配置完即可动态渲染实时得到渲染结果, 如果是web页面我们可以iframe在区块内实时预览 44 | 1. 定制可服用模版和组件, 这样我们就可以定制各种页面 45 | 1. 通过把组件生成可动态配置的组件文件,然后把配置组件属性通过postMessage传递给iframe页进行实时修改 46 | 1. 把我们最佳项目实践直接做为脚手架,通用的文件设置为模版,或分隔多个小组件 47 | 48 | ## 在线运行太慢,或者网上代码不安全 49 | 1. 使用辅助工具,直接把渲染结果输出到本地,同时调用git命令保存修改文件,方便查看修改记录或回滚 50 | 51 | 52 | ![扩展字段](http://gen.sdemo.cn/gen.gif) 53 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '../assets/theme'; 2 | 3 | // .user-layout{ 4 | // } 5 | .user-layout-container { 6 | display: flex; 7 | flex-direction: column; 8 | height: 100vh; 9 | overflow: auto; 10 | background: #f0f2f5; 11 | } 12 | 13 | .user-layout-content { 14 | padding: 32px 0; 15 | flex: 1; 16 | } 17 | 18 | @media (min-width: 768px) { 19 | .user-layout-container { 20 | background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg'); 21 | background-repeat: no-repeat; 22 | background-position: center 110px; 23 | background-size: 100%; 24 | } 25 | .user-layout-content { 26 | padding: 212px 0 24px 0; 27 | } 28 | } 29 | 30 | .user-layout-top { 31 | text-align: center; 32 | } 33 | 34 | .user-layout-header { 35 | height: 44px; 36 | line-height: 44px; 37 | a { 38 | text-decoration: none; 39 | } 40 | } 41 | 42 | .user-layout-logo { 43 | height: 44px; 44 | vertical-align: top; 45 | margin-right: 16px; 46 | } 47 | 48 | .user-layout-title { 49 | font-size: 33px; 50 | color: fade(#000, 85%); 51 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 52 | font-weight: 600; 53 | position: relative; 54 | top: 2px; 55 | } 56 | 57 | .user-layout-desc { 58 | font-size: 14px; 59 | color: fade(#000, 45%); 60 | margin-top: 12px; 61 | margin-bottom: 40px; 62 | } 63 | -------------------------------------------------------------------------------- /src/components/package/components/SchemaComponents/FieldInput.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Input } from 'antd'; 4 | 5 | export default class FieldInput extends PureComponent { 6 | static propTypes = { 7 | onChange: PropTypes.func, 8 | value: PropTypes.string 9 | }; 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | value: props.value 15 | }; 16 | } 17 | 18 | handleChange = e => { 19 | let value = e.target.value; 20 | this.setState({ 21 | value 22 | }); 23 | }; 24 | 25 | componentWillReceiveProps(nextProps) { 26 | if (nextProps.value !== this.props.value) { 27 | this.setState({ 28 | value: nextProps.value 29 | }); 30 | } 31 | } 32 | 33 | onKeyup = e => { 34 | if (e.keyCode === 13) { 35 | if (e.target.value !== this.props.value) return this.props.onChange(e); 36 | } 37 | }; 38 | 39 | handleBlur = e => { 40 | if (e.target.value !== this.props.value) return this.props.onChange(e); 41 | }; 42 | 43 | render() { 44 | const { value } = this.state; 45 | 46 | return ( 47 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/layouts/GlobalSider/index.less: -------------------------------------------------------------------------------- 1 | @import '../../assets/theme'; 2 | :global { 3 | .logo { 4 | height: 64px; 5 | position: relative; 6 | line-height: 64px; // padding-left: (@menu-collapsed-width - 32px) / 2; 7 | transition: all 0.3s; 8 | background: #002140; 9 | overflow: hidden; 10 | text-align: center; 11 | .logo-img { 12 | display: inline-block; 13 | vertical-align: middle; 14 | color: #fff; 15 | font-size: 36px; // height: 32px; 16 | } 17 | h1 { 18 | color: white; 19 | display: inline-block; 20 | vertical-align: middle; 21 | font-size: 16px; 22 | margin: 0 0 0 12px; 23 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 24 | font-weight: 600; 25 | } 26 | } 27 | .icon { 28 | width: 14px; 29 | margin-right: 10px; 30 | } 31 | i.trigger { 32 | font-size: 20px; 33 | line-height: 64px; 34 | cursor: pointer; 35 | transition: all 0.3s, padding 0s; 36 | padding: 0 24px; 37 | &:hover { 38 | background: #e6f7ff; 39 | } 40 | } 41 | .sider { 42 | min-height: 100vh; 43 | box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); 44 | position: relative; 45 | z-index: 10; 46 | &.ligth { 47 | background-color: white; 48 | .logo { 49 | background: white; 50 | h1 { 51 | color: #002140; 52 | } 53 | } 54 | } 55 | } 56 | .screen-xs { 57 | .drawer-handle { 58 | top: 11px; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/layouts/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import connect from 'dva'; 3 | import { Route, Redirect } from 'dva/router'; 4 | import BasicLayout from './BasicLayout'; 5 | import UserLayout from './UserLayout'; 6 | import UserLogin from '@/modules/User/UserLogin'; 7 | import UserRegister from '@/modules/User/UserRegister'; 8 | 9 | import { getRouterData } from '@/common/router'; 10 | 11 | export default class Layout extends React.Component { 12 | render() { 13 | const app = this.props.app; 14 | const userInfo = app._store.getState().user.userInfo; 15 | 16 | const routerData = getRouterData(app); 17 | const isLogin = !!userInfo.name || !!localStorage.getItem('user'); 18 | const pathname = this.props.location.pathname; 19 | const isAbout = 20 | pathname === '/about' || pathname === '' || pathname === '/'; 21 | 22 | return isLogin || isAbout ? ( 23 | 24 | {routerData.map(item => ( 25 | 26 | ))} 27 | 32 | 33 | ) : ( 34 | 35 | 36 | 37 | 42 | 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: PageHeader 4 | zh-CN: PageHeader 5 | subtitle: 页头 6 | cols: 1 7 | order: 11 8 | --- 9 | 10 | 页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | title | title 区域 | ReactNode | - | 17 | | logo | logo区域 | ReactNode | - | 18 | | action | 操作区,位于 title 行的行尾 | ReactNode | - | 19 | | content | 内容区 | ReactNode | - | 20 | | extraContent | 额外内容区,位于content的右侧 | ReactNode | - | 21 | | breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - | 22 | | routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | 23 | | params | 面包屑相关属性,路由的参数 | object | - | 24 | | location | 面包屑相关属性,当前的路由信息 | object | - | 25 | | breadcrumbNameMap | 面包屑相关属性,路由的地址-名称映射表 | object | - | 26 | | tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | 27 | | tabActiveKey | 当前高亮的 tab 项 | string | - | 28 | | onTabChange | 切换面板的回调 | (key) => void | - | 29 | | linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - | 30 | 31 | > 面包屑的配置方式有三种,一是直接配置 `breadcrumbList`,二是结合 `react-router@2` `react-router@3`,配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router),三是结合 `react-router@4`,配置 `location` `breadcrumbNameMap`,优先级依次递减,脚手架中使用最后一种。 对于后两种用法,你也可以将 `routes` `params` 及 `location` `breadcrumbNameMap` 放到 context 中,组件会自动获取。 32 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.test.js: -------------------------------------------------------------------------------- 1 | import { getBreadcrumb } from './index'; 2 | import { urlToList } from '../utils/pathTools'; 3 | 4 | const routerData = { 5 | '/dashboard/analysis': { 6 | name: '分析页', 7 | }, 8 | '/userinfo': { 9 | name: '用户列表', 10 | }, 11 | '/userinfo/:id': { 12 | name: '用户信息', 13 | }, 14 | '/userinfo/:id/addr': { 15 | name: '收货订单', 16 | }, 17 | }; 18 | describe('test getBreadcrumb', () => { 19 | it('Simple url', () => { 20 | expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual( 21 | '分析页', 22 | ); 23 | }); 24 | it('Parameters url', () => { 25 | expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual( 26 | '用户信息', 27 | ); 28 | }); 29 | it('The middle parameter url', () => { 30 | expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual( 31 | '收货订单', 32 | ); 33 | }); 34 | it('Loop through the parameters', () => { 35 | const urlNameList = urlToList('/userinfo/2144/addr').map((url) => { 36 | return getBreadcrumb(routerData, url).name; 37 | }); 38 | expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']); 39 | }); 40 | 41 | it('a path', () => { 42 | const urlNameList = urlToList('/userinfo').map((url) => { 43 | return getBreadcrumb(routerData, url).name; 44 | }); 45 | expect(urlNameList).toEqual(['用户列表']); 46 | }); 47 | it('Secondary path', () => { 48 | const urlNameList = urlToList('/userinfo/2144').map((url) => { 49 | return getBreadcrumb(routerData, url).name; 50 | }); 51 | expect(urlNameList).toEqual(['用户列表', '用户信息']); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/Exception/index.css: -------------------------------------------------------------------------------- 1 | .exception { 2 | display: flex; 3 | align-items: center; 4 | height: 100%; } 5 | .exception .imgBlock { 6 | flex: 0 0 62.5%; 7 | width: 62.5%; 8 | padding-right: 152px; 9 | zoom: 1; } 10 | .exception .imgBlock:before, .exception .imgBlock:after { 11 | content: ' '; 12 | display: table; } 13 | .exception .imgBlock:after { 14 | clear: both; 15 | visibility: hidden; 16 | font-size: 0; 17 | height: 0; } 18 | .exception .imgEle { 19 | height: 360px; 20 | width: 100%; 21 | max-width: 430px; 22 | float: right; 23 | background-repeat: no-repeat; 24 | background-position: 50% 50%; 25 | background-size: contain; } 26 | .exception .content { 27 | flex: auto; } 28 | .exception .content h1 { 29 | color: #434e59; 30 | font-size: 72px; 31 | font-weight: 600; 32 | line-height: 72px; 33 | margin-bottom: 24px; } 34 | .exception .content .desc { 35 | color: fade(#000, 45%); 36 | font-size: 20px; 37 | line-height: 28px; 38 | margin-bottom: 16px; } 39 | .exception .content .actions button:not(:last-child) { 40 | margin-right: 8px; } 41 | 42 | @media screen and (max-width: 1200px) { 43 | .exception .imgBlock { 44 | padding-right: 88px; } } 45 | 46 | @media screen and (max-width: 576px) { 47 | .exception { 48 | display: block; 49 | text-align: center; } 50 | .exception .imgBlock { 51 | padding-right: 0; 52 | margin: 0 auto 24px; } } 53 | 54 | @media screen and (max-width: 480px) { 55 | .exception .imgBlock { 56 | margin-bottom: -24px; 57 | overflow: hidden; } } 58 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Structure 4 | --- 5 | 6 | 基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const breadcrumbList = [{ 12 | title: '面包屑', 13 | }]; 14 | 15 | const tabList = [{ 16 | key: '1', 17 | tab: '页签一', 18 | }, { 19 | key: '2', 20 | tab: '页签二', 21 | }, { 22 | key: '3', 23 | tab: '页签三', 24 | }]; 25 | 26 | ReactDOM.render( 27 |
28 | Title
} 31 | logo={
logo
} 32 | action={
action
} 33 | content={
content
} 34 | extraContent={
extraContent
} 35 | breadcrumbList={breadcrumbList} 36 | tabList={tabList} 37 | tabActiveKey="1" 38 | /> 39 |
40 | , mountNode); 41 | ```` 42 | 43 | 69 | -------------------------------------------------------------------------------- /src/modules/Exception/triggerException.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Button, Spin, Card } from 'antd'; 3 | import { connect } from 'dva'; 4 | import styles from './style.less'; 5 | 6 | @connect(state => ({ 7 | isloading: state.error.isloading 8 | })) 9 | export default class TriggerException extends PureComponent { 10 | state = { 11 | isloading: false 12 | }; 13 | trigger401 = () => { 14 | this.setState({ 15 | isloading: true 16 | }); 17 | this.props.dispatch({ 18 | type: 'error/query401' 19 | }); 20 | }; 21 | trigger403 = () => { 22 | this.setState({ 23 | isloading: true 24 | }); 25 | this.props.dispatch({ 26 | type: 'error/query403' 27 | }); 28 | }; 29 | trigger500 = () => { 30 | this.setState({ 31 | isloading: true 32 | }); 33 | this.props.dispatch({ 34 | type: 'error/query500' 35 | }); 36 | }; 37 | trigger404 = () => { 38 | this.setState({ 39 | isloading: true 40 | }); 41 | this.props.dispatch({ 42 | type: 'error/query404' 43 | }); 44 | }; 45 | render() { 46 | return ( 47 | 48 | 49 | 52 | 55 | 58 | 61 | 62 | 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/modules/Valid/List.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { Form, Divider, Popconfirm } from 'antd'; 3 | import { connect } from 'dva'; 4 | import { Link } from 'dva/router'; 5 | import MyList from '../../components/List'; 6 | 7 | @Form.create() 8 | @connect(({ global, valid, loading }) => ({ 9 | global, 10 | valid, 11 | loading: loading.effects['valid/list'] 12 | })) 13 | export default class List extends MyList { 14 | modeName = 'valid'; 15 | dataKey = 'data'; 16 | listUrl = 'valid/list'; 17 | addUrl = '/valid/add/0'; 18 | delUrl = 'valid/remove'; 19 | columns = [ 20 | { 21 | dataIndex: 'name', 22 | title: '规则名称', 23 | key: 'name' 24 | }, 25 | { 26 | dataIndex: 'label', 27 | title: '规则类型', 28 | key: 'label' 29 | }, 30 | { 31 | dataIndex: 'rule', 32 | title: '验证规则', 33 | key: 'rule' 34 | }, 35 | { 36 | dataIndex: 'error', 37 | title: '验证失败信息', 38 | key: 'error' 39 | }, 40 | { 41 | dataIndex: 'success', 42 | title: '验证成功信息', 43 | key: 'success' 44 | }, 45 | { 46 | title: '操作', 47 | key: 'action', 48 | render: (text, record, index) => { 49 | return ( 50 | 51 | 编辑 52 | 53 | this.onDelete(record, index)}> 54 | 删除 55 | 56 | 57 | ); 58 | } 59 | } 60 | ]; 61 | 62 | componentDidMount() { 63 | const { dispatch } = this.props; 64 | dispatch({ 65 | type: 'valid/list' 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/Exception/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../assets/_common.scss'; 2 | 3 | .exception { 4 | display: flex; 5 | align-items: center; 6 | height: 100%; 7 | 8 | .imgBlock { 9 | flex: 0 0 62.5%; 10 | width: 62.5%; 11 | padding-right: 152px; 12 | zoom: 1; 13 | &:before, 14 | &:after { 15 | content: ' '; 16 | display: table; 17 | } 18 | &:after { 19 | clear: both; 20 | visibility: hidden; 21 | font-size: 0; 22 | height: 0; 23 | } 24 | } 25 | 26 | .imgEle { 27 | height: 360px; 28 | width: 100%; 29 | max-width: 430px; 30 | float: right; 31 | background-repeat: no-repeat; 32 | background-position: 50% 50%; 33 | background-size: contain; 34 | } 35 | 36 | .content { 37 | flex: auto; 38 | 39 | h1 { 40 | color: #434e59; 41 | font-size: 72px; 42 | font-weight: 600; 43 | line-height: 72px; 44 | margin-bottom: 24px; 45 | } 46 | 47 | .desc { 48 | color: $text-color-secondary; 49 | font-size: 20px; 50 | line-height: 28px; 51 | margin-bottom: 16px; 52 | } 53 | 54 | .actions { 55 | button:not(:last-child) { 56 | margin-right: 8px; 57 | } 58 | } 59 | } 60 | } 61 | 62 | @media screen and (max-width: $screen-xl) { 63 | .exception { 64 | .imgBlock { 65 | padding-right: 88px; 66 | } 67 | } 68 | } 69 | 70 | @media screen and (max-width: $screen-sm) { 71 | .exception { 72 | display: block; 73 | text-align: center; 74 | .imgBlock { 75 | padding-right: 0; 76 | margin: 0 auto 24px; 77 | } 78 | } 79 | } 80 | 81 | @media screen and (max-width: $screen-xs) { 82 | .exception { 83 | .imgBlock { 84 | margin-bottom: -24px; 85 | overflow: hidden; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/Exception/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .exception { 4 | display: flex; 5 | align-items: center; 6 | height: 100%; 7 | 8 | .imgBlock { 9 | flex: 0 0 62.5%; 10 | width: 62.5%; 11 | padding-right: 152px; 12 | zoom: 1; 13 | &:before, 14 | &:after { 15 | content: " "; 16 | display: table; 17 | } 18 | &:after { 19 | clear: both; 20 | visibility: hidden; 21 | font-size: 0; 22 | height: 0; 23 | } 24 | } 25 | 26 | .imgEle { 27 | height: 360px; 28 | width: 100%; 29 | max-width: 430px; 30 | float: right; 31 | background-repeat: no-repeat; 32 | background-position: 50% 50%; 33 | background-size: contain; 34 | } 35 | 36 | .content { 37 | flex: auto; 38 | 39 | h1 { 40 | color: #434e59; 41 | font-size: 72px; 42 | font-weight: 600; 43 | line-height: 72px; 44 | margin-bottom: 24px; 45 | } 46 | 47 | .desc { 48 | color: @text-color-secondary; 49 | font-size: 20px; 50 | line-height: 28px; 51 | margin-bottom: 16px; 52 | } 53 | 54 | .actions { 55 | button:not(:last-child) { 56 | margin-right: 8px; 57 | } 58 | } 59 | } 60 | } 61 | 62 | @media screen and (max-width: @screen-xl) { 63 | .exception { 64 | .imgBlock { 65 | padding-right: 88px; 66 | } 67 | } 68 | } 69 | 70 | @media screen and (max-width: @screen-sm) { 71 | .exception { 72 | display: block; 73 | text-align: center; 74 | .imgBlock { 75 | padding-right: 0; 76 | margin: 0 auto 24px; 77 | } 78 | } 79 | } 80 | 81 | @media screen and (max-width: @screen-xs) { 82 | .exception { 83 | .imgBlock { 84 | margin-bottom: -24px; 85 | overflow: hidden; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/package/components/MockSelect/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, AutoComplete, Icon } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import LocaleProvider from '../LocalProvider/index.js'; 5 | 6 | const Option = AutoComplete.Option; 7 | 8 | export default class MockSelect extends React.Component { 9 | constructor(props, context) { 10 | super(props); 11 | this.state = { 12 | mock: '' 13 | }; 14 | this.mock = context.Model.__jsonSchemaMock || []; 15 | } 16 | 17 | static propTypes = { 18 | schema: PropTypes.object, 19 | showEdit: PropTypes.func, 20 | onChange: PropTypes.func 21 | }; 22 | 23 | render() { 24 | // const children = []; 25 | const { schema } = this.props; 26 | const children = this.mock.map(item => ( 27 | 28 | )); 29 | 30 | return ( 31 |
32 | 43 | { 48 | e.stopPropagation(); 49 | this.props.showEdit(); 50 | }} 51 | /> 52 | } 53 | /> 54 | 55 |
56 | ); 57 | } 58 | } 59 | 60 | MockSelect.contextTypes = { 61 | Model: PropTypes.object 62 | }; 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './assets/common.less'; 4 | import App from './App'; 5 | import registerServiceWorker, { unregister } from './registerServiceWorker'; 6 | import { hot } from 'react-hot-loader'; 7 | 8 | import dva from 'dva'; 9 | import createHistory from 'history/createHashHistory'; 10 | // user BrowserHistory 11 | // import createHistory from 'history/createBrowserHistory'; 12 | import createLoading from 'dva-loading'; 13 | import 'moment/locale/zh-cn'; 14 | import { createLogger } from 'redux-logger'; 15 | 16 | // 1. Initialize 17 | const app = dva({ 18 | history: createHistory(), 19 | onAction: createLogger({ level: 'log' }) 20 | }); 21 | 22 | app.use(createLoading()); 23 | 24 | app.model(require('./models/global').default); 25 | app.model(require('./models/app').default); 26 | app.model(require('./models/template').default); 27 | app.model(require('./models/component').default); 28 | app.model(require('./models/inter').default); 29 | app.model(require('./models/interApp').default); 30 | app.model(require('./models/layout').default); 31 | app.model(require('./models/page').default); 32 | app.model(require('./models/preview').default); 33 | app.model(require('./models/scaffold').default); 34 | app.model(require('./models/user').default); 35 | app.model(require('./models/valid').default); 36 | 37 | hot(module)(App); 38 | 39 | app.router(App); 40 | app.start('#root'); 41 | 42 | const store = app._store; 43 | 44 | const user = localStorage.getItem('user'); 45 | if (user) { 46 | try { 47 | store.dispatch({ 48 | type: 'user/userInfo', 49 | payload: JSON.parse(user) 50 | }); 51 | } catch (error) {} 52 | } 53 | 54 | export default store; 55 | 56 | if (window.location.protocol === 'https:') { 57 | registerServiceWorker(); 58 | } 59 | 60 | // unregister(); 61 | -------------------------------------------------------------------------------- /src/components/SimpleTable/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Table, Form } from 'antd'; 4 | import { connect } from 'dva'; 5 | 6 | const FormItem = Form.Item; 7 | 8 | @connect(({ global }) => ({ 9 | global 10 | })) 11 | export default class SimpleTable extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = {}; 15 | } 16 | componentWillReceiveProps(nextProps) {} 17 | 18 | render() { 19 | let columns = this.props.columns; 20 | let expandColumns = []; 21 | let size = ''; 22 | const formItemLayout = { 23 | labelCol: { span: 24 }, 24 | wrapperCol: { span: 24 } 25 | }; 26 | 27 | if (this.props.global.isMobile) { 28 | columns = this.props.columns.filter(item => item.isExpand !== true); 29 | expandColumns = this.props.columns.filter(item => item.isExpand === true); 30 | } 31 | return ( 32 | ( 40 | 41 | {expandColumns.map((item, index) => ( 42 | 47 | 48 | {item.render 49 | ? item.render(record[item.dataIndex], record) 50 | : record[item.dataIndex]} 51 | 52 | 53 | ))} 54 | 55 | ) 56 | } 57 | /> 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/SiderMenu/SiderMenu.test.js: -------------------------------------------------------------------------------- 1 | import { urlToList } from '../_utils/pathTools'; 2 | import { getFlatMenuKeys, getMenuMatchKeys } from './SiderMenu'; 3 | 4 | const menu = [ 5 | { 6 | path: '/dashboard', 7 | children: [ 8 | { 9 | path: '/dashboard/name' 10 | } 11 | ] 12 | }, 13 | { 14 | path: '/userinfo', 15 | children: [ 16 | { 17 | path: '/userinfo/:id', 18 | children: [ 19 | { 20 | path: '/userinfo/:id/info' 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | ]; 27 | 28 | const flatMenuKeys = getFlatMenuKeys(menu); 29 | 30 | describe('test convert nested menu to flat menu', () => { 31 | it('simple menu', () => { 32 | expect(flatMenuKeys).toEqual([ 33 | '/dashboard', 34 | '/dashboard/name', 35 | '/userinfo', 36 | '/userinfo/:id', 37 | '/userinfo/:id/info' 38 | ]); 39 | }); 40 | }); 41 | 42 | describe('test menu match', () => { 43 | it('simple path', () => { 44 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard'))).toEqual([ 45 | '/dashboard' 46 | ]); 47 | }); 48 | 49 | it('error path', () => { 50 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboardname'))).toEqual( 51 | [] 52 | ); 53 | }); 54 | 55 | it('Secondary path', () => { 56 | expect( 57 | getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard/name')) 58 | ).toEqual(['/dashboard', '/dashboard/name']); 59 | }); 60 | 61 | it('Parameter path', () => { 62 | expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144'))).toEqual( 63 | ['/userinfo', '/userinfo/:id'] 64 | ); 65 | }); 66 | 67 | it('three parameter path', () => { 68 | expect( 69 | getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144/info')) 70 | ).toEqual(['/userinfo', '/userinfo/:id', '/userinfo/:id/info']); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/image.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: With Image 4 | --- 5 | 6 | 带图片的页头。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const content = ( 12 |
13 |

段落示意:蚂蚁金服务设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。

14 |
15 | 16 | 快速开始 17 | 18 | 19 | 产品简介 20 | 21 | 22 | 产品文档 23 | 24 |
25 |
26 | ); 27 | 28 | const extra = ( 29 |
30 | 31 |
32 | ); 33 | 34 | const breadcrumbList = [{ 35 | title: '一级菜单', 36 | href: '/', 37 | }, { 38 | title: '二级菜单', 39 | href: '/', 40 | }, { 41 | title: '三级菜单', 42 | }]; 43 | 44 | ReactDOM.render( 45 |
46 | 52 |
53 | , mountNode); 54 | ```` 55 | 56 | 76 | -------------------------------------------------------------------------------- /src/assets/common.less: -------------------------------------------------------------------------------- 1 | @import './theme.less'; 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | .clearfix { 10 | &:before, 11 | &:after { 12 | content: ' '; 13 | clear: both; 14 | display: block; 15 | } 16 | } 17 | 18 | em, 19 | i { 20 | font-style: normal; 21 | } 22 | 23 | body { 24 | margin: 0; 25 | padding: 0; 26 | font-family: sans-serif; 27 | } 28 | .flex { 29 | display: flex; 30 | } 31 | .tableList { 32 | .tableListOperator { 33 | margin-bottom: 16px; 34 | button { 35 | margin-right: 8px; // color: #fff; 36 | } 37 | } 38 | } 39 | .tableListForm { 40 | .ant-form { 41 | .ant-form-item { 42 | height: 32px; 43 | margin-bottom: 24px; 44 | margin-right: 0; 45 | display: flex; 46 | > .ant-form-item-label { 47 | width: auto; 48 | min-width: 100px; 49 | line-height: 32px; 50 | padding-right: 8px; 51 | text-align: right; 52 | } 53 | .ant-form-item-control { 54 | line-height: 32px; 55 | } 56 | } 57 | .ant-form-item-control-wrapper { 58 | flex: 1; 59 | } 60 | } 61 | .submitButtons { 62 | white-space: nowrap; 63 | margin-bottom: 24px; 64 | margin-left: 100px; 65 | } 66 | } // @media screen and (max-width: @screen-lg) { 67 | // .tableListForm :global(.ant-form-item) { 68 | // margin-right: 24px; 69 | // } 70 | // } 71 | // @media screen and (max-width: @screen-md) { 72 | // .tableListForm :global(.ant-form-item) { 73 | // margin-right: 8px; 74 | // } 75 | // } 76 | .vertical-gutter { 77 | > div[class*='ant-col'] { 78 | margin-bottom: 24px; 79 | } 80 | } 81 | /* 移动端通用样式 */ 82 | .screen-xs { 83 | .ant-calendar-picker { 84 | width: auto !important; 85 | .ant-calendar-picker-input { 86 | font-size: 12px; 87 | } 88 | } 89 | .tableListFormMobile { 90 | .submitButtons { 91 | white-space: nowrap; 92 | margin-bottom: 24px; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/common/urlMaps.js: -------------------------------------------------------------------------------- 1 | const urlMaps = { 2 | login: '/api/user/login', 3 | logout: '/api/user/logout', 4 | register: '/api/user/register', 5 | getUserInfo: '/api/user/userInfo', 6 | previewApp: `/api/preview/app`, 7 | previewPage: `/api/preview/page`, 8 | preview: `/api/preview/preview`, 9 | appList: `/api/app/page`, 10 | appInfo: `/api/app/info`, 11 | appRemove: `/api/app/remove`, 12 | appAdd: `/api/app/save`, 13 | pageList: `/api/page/page`, 14 | pageInfo: `/api/page/info`, 15 | pageRemove: `/api/page/remove`, 16 | pageAdd: `/api/page/save`, 17 | scaffoldList: `/api/scaffold/page`, 18 | scaffoldFiles: `/api/scaffold/files`, 19 | scaffoldFileContent: `/api/scaffold/fileContent`, 20 | scaffoldInfo: `/api/scaffold/info`, 21 | scaffoldRemove: `/api/scaffold/remove`, 22 | scaffoldAdd: `/api/scaffold/save`, 23 | upload: `/api/scaffold/upload`, 24 | pullCode: `/api/scaffold/pullCode`, 25 | interAppList: `/api/interapp/page`, 26 | interAppInfo: `/api/interapp/info`, 27 | interAppRemove: `/api/interapp/remove`, 28 | interAppAdd: `/api/interapp/save`, 29 | interList: `/api/inter/page`, 30 | interInfo: `/api/inter/info`, 31 | interRemove: `/api/inter/remove`, 32 | interAdd: `/api/inter/save`, 33 | layoutList: `/api/layout/page`, 34 | layoutInfo: `/api/layout/info`, 35 | layoutRemove: `/api/layout/remove`, 36 | layoutAdd: `/api/layout/save`, 37 | templateList: `/api/template/page`, 38 | templateInfo: `/api/template/info`, 39 | templateRemove: `/api/template/remove`, 40 | templateAdd: `/api/template/save`, 41 | componentList: `/api/component/page`, 42 | componentInfo: `/api/component/info`, 43 | componentRemove: `/api/component/remove`, 44 | componentAdd: `/api/component/save`, 45 | validList: `/api/valid/page`, 46 | validInfo: `/api/valid/info`, 47 | validRemove: `/api/valid/remove`, 48 | validAdd: `/api/valid/save` 49 | }; 50 | 51 | // export const baseUrl = "http://share.axingxing.com/proxy" 52 | export const baseUrl = ''; 53 | 54 | export const loginWhiteList = [urlMaps.getUserInfo, urlMaps.getBannerList]; 55 | 56 | export default urlMaps; 57 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "plugins": ["compat"], 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "es6": true, 9 | "mocha": true, 10 | "jest": true, 11 | "jasmine": true 12 | }, 13 | "rules": { 14 | "generator-star-spacing": [0], 15 | "consistent-return": [0], 16 | "react/forbid-prop-types": [0], 17 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }], 18 | "global-require": [1], 19 | "import/prefer-default-export": [0], 20 | "react/jsx-no-bind": [0], 21 | "react/prop-types": [0], 22 | "react/prefer-stateless-function": [0], 23 | "react/jsx-wrap-multilines": ["error", { 24 | "declaration": "parens-new-line", 25 | "assignment": "parens-new-line", 26 | "return": "parens-new-line", 27 | "arrow": "parens-new-line", 28 | "condition": "parens-new-line", 29 | "logical": "parens-new-line", 30 | "prop": "ignore" 31 | }], 32 | "no-else-return": [0], 33 | "no-restricted-syntax": [0], 34 | "import/no-extraneous-dependencies": [0], 35 | "no-use-before-define": [0], 36 | "jsx-a11y/no-static-element-interactions": [0], 37 | "jsx-a11y/no-noninteractive-element-interactions": [0], 38 | "jsx-a11y/click-events-have-key-events": [0], 39 | "jsx-a11y/anchor-is-valid": [0], 40 | "no-nested-ternary": [0], 41 | "arrow-body-style": [0], 42 | "import/extensions": [0], 43 | "no-bitwise": [0], 44 | "no-cond-assign": [0], 45 | "import/no-unresolved": [0], 46 | "comma-dangle": ["error", { 47 | "arrays": "always-multiline", 48 | "objects": "always-multiline", 49 | "imports": "always-multiline", 50 | "exports": "always-multiline", 51 | "functions": "ignore" 52 | }], 53 | "object-curly-newline": [0], 54 | "function-paren-newline": [0], 55 | "no-restricted-globals": [0], 56 | "require-yield": [1], 57 | "compat/compat": "error" 58 | }, 59 | "parserOptions": { 60 | "ecmaFeatures": { 61 | "experimentalObjectRestSpread": true 62 | } 63 | }, 64 | "settings": { 65 | "polyfills": ["fetch", "promises"] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/modules/Page/Template.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { connect } from 'dva'; 3 | import { message, notification, Select } from 'antd'; 4 | import _ from 'lodash'; 5 | 6 | @connect(({ template, loading }) => ({ 7 | template 8 | })) 9 | export default class Template extends PureComponent { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | templateName: undefined 14 | }; 15 | } 16 | componentWillReceiveProps(nextProps) { 17 | if (nextProps.value !== this.state.templateName) { 18 | let templateName = nextProps.value; 19 | console.log('TCL: Template -> componentWillReceiveProps -> templateName', templateName); 20 | this.setState({ templateName }); 21 | } 22 | } 23 | componentDidMount() { 24 | this.props.dispatch({ 25 | type: 'template/list', 26 | payload: {} 27 | }); 28 | } 29 | 30 | onChange = templateName => { 31 | console.log('TCL: Template -> templateName', templateName); 32 | if (!templateName) { 33 | message.warning('请选择页面模版'); 34 | return; 35 | } 36 | 37 | let template = this.props.template.data.list.find(item => item.name === templateName); 38 | 39 | this.setState({ 40 | templateName: templateName 41 | }); 42 | if (this.props.onChange) { 43 | this.props.onChange(template); 44 | } 45 | }; 46 | changePageTemplateTips = templateName => { 47 | if (!templateName) { 48 | return; 49 | } 50 | notification.warning({ 51 | message: '更换模板数据不能恢复' 52 | }); 53 | }; 54 | 55 | render() { 56 | const templateData = this.props.template.data; 57 | const templateName = this.state.templateName; 58 | return ( 59 | 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/utils/jsonp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | /** 6 | * Module exports. 7 | */ 8 | 9 | /** 10 | * Callback index. 11 | */ 12 | 13 | let count = 0; 14 | 15 | /** 16 | * Noop function. 17 | */ 18 | 19 | function noop(){} 20 | 21 | /** 22 | * JSONP handler 23 | * 24 | * Options: 25 | * - param {String} qs parameter (`callback`) 26 | * - prefix {String} qs parameter (`__jp`) 27 | * - name {String} qs parameter (`prefix` + incr) 28 | * - timeout {Number} how long after a timeout error is emitted (`60000`) 29 | * 30 | * @param {String} url 31 | * @param {Object|Function} optional options / callback 32 | * @param {Function} optional callback 33 | */ 34 | export default function jsonp(url, opts, fn){ 35 | if ('function' == typeof opts) { 36 | fn = opts; 37 | opts = {}; 38 | } 39 | if (!opts) opts = {}; 40 | 41 | let prefix = opts.prefix || '__jp'; 42 | 43 | // use the callback name that was passed if one was provided. 44 | // otherwise generate a unique name by incrementing our counter. 45 | let id = opts.name || (prefix + (count++)); 46 | 47 | let param = opts.param || 'callback'; 48 | let timeout = null != opts.timeout ? opts.timeout : 60000; 49 | let enc = encodeURIComponent; 50 | let target = document.getElementsByTagName('script')[0] || document.head; 51 | let script; 52 | let timer; 53 | 54 | 55 | if (timeout) { 56 | timer = setTimeout(function(){ 57 | cleanup(); 58 | if (fn) fn(new Error('Timeout')); 59 | }, timeout); 60 | } 61 | 62 | function cleanup(){ 63 | if (script.parentNode) script.parentNode.removeChild(script); 64 | window[id] = noop; 65 | if (timer) clearTimeout(timer); 66 | } 67 | 68 | function cancel(){ 69 | if (window[id]) { 70 | cleanup(); 71 | } 72 | } 73 | 74 | window[id] = function(data){ 75 | cleanup(); 76 | if (fn) fn(null, data); 77 | }; 78 | 79 | // add qs component 80 | url += (~url.indexOf('?') ? '&' : '?') + param + '&callback=' + enc(id); 81 | url = url.replace('?&', '?'); 82 | 83 | // create script 84 | script = document.createElement('script'); 85 | script.src = url; 86 | target.parentNode.insertBefore(script, target); 87 | 88 | return cancel; 89 | } 90 | -------------------------------------------------------------------------------- /src/modules/Template/List.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import { Form, Divider, Popconfirm } from 'antd'; 3 | import { connect } from 'dva'; 4 | import { Link } from 'dva/router'; 5 | import MyList from '../../components/List'; 6 | 7 | @Form.create() 8 | @connect(({ global, template, loading }) => ({ 9 | global, 10 | template, 11 | loading: loading.effects['template/list'] 12 | })) 13 | export default class List extends MyList { 14 | modeName = 'template'; 15 | dataKey = 'data'; 16 | listUrl = 'template/list'; 17 | addUrl = '/template/add/0'; 18 | delUrl = 'template/remove'; 19 | columns = [ 20 | { 21 | dataIndex: 'name', 22 | title: '英文名称', 23 | key: 'name' 24 | }, 25 | { 26 | dataIndex: 'label', 27 | title: '中文名称', 28 | key: 'label' 29 | }, 30 | { 31 | dataIndex: 'desc', 32 | title: '简介', 33 | key: 'desc' 34 | }, 35 | { 36 | dataIndex: 'cate_id', 37 | title: '类别', 38 | key: 'cate_id' 39 | }, 40 | { 41 | dataIndex: 'scaffold.label', 42 | title: '脚手架', 43 | key: 'scaffold_id', 44 | render: (text, record) => { 45 | let url = '/scaffold/Add/' + record.scaffold_id; 46 | return ( 47 | 48 | {text} 49 | 50 | ); 51 | } 52 | }, 53 | // { 54 | // dataIndex: 'template', 55 | // title: '默认模版', 56 | // key: 'template', 57 | // }, 58 | { 59 | title: '操作', 60 | key: 'action', 61 | render: (text, record, index) => { 62 | return ( 63 | 64 | 查看 65 | 66 | 编辑 67 | 68 | this.onDelete(record, index)}> 69 | 删除 70 | 71 | 72 | ); 73 | } 74 | } 75 | ]; 76 | 77 | componentDidMount() { 78 | const { dispatch } = this.props; 79 | dispatch({ 80 | type: 'template/list' 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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