├── src ├── assets │ ├── fonts │ │ ├── readme.md │ │ └── icomoon │ │ │ ├── icomoon.eot │ │ │ ├── icomoon.ttf │ │ │ └── icomoon.woff │ ├── images │ │ ├── bg.jpg │ │ ├── logo.png │ │ ├── logo1.png │ │ ├── avatar.jpg │ │ ├── logo-r.png │ │ ├── pattern.png │ │ ├── topbar-bg.jpg │ │ └── topbar-bg2.jpg │ └── styles │ │ ├── transition.less │ │ └── animate.css ├── components │ ├── Mask │ │ ├── util.js │ │ └── style │ │ │ └── index.less │ ├── Icon │ │ ├── style │ │ │ └── index.less │ │ ├── index.js │ │ └── Icon.js │ ├── Modal │ │ ├── ModalSystem.js │ │ ├── index.js │ │ └── style │ │ │ └── index.less │ ├── Video │ │ ├── style │ │ │ └── index.less │ │ ├── index.js │ │ └── Video.js │ ├── Charts │ │ ├── G2 │ │ │ ├── style │ │ │ │ └── index.less │ │ │ ├── index.js │ │ │ └── G2.js │ │ └── ECharts │ │ │ └── index.js │ ├── Clock │ │ ├── index.js │ │ ├── style │ │ │ └── index.less │ │ └── Clock.js │ ├── Drag │ │ ├── index.js │ │ ├── test.js │ │ └── Drag.js │ ├── Form │ │ ├── index.js │ │ └── model │ │ │ ├── custom.js │ │ │ ├── number.js │ │ │ ├── treeselect.js │ │ │ ├── input.js │ │ │ ├── radio.js │ │ │ ├── checkbox.js │ │ │ ├── select.js │ │ │ ├── cascade.js │ │ │ └── editor.js │ ├── Image │ │ ├── index.js │ │ ├── util.js │ │ └── Image.js │ ├── Panel │ │ ├── index.js │ │ └── style │ │ │ └── index.less │ ├── Print │ │ └── index.js │ ├── Editor │ │ ├── index.js │ │ ├── style │ │ │ └── index.less │ │ ├── Editor.js │ │ └── config.js │ ├── NavBar │ │ ├── index.js │ │ ├── SearchBox.js │ │ └── style │ │ │ └── searchbox.less │ ├── TopBar │ │ └── index.js │ ├── Upload │ │ ├── index.js │ │ └── Upload.js │ ├── Toolbar │ │ └── index.js │ ├── LazyLoad │ │ └── index.js │ ├── BannerMng │ │ ├── index.js │ │ └── Form.js │ ├── SearchBar │ │ └── index.js │ ├── SideLayout │ │ ├── index.js │ │ ├── style │ │ │ └── index.less │ │ └── SideLayout.js │ ├── WaterFall │ │ ├── index.js │ │ └── style │ │ │ └── index.less │ ├── SkinToolbox │ │ ├── index.js │ │ ├── SideBarBox.js │ │ ├── LayoutBox.js │ │ └── NavBarBox.js │ ├── TransferTree │ │ ├── index.js │ │ └── Search.js │ ├── Pages │ │ ├── style │ │ │ ├── images │ │ │ │ ├── balk.png │ │ │ │ ├── saw.png │ │ │ │ ├── error.gif │ │ │ │ └── workers.png │ │ │ ├── 500.less │ │ │ ├── 403.less │ │ │ ├── index.less │ │ │ ├── screenLock.less │ │ │ └── result.less │ │ ├── index.js │ │ ├── 500.js │ │ ├── 403.js │ │ └── Result.js │ ├── Button │ │ ├── style │ │ │ ├── index.less │ │ │ └── ripple.less │ │ ├── index.js │ │ ├── Button.js │ │ └── Ripple.js │ ├── Notification │ │ ├── index.js │ │ ├── Notification.js │ │ ├── antdNotice.less │ │ ├── antdNotice.js │ │ └── normal.less │ ├── DataTable │ │ └── index.js │ ├── SideBar │ │ ├── index.js │ │ └── RightSideBar.js │ ├── Loading │ │ ├── PageLoading.js │ │ └── PageLoading.less │ ├── BaseComponent │ │ ├── BaseEventComponent.js │ │ └── index.js │ ├── index.js │ ├── PatternLock │ │ ├── index.js │ │ └── style │ │ │ └── index.less │ ├── Portal │ │ └── index.js │ └── CSSAnimate │ │ └── index.js ├── routes │ ├── Blank │ │ ├── service │ │ │ └── index.js │ │ ├── components │ │ │ ├── index.less │ │ │ ├── index.module.less │ │ │ └── index.js │ │ ├── model │ │ │ └── index.js │ │ └── index.js │ ├── Dashboard │ │ ├── service │ │ │ └── index.js │ │ ├── index.js │ │ ├── model │ │ │ └── index.js │ │ └── components │ │ │ └── index.less │ ├── Business │ │ └── CRUD │ │ │ ├── service │ │ │ └── index.js │ │ │ ├── components │ │ │ └── index.less │ │ │ ├── routers │ │ │ └── Detail │ │ │ │ ├── model │ │ │ │ └── index.js │ │ │ │ ├── index.js │ │ │ │ └── components │ │ │ │ └── index.js │ │ │ └── index.js │ ├── Widgets │ │ ├── Toolbar │ │ │ ├── service │ │ │ │ └── index.js │ │ │ ├── model │ │ │ │ └── index.js │ │ │ ├── components │ │ │ │ ├── index.less │ │ │ │ └── columns.js │ │ │ └── index.js │ │ ├── DataTable │ │ │ ├── service │ │ │ │ └── index.js │ │ │ ├── components │ │ │ │ └── index.less │ │ │ ├── index.js │ │ │ └── model │ │ │ │ └── index.js │ │ ├── TransferTree │ │ │ ├── service │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── model │ │ │ │ └── index.js │ │ ├── LevelRoute │ │ │ ├── routes │ │ │ │ └── SubRoute │ │ │ │ │ ├── components │ │ │ │ │ ├── index.less │ │ │ │ │ └── index.js │ │ │ │ │ ├── model │ │ │ │ │ └── index.js │ │ │ │ │ └── index.js │ │ │ ├── model │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── components │ │ │ │ ├── index.less │ │ │ │ └── index.js │ │ ├── Gallery │ │ │ ├── components │ │ │ │ └── index.less │ │ │ └── index.js │ │ ├── ScreenLock │ │ │ └── index.js │ │ ├── Print │ │ │ ├── index.js │ │ │ └── components │ │ │ │ ├── Dynamic.js │ │ │ │ └── report.less │ │ ├── Result │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── Charts │ │ │ ├── G2 │ │ │ │ ├── index.js │ │ │ │ └── components │ │ │ │ │ ├── Bar.js │ │ │ │ │ ├── Line.js │ │ │ │ │ ├── Pie.js │ │ │ │ │ ├── Scatter.js │ │ │ │ │ ├── Radar.js │ │ │ │ │ └── Pie2.js │ │ │ ├── EC │ │ │ │ ├── index.js │ │ │ │ └── components │ │ │ │ │ ├── Line.js │ │ │ │ │ ├── Radar.js │ │ │ │ │ ├── Bar.js │ │ │ │ │ └── Pie.js │ │ │ └── components │ │ │ │ ├── index.less │ │ │ │ └── SideLayout.js │ │ ├── Column │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── Banner │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── SearchBar │ │ │ └── index.js │ │ ├── BaseComponent │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── Form │ │ │ ├── index.js │ │ │ └── model │ │ │ │ └── index.js │ │ └── Coming │ │ │ └── index.js │ ├── UI │ │ ├── Button │ │ │ ├── components │ │ │ │ ├── index.less │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── Mask │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── Editor │ │ │ └── index.js │ │ ├── Icon │ │ │ └── index.js │ │ ├── Modal │ │ │ ├── index.js │ │ │ └── components │ │ │ │ ├── columns.js │ │ │ │ └── index.js │ │ ├── Image │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ ├── Alerts │ │ │ ├── index.js │ │ │ └── components │ │ │ │ └── index.js │ │ └── CSSAnimate │ │ │ ├── index.js │ │ │ └── components │ │ │ └── index.less │ ├── Login │ │ ├── service │ │ │ └── index.js │ │ ├── index.js │ │ ├── components │ │ │ └── index.less │ │ └── model │ │ │ └── index.js │ ├── Register │ │ ├── service │ │ │ └── index.js │ │ ├── components │ │ │ ├── index.less │ │ │ └── Success.js │ │ ├── index.js │ │ └── model │ │ │ └── index.js │ └── Pages │ │ ├── 404.js │ │ ├── 403.js │ │ └── 500.js ├── decorator │ ├── index.js │ ├── breadcrumb.js │ └── coming.js ├── layouts │ ├── styles │ │ ├── user.less │ │ ├── card.less │ │ ├── tabs.less │ │ └── basic.less │ └── UserLayout.js ├── __mocks__ │ ├── index.js │ ├── form.js │ ├── charts.js │ ├── datatable.js │ ├── crud.js │ └── demo.js ├── setupProxy.js ├── utils │ ├── pageHelper │ │ └── index.js │ └── enquireScreen.js ├── models │ └── global.js └── index.js ├── templates └── routes │ ├── CRUD │ ├── service │ │ └── index.js │ ├── components │ │ ├── index.less │ │ └── columns.js │ ├── prompts.json │ └── index.js │ └── Blank │ ├── service │ └── index.js │ ├── components │ ├── index.less │ └── index.js │ ├── model │ └── index.js │ └── index.js ├── docs ├── components.md ├── index.md ├── pageHelper.md ├── build.md ├── mock.md └── columns.md ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .eslintrc ├── .vscode ├── settings.json └── launch.json ├── .env ├── jsconfig.json ├── mock-server.js ├── .gitignore ├── LICENSE ├── config-overrides.js └── package.json /src/assets/fonts/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Mask/util.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Blank/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Icon/style/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Modal/ModalSystem.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Video/style/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Blank/components/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Dashboard/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/routes/CRUD/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Charts/G2/style/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Business/CRUD/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Widgets/Toolbar/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/routes/Blank/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Widgets/DataTable/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Widgets/TransferTree/service/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/components.md: -------------------------------------------------------------------------------- 1 | # Components 自带组件 2 | 3 | to be continued -------------------------------------------------------------------------------- /src/routes/Business/CRUD/components/index.less: -------------------------------------------------------------------------------- 1 | .crud-page { 2 | } 3 | -------------------------------------------------------------------------------- /src/components/Clock/index.js: -------------------------------------------------------------------------------- 1 | import Clock from './Clock'; 2 | 3 | export default Clock; -------------------------------------------------------------------------------- /src/components/Drag/index.js: -------------------------------------------------------------------------------- 1 | import Drag from './Drag'; 2 | 3 | export default Drag; -------------------------------------------------------------------------------- /src/components/Form/index.js: -------------------------------------------------------------------------------- 1 | import Form from './Form'; 2 | 3 | export default Form; -------------------------------------------------------------------------------- /src/components/Icon/index.js: -------------------------------------------------------------------------------- 1 | import Icon from './Icon'; 2 | 3 | export default Icon; -------------------------------------------------------------------------------- /src/components/Image/index.js: -------------------------------------------------------------------------------- 1 | import Image from './Image'; 2 | 3 | export default Image; -------------------------------------------------------------------------------- /src/components/Panel/index.js: -------------------------------------------------------------------------------- 1 | import Panel from './Panel'; 2 | 3 | export default Panel; -------------------------------------------------------------------------------- /src/components/Print/index.js: -------------------------------------------------------------------------------- 1 | import Print from './Print'; 2 | 3 | export default Print; -------------------------------------------------------------------------------- /src/components/Video/index.js: -------------------------------------------------------------------------------- 1 | import Video from './Video'; 2 | 3 | export default Video; -------------------------------------------------------------------------------- /src/decorator/index.js: -------------------------------------------------------------------------------- 1 | import coming from './coming'; 2 | 3 | export { coming }; 4 | -------------------------------------------------------------------------------- /templates/routes/Blank/components/index.less: -------------------------------------------------------------------------------- 1 | // 页面样式 2 | .<%=name %>-page { 3 | } 4 | -------------------------------------------------------------------------------- /templates/routes/CRUD/components/index.less: -------------------------------------------------------------------------------- 1 | // 页面样式 2 | .<%=name %>-page { 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Editor/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './Editor'; 2 | 3 | export default Editor; -------------------------------------------------------------------------------- /src/components/NavBar/index.js: -------------------------------------------------------------------------------- 1 | import NavBar from './NavBar'; 2 | 3 | export default NavBar; -------------------------------------------------------------------------------- /src/components/TopBar/index.js: -------------------------------------------------------------------------------- 1 | import TopBar from './TopBar'; 2 | 3 | export default TopBar; -------------------------------------------------------------------------------- /src/components/Upload/index.js: -------------------------------------------------------------------------------- 1 | import Upload from './Upload'; 2 | 3 | export default Upload; -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/components/Toolbar/index.js: -------------------------------------------------------------------------------- 1 | import Toolbar from './Toolbar'; 2 | 3 | export default Toolbar; -------------------------------------------------------------------------------- /src/components/LazyLoad/index.js: -------------------------------------------------------------------------------- 1 | import LazyLoad from './LazyLoad'; 2 | 3 | export default LazyLoad; -------------------------------------------------------------------------------- /src/assets/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/bg.jpg -------------------------------------------------------------------------------- /src/components/BannerMng/index.js: -------------------------------------------------------------------------------- 1 | import BannerMng from './BannerMng'; 2 | 3 | export default BannerMng; -------------------------------------------------------------------------------- /src/components/SearchBar/index.js: -------------------------------------------------------------------------------- 1 | import SearchBar from './SearchBar'; 2 | 3 | export default SearchBar; -------------------------------------------------------------------------------- /src/components/SideLayout/index.js: -------------------------------------------------------------------------------- 1 | import SideLayout from './SideLayout'; 2 | 3 | export default SideLayout; -------------------------------------------------------------------------------- /src/components/WaterFall/index.js: -------------------------------------------------------------------------------- 1 | import WaterFall from './WaterFall'; 2 | 3 | export default WaterFall; -------------------------------------------------------------------------------- /src/routes/Blank/components/index.module.less: -------------------------------------------------------------------------------- 1 | /* less-modules */ 2 | .className { 3 | color: red; 4 | } -------------------------------------------------------------------------------- /src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/logo.png -------------------------------------------------------------------------------- /src/assets/images/logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/logo1.png -------------------------------------------------------------------------------- /src/components/SkinToolbox/index.js: -------------------------------------------------------------------------------- 1 | import SkinToolbox from './SkinToolbox'; 2 | 3 | export default SkinToolbox; -------------------------------------------------------------------------------- /src/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/avatar.jpg -------------------------------------------------------------------------------- /src/assets/images/logo-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/logo-r.png -------------------------------------------------------------------------------- /src/assets/images/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/pattern.png -------------------------------------------------------------------------------- /src/components/TransferTree/index.js: -------------------------------------------------------------------------------- 1 | import TransferTree from './TransferTree'; 2 | 3 | export default TransferTree; -------------------------------------------------------------------------------- /src/assets/images/topbar-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/topbar-bg.jpg -------------------------------------------------------------------------------- /src/assets/images/topbar-bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/images/topbar-bg2.jpg -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/routes/SubRoute/components/index.less: -------------------------------------------------------------------------------- 1 | .level2-route-page { 2 | background: #fffde7; 3 | } -------------------------------------------------------------------------------- /src/routes/UI/Button/components/index.less: -------------------------------------------------------------------------------- 1 | .button-page { 2 | button, 3 | .ripple-btn { 4 | margin: 8px; 5 | } 6 | } -------------------------------------------------------------------------------- /src/assets/fonts/icomoon/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/fonts/icomoon/icomoon.eot -------------------------------------------------------------------------------- /src/assets/fonts/icomoon/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/fonts/icomoon/icomoon.ttf -------------------------------------------------------------------------------- /src/assets/fonts/icomoon/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/assets/fonts/icomoon/icomoon.woff -------------------------------------------------------------------------------- /src/routes/Widgets/Gallery/components/index.less: -------------------------------------------------------------------------------- 1 | .gallery-page { 2 | .antui-waterfall { 3 | margin: 16px auto 0; 4 | } 5 | } -------------------------------------------------------------------------------- /src/components/Pages/style/images/balk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/components/Pages/style/images/balk.png -------------------------------------------------------------------------------- /src/components/Pages/style/images/saw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/components/Pages/style/images/saw.png -------------------------------------------------------------------------------- /src/components/Pages/style/images/error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/components/Pages/style/images/error.gif -------------------------------------------------------------------------------- /src/components/Button/style/index.less: -------------------------------------------------------------------------------- 1 | .antui-button-tooltip { 2 | padding-bottom: 2px; 3 | .ant-tooltip-arrow { 4 | display: none; 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/Pages/style/images/workers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LANIF-UI/dva-boot-admin/HEAD/src/components/Pages/style/images/workers.png -------------------------------------------------------------------------------- /src/components/WaterFall/style/index.less: -------------------------------------------------------------------------------- 1 | .antui-waterfall { 2 | .antui-waterfall-item { 3 | &:hover { 4 | z-index: 1; 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/routes/Blank/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'blank', 5 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "react-app", 3 | "parserOptions": { 4 | "ecmaFeatures": { 5 | "legacyDecorators": true 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | import Ripple from './Ripple'; 3 | 4 | Button.Ripple = Ripple; 5 | export default Button; -------------------------------------------------------------------------------- /src/routes/Login/service/index.js: -------------------------------------------------------------------------------- 1 | import $$ from 'cmn-utils'; 2 | 3 | export async function login(payload) { 4 | return $$.post('/user/login', payload); 5 | } -------------------------------------------------------------------------------- /src/components/Modal/index.js: -------------------------------------------------------------------------------- 1 | import ModalForm from './ModalForm'; 2 | import ModalTable from './ModalTable'; 3 | 4 | export { 5 | ModalForm, 6 | ModalTable 7 | } -------------------------------------------------------------------------------- /src/components/Notification/index.js: -------------------------------------------------------------------------------- 1 | import normal from './normal'; 2 | import antdNotice from './antdNotice'; 3 | 4 | export { 5 | normal, antdNotice 6 | }; 7 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'level1', 5 | }); -------------------------------------------------------------------------------- /src/routes/Widgets/Toolbar/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'toolbar', 5 | }); -------------------------------------------------------------------------------- /src/routes/Register/service/index.js: -------------------------------------------------------------------------------- 1 | import $$ from 'cmn-utils'; 2 | 3 | export async function register(payload) { 4 | return $$.post('/user/register', payload); 5 | } -------------------------------------------------------------------------------- /src/routes/Widgets/DataTable/components/index.less: -------------------------------------------------------------------------------- 1 | .datatable-page { 2 | .footer { 3 | text-align: center; 4 | background: #e9e9e9; 5 | padding: 14px; 6 | } 7 | } -------------------------------------------------------------------------------- /src/routes/Business/CRUD/routers/Detail/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'crudDetail', 5 | }); -------------------------------------------------------------------------------- /src/components/Charts/ECharts/index.js: -------------------------------------------------------------------------------- 1 | // 引入全部echarts包,若想按需加载则直接引EC.js 2 | import 'echarts'; 3 | import EC, { echarts } from './EC'; 4 | 5 | export { echarts }; 6 | export default EC; 7 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/routes/SubRoute/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'subRoute', 5 | }); -------------------------------------------------------------------------------- /src/routes/Widgets/Toolbar/components/index.less: -------------------------------------------------------------------------------- 1 | .toolbar-page { 2 | .antui-panel-body { 3 | background: #eee; 4 | } 5 | .toolbar-demo { 6 | background: #fff; 7 | border: 1px solid #e0e0e0; 8 | } 9 | } -------------------------------------------------------------------------------- /src/components/Editor/style/index.less: -------------------------------------------------------------------------------- 1 | .antui-editor { 2 | .w-e-toolbar { 3 | flex-wrap: wrap; 4 | .w-e-menu { 5 | &:hover { 6 | z-index: 10002 !important; 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/components/DataTable/index.js: -------------------------------------------------------------------------------- 1 | import DataTable, { Tip, Oper, Paging } from './DataTable'; 2 | import { Editable, EditableOper } from './Editable'; 3 | export { Tip, Oper, Paging, Editable, EditableOper }; 4 | export default DataTable; 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // 将设置放入此文件中以覆盖默认值和用户设置。 2 | { 3 | "editor.tabSize": 2, 4 | "editor.renderIndentGuides": true, 5 | "eslint.enable": true, 6 | "prettier.singleQuote": true, 7 | "javascript.implicitProjectConfig.experimentalDecorators": true, 8 | } -------------------------------------------------------------------------------- /src/components/SideBar/index.js: -------------------------------------------------------------------------------- 1 | import LeftSideBar from './LeftSideBar'; 2 | import RightSideBar from './RightSideBar'; 3 | 4 | export default { 5 | LeftSideBar, 6 | RightSideBar 7 | } 8 | 9 | export { 10 | LeftSideBar, 11 | RightSideBar 12 | } -------------------------------------------------------------------------------- /templates/routes/CRUD/prompts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "api_0", 4 | "message": "⭐️ 列表查询接口" 5 | }, 6 | { 7 | "name": "api_1", 8 | "message": "⭐️ 保存接口" 9 | }, 10 | { 11 | "name": "api_2", 12 | "message": "⭐️ 删除接口" 13 | } 14 | ] -------------------------------------------------------------------------------- /src/components/Loading/PageLoading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './PageLoading.less' 3 | 4 | /** 5 | * 加载效果示例 6 | */ 7 | export default ({loading, style = 'style1'}) => 8 | loading ?
: null; -------------------------------------------------------------------------------- /templates/routes/Blank/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: '<%=name %>', 5 | 6 | state: {}, 7 | 8 | subscriptions: {}, 9 | 10 | effects: {}, 11 | 12 | reducers: {} 13 | }); 14 | -------------------------------------------------------------------------------- /src/routes/Pages/404.js: -------------------------------------------------------------------------------- 1 | import { createRoute } from '@/utils/core'; 2 | import { P404 } from 'components/Pages'; 3 | 4 | const routesConfig = (app) => ({ 5 | title: '页面没有找到', 6 | component: P404, 7 | }); 8 | 9 | export default (app) => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/decorator/breadcrumb.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | /** 4 | * 在一个类上增加这个装饰器,可以改变页面的面包屑 5 | * @param {*} options 6 | */ 7 | const breadcrumb = options => WrappedComponent => { 8 | return WrappedComponent; 9 | }; 10 | 11 | export default breadcrumb; 12 | -------------------------------------------------------------------------------- /src/components/Form/model/custom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义表单元件, 3 | * 在column中如果需要用form控制 4 | * 5 | return form.getFieldDecorator('xxx')( 6 | // ... 7 | ); 8 | */ 9 | export default ({form, render, record, ...otherProps}) => { 10 | return render(record, form, otherProps); 11 | }; -------------------------------------------------------------------------------- /src/routes/Pages/403.js: -------------------------------------------------------------------------------- 1 | import { createRoute } from '@/utils/core'; 2 | import { P403 } from 'components/Pages'; 3 | 4 | const routesConfig = app => ({ 5 | path: '/403', 6 | title: '403', 7 | component: P403 8 | }); 9 | 10 | export default app => createRoute(app, routesConfig); 11 | -------------------------------------------------------------------------------- /src/routes/Pages/500.js: -------------------------------------------------------------------------------- 1 | import { createRoute } from '@/utils/core'; 2 | import { P500 } from 'components/Pages'; 3 | 4 | const routesConfig = app => ({ 5 | path: '/500', 6 | title: '500', 7 | component: P500 8 | }); 9 | 10 | export default app => createRoute(app, routesConfig); 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "chrome", 6 | "request": "launch", 7 | "name": "启动 Chrome 并打开 localhost", 8 | "url": "http://localhost:3000", 9 | "webRoot": "${workspaceFolder}" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/routes/UI/Mask/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/mask', 5 | title: '遮罩', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # By default port 3000 2 | # PORT=3000 3 | 4 | # When set to false, source maps are not generated for a production build. This solves out of memory (OOM) issues on some smaller machines. 5 | # GENERATE_SOURCEMAP=false 6 | 7 | # By default "CLIENT", set mock type [CLIENT|SERVER|DISABLED] 8 | REACT_APP_MOCK=SERVER 9 | -------------------------------------------------------------------------------- /src/routes/UI/Button/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/button', 5 | title: '按钮', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/UI/Editor/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/editor', 5 | title: '富文本', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/UI/Icon/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/icons', 5 | title: '图标', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/UI/Modal/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/modal', 5 | title: '模态窗', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/ScreenLock/index.js: -------------------------------------------------------------------------------- 1 | import { createRoute } from '@/utils/core'; 2 | import { ScreenLock } from 'components/Pages'; 3 | 4 | const routesConfig = app => ({ 5 | path: '/lock', 6 | title: '锁屏', 7 | component: ScreenLock 8 | }); 9 | 10 | export default app => createRoute(app, routesConfig); 11 | -------------------------------------------------------------------------------- /src/components/BaseComponent/BaseEventComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BaseComponent from './index'; 3 | 4 | /** 5 | * 带基本事件支持的组件基类 6 | */ 7 | class BaseEventComponent extends BaseComponent { 8 | render() { 9 | return
; 10 | } 11 | } 12 | 13 | export default BaseEventComponent; 14 | -------------------------------------------------------------------------------- /src/routes/UI/Image/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/image', 5 | title: 'Image page', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Print/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/print', 5 | title: '打印', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Result/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/result', 5 | title: '结果页', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/UI/Alerts/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/alerts', 5 | title: 'Alerts page', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/UI/CSSAnimate/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/animations', 5 | title: '动画', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/charts/g2', 5 | title: 'G2', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Column/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/column', 5 | title: 'Columns', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Gallery/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/gallery', 5 | title: '画廊', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | // 2 | // import PageLoading from './Loading/PageLoading'; 3 | // import Notification from './Notification'; 4 | // import TransferTree from './TransferTree'; 5 | // import Panel from './Panel'; 6 | 7 | // export { 8 | // PageLoading, 9 | // Notification, 10 | // TransferTree, 11 | // Panel, 12 | // } -------------------------------------------------------------------------------- /src/routes/Blank/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/blank', 5 | title: '空白页', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Banner/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/banner', 5 | title: 'Banner 管理', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/EC/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/charts/ec', 5 | title: 'ECharts', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/SearchBar/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/searchBar', 5 | title: '搜索条', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Dashboard/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/dashboard', 5 | title: '仪表盘', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/BaseComponent/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/baseComponent', 5 | title: '组件父类', 6 | component: dynamicWrapper(app, [], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Form/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/form', 5 | title: '表单', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/components/Charts/G2/index.js: -------------------------------------------------------------------------------- 1 | // 全局 G2 设置 2 | import { track, setTheme } from 'bizcharts'; 3 | import G2 from './G2'; 4 | 5 | const config = { 6 | defaultColor: '#1089ff', 7 | shape: { 8 | interval: { 9 | fillOpacity: 1 10 | } 11 | } 12 | }; 13 | 14 | track(false); 15 | setTheme(config); 16 | export default G2; 17 | -------------------------------------------------------------------------------- /src/routes/Login/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = (app) => ({ 4 | path: '/sign/login', 5 | title: 'Login', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default (app) => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/Toolbar/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/toolbar', 5 | title: '工具条', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/DataTable/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/datatable', 5 | title: '数据表格', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /templates/routes/CRUD/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/<%=name %>', 5 | title: '<%=title %>', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "target": "es2017", 5 | "baseUrl": "./", 6 | "paths": { 7 | "@/*": ["src/*"], 8 | "components/*": ["src/components/*"], 9 | "assets/*": ["src/assets/*"] 10 | } 11 | }, 12 | "exclude": ["node_modules", "build"] 13 | } 14 | -------------------------------------------------------------------------------- /src/routes/Register/components/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .register-page { 4 | .register-form-button { 5 | width: 100%; 6 | } 7 | .getCaptcha { 8 | width: 100%; 9 | } 10 | } 11 | 12 | .progress-pass.ant-progress { 13 | .ant-progress-bg { 14 | background-color: @warning-color; 15 | } 16 | } -------------------------------------------------------------------------------- /src/routes/Register/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = (app) => ({ 4 | path: '/sign/register', 5 | title: 'Register', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')), 7 | }); 8 | 9 | export default (app) => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/TransferTree/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/transferTree', 5 | title: '穿梭树', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /templates/routes/Blank/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = app => ({ 4 | path: '/<%=name %>', 5 | title: '<%=title %>', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default app => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /mock-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mock-server 利用 express 来生成真实的模拟数据 3 | * 为了解决之前mock数据时,只针对fetch有效,且在开发人员工具Network面板中无法看到请求的问题 4 | */ 5 | 6 | exports.addMockServer = () => config => { 7 | config.before = app => { 8 | app.get('/test/get', (req, res) => { 9 | res.json({ get: 'response get' }); 10 | }); 11 | }; 12 | return config; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /src/routes/Business/CRUD/index.js: -------------------------------------------------------------------------------- 1 | import {dynamicWrapper, createRoute} from '@/utils/core'; 2 | 3 | const routesConfig = (app) => ({ 4 | path: '/crud', 5 | title: 'CRUD示例', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')), 7 | exact: true 8 | }); 9 | 10 | export default (app) => createRoute(app, routesConfig); 11 | -------------------------------------------------------------------------------- /src/layouts/styles/user.less: -------------------------------------------------------------------------------- 1 | .user-layout { 2 | &.fixed.ant-layout, 3 | &.fixed .full-layout.ant-layout { 4 | position: absolute !important; 5 | top: 0; 6 | left: 0; 7 | right: 0; 8 | bottom: 0; 9 | .ant-layout-sider > .ant-layout-sider-children { 10 | overflow-y: auto; 11 | overflow-x: hidden; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/routes/Business/CRUD/routers/Detail/index.js: -------------------------------------------------------------------------------- 1 | import {dynamicWrapper, createRoute} from '@/utils/core'; 2 | 3 | const routesConfig = (app) => ({ 4 | path: '/crud/detail', 5 | title: 'CRUD示例-详情页路由', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')) 7 | }); 8 | 9 | export default (app) => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/routes/SubRoute/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | 3 | const routesConfig = (app) => ({ 4 | path: '/level-route/sub-route', 5 | title: '二级路由', 6 | component: dynamicWrapper(app, [import('./model')], () => import('./components')), 7 | }); 8 | 9 | export default (app) => createRoute(app, routesConfig); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /src/components/Video/Video.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style/index.less'; 3 | 4 | /** 5 | * 视频播放组件 6 | */ 7 | class Video extends Component { 8 | static defaultProps = { 9 | prefixCls: 'antui-video' 10 | }; 11 | 12 | render() { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default Video; -------------------------------------------------------------------------------- /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": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/routes/Widgets/DataTable/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | import PageHelper from '@/utils/pageHelper'; 3 | 4 | export default modelEnhance({ 5 | namespace: 'datatable', 6 | 7 | state: { 8 | pageData: PageHelper.create(), 9 | pageDataSort: PageHelper.create(), 10 | deptTreeData: [], 11 | dataList: { 12 | list: [] 13 | }, 14 | } 15 | }); -------------------------------------------------------------------------------- /src/components/Notification/Notification.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 通知接口,需要子类实现 3 | */ 4 | export default class Notification { 5 | static success(config) {/* 成功 */} 6 | 7 | static error(config) {/* 失败 */} 8 | 9 | static info(config) {/* 信息 */} 10 | 11 | static warning(config) {/* 警告 */} 12 | 13 | static warn(config) {/* 警告 */} 14 | 15 | static close(key) {/* 关闭 */} 16 | 17 | static destroy() {/* 销毁 */} 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Pages/index.js: -------------------------------------------------------------------------------- 1 | import './style/index.less'; 2 | import Coming from './Coming'; 3 | import ScreenLock from './ScreenLock'; 4 | import P403 from './403'; 5 | import P404 from './404'; 6 | import P500 from './500'; 7 | import Result from './Result'; 8 | 9 | export { Coming, ScreenLock, Result, P403, P404, P500 }; 10 | 11 | export default { 12 | Coming, 13 | ScreenLock, 14 | P403, 15 | P404, 16 | P500, 17 | Result 18 | }; 19 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/index.js: -------------------------------------------------------------------------------- 1 | import { dynamicWrapper, createRoute } from '@/utils/core'; 2 | import SubRoute from './routes/SubRoute'; 3 | 4 | const routesConfig = (app) => ({ 5 | path: '/level-route', 6 | title: '一级路由', 7 | component: dynamicWrapper(app, [import('./model')], () => import('./components')), 8 | childRoutes: [ 9 | SubRoute(app), 10 | ] 11 | }); 12 | 13 | export default (app) => createRoute(app, routesConfig); 14 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/components/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .level-route-page { 4 | .ant-layout-header { 5 | padding: 10px; 6 | background: #fff9c4; 7 | 8 | .sub-route-link { 9 | color: @primary-color; 10 | margin-right: 8px; 11 | &:hover { 12 | color: @primary-6; 13 | } 14 | &:active { 15 | color: @primary-4; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/routes/Widgets/Coming/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoute } from '@/utils/core'; 3 | import { Coming } from 'components/Pages'; 4 | 5 | const routesConfig = app => ({ 6 | path: '/coming', 7 | title: 'Coming Soon', 8 | component: () => ( 9 | 13 | ) 14 | }); 15 | 16 | export default app => createRoute(app, routesConfig); 17 | -------------------------------------------------------------------------------- /src/__mocks__/index.js: -------------------------------------------------------------------------------- 1 | // http://www.wheresrhys.co.uk/fetch-mock/api 2 | import packMock from '@/utils/packMock'; 3 | import user from './user'; 4 | import crud from './crud'; 5 | import tree from './tree'; 6 | import datatable from './datatable'; 7 | import charts from './charts'; 8 | import formData from './form'; 9 | /** 10 | * 加载mock文件 11 | * packMock(mock1[,mock2]) 12 | */ 13 | packMock( 14 | user, 15 | crud, 16 | tree, 17 | datatable, 18 | charts, 19 | formData, 20 | ); -------------------------------------------------------------------------------- /src/routes/Business/CRUD/routers/Detail/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | const { Content } = Layout; 6 | 7 | @connect() 8 | export default class extends BaseComponent { 9 | render() { 10 | return ( 11 | 12 | 详情页 13 | 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/assets/styles/transition.less: -------------------------------------------------------------------------------- 1 | 2 | .fade-enter { 3 | opacity: 0; 4 | transform: translateX(-30px); 5 | } 6 | .fade-enter-active { 7 | opacity: 1; 8 | transform: translateX(0px); 9 | transition: all .5s; 10 | } 11 | .fade-enter-done { 12 | opacity: 1; 13 | } 14 | 15 | .fade-exit { 16 | opacity: 1; 17 | transform: translateX(0px); 18 | } 19 | .fade-exit-active { 20 | opacity: 0; 21 | transform: translateX(30px); 22 | transition: all .5s; 23 | } 24 | .fade-exit-done { 25 | opacity: 0; 26 | } -------------------------------------------------------------------------------- /templates/routes/Blank/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import './index.less'; 6 | const { Content } = Layout; 7 | 8 | @connect() 9 | export default class extends BaseComponent { 10 | render() { 11 | return ( 12 | 13 | Route created success, happy work! 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/Blank/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import style from './index.module.less'; 6 | const { Content } = Layout; 7 | 8 | @connect() 9 | export default class extends BaseComponent { 10 | render() { 11 | return ( 12 | 13 | 空白页 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Pages/style/500.less: -------------------------------------------------------------------------------- 1 | .page500 { 2 | background: #fff; 3 | .ant-layout-content { 4 | overflow: hidden !important; 5 | } 6 | .error-block { 7 | margin: 50px 8 | } 9 | .center-block { 10 | margin: 0 100px; 11 | .error-title { 12 | font-size: 60px; 13 | font-weight: 800; 14 | color: #444; 15 | margin-bottom: 10px; 16 | } 17 | .error-subtitle { 18 | font-weight: 400; 19 | font-size: 32px; 20 | color: #444; 21 | margin-bottom: 80px; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Pages/style/403.less: -------------------------------------------------------------------------------- 1 | .page403 { 2 | background: #fff; 3 | .ant-layout-content { 4 | overflow: hidden !important; 5 | } 6 | .error-block { 7 | margin: 50px 8 | } 9 | .center-block { 10 | margin: 0 100px; 11 | .error-title { 12 | font-size: 60px; 13 | font-weight: 800; 14 | color: #444; 15 | margin-bottom: 10px; 16 | } 17 | .error-subtitle { 18 | font-weight: 400; 19 | font-size: 32px; 20 | color: #444; 21 | margin-bottom: 80px; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/decorator/coming.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import Coming from '../components/Pages/Coming'; 3 | 4 | /** 5 | * 在一个类上增加这个装饰器,表示这个类是一个未完成的功能, 6 | * 将会展示成一个即装到来的友好页面,可以设置倒计时时间 7 | * @param {*} options Coming 组件选项 8 | */ 9 | const coming = options => WrappedComponent => { 10 | return class extends PureComponent { 11 | render() { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | } 18 | }; 19 | }; 20 | 21 | export default coming; 22 | -------------------------------------------------------------------------------- /src/routes/Widgets/LevelRoute/routes/SubRoute/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import './index.less'; 6 | const { Content } = Layout; 7 | 8 | @connect() 9 | export default class extends BaseComponent { 10 | render() { 11 | return ( 12 | 13 | 14 |

二级路由

15 |
16 |
17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Mask/style/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | // mask 3 | .basic-mask { 4 | z-index: 9998; 5 | display: none; 6 | position: fixed; 7 | left: 0; 8 | right: 0; 9 | top: 0; 10 | bottom: 0; 11 | background: rgba(0, 0, 0, 0.6); 12 | .basic-mask-close { 13 | cursor: pointer; 14 | position: absolute; 15 | right: 12px; 16 | top: 12px; 17 | font-size: 3rem; 18 | color: #bdbdbd; 19 | transition: color .3s; 20 | &:hover { 21 | color: #ddd; 22 | } 23 | &:active { 24 | color: #bdbdbd; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/assets/styles/animate.css: -------------------------------------------------------------------------------- 1 | /* Animate.css Helpers */ 2 | .sparkline-delay { 3 | position: absolute; 4 | bottom: 0; 5 | width: 40px; 6 | height: 35px; 7 | line-height: 24px; 8 | } 9 | .animated.animated-delay { 10 | animation-delay: .6s !important; 11 | } 12 | .animated.animated-short { 13 | animation-duration: 0.6s !important; 14 | } 15 | .animated.animated-shorter { 16 | animation-duration: 0.3s !important; 17 | } 18 | .animated.animated-long { 19 | animation-duration: 1.4s !important; 20 | } 21 | .animated.animated-longer { 22 | animation-duration: 2s !important; 23 | } -------------------------------------------------------------------------------- /src/routes/Widgets/Toolbar/components/columns.js: -------------------------------------------------------------------------------- 1 | export default (self) => [ 2 | { 3 | title: '姓名', 4 | name: 'name', 5 | searchItem: { 6 | group: '1', 7 | }, 8 | }, 9 | { 10 | title: '角色', 11 | name: 'role', 12 | dict: [ 13 | {code: '1', codeName: '管理员'}, 14 | {code: '2', codeName: '编辑'}, 15 | {code: '3', codeName: '游客'}, 16 | ], 17 | searchItem: { 18 | type: 'select', 19 | group: '1', 20 | } 21 | }, 22 | { 23 | title: '生日', 24 | name: 'birthday', 25 | searchItem: { 26 | type: 'date', 27 | width: 120, 28 | } 29 | } 30 | ]; -------------------------------------------------------------------------------- /src/components/Pages/style/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | @import './screenLock.less'; 3 | @import './coming.less'; 4 | @import './403.less'; 5 | @import './404.less'; 6 | @import './500.less'; 7 | @import './result.less'; 8 | 9 | .backhome { 10 | position: absolute; 11 | border: 1px solid #89949b; 12 | border-radius: 50%; 13 | width: 40px; 14 | height: 40px; 15 | line-height: 0px; 16 | color: #89949b; 17 | top: 20px; 18 | font-size: 48px; 19 | left: 20px; 20 | overflow: hidden; 21 | &:hover { 22 | color: @primary-color; 23 | border-color: @primary-color; 24 | background: #fff; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/layouts/UserLayout.js: -------------------------------------------------------------------------------- 1 | import './styles/user.less'; 2 | import React from 'react'; 3 | import { connect } from 'dva'; 4 | import { Layout } from 'antd'; 5 | import { router } from 'dva'; 6 | const { Switch } = router; 7 | const { Content } = Layout; 8 | 9 | @connect() 10 | export default class UserLayout extends React.PureComponent { 11 | render() { 12 | const {routerData} = this.props; 13 | const {childRoutes} = routerData; 14 | 15 | return ( 16 | 17 | 18 | {childRoutes} 19 | 20 | 21 | ); 22 | } 23 | } -------------------------------------------------------------------------------- /src/routes/Widgets/Form/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | 3 | export default modelEnhance({ 4 | namespace: 'form', 5 | 6 | state: { 7 | treeData: [], 8 | }, 9 | 10 | subscriptions: { 11 | setup({ dispatch, history }) { 12 | history.listen(({ pathname }) => { 13 | if (pathname === '/form') { 14 | dispatch({ 15 | type: '@request', 16 | afterResponse: resp => resp.data, 17 | payload: { 18 | valueField: 'treeData', 19 | url: '/tree/getAsyncTreeSelect', 20 | } 21 | }); 22 | } 23 | }); 24 | } 25 | }, 26 | }); -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | - [如何开始](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/start.md) 2 | - [全局配置](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/config.md) 3 | - [modelEnhance用法](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/modelEnhance.md) 4 | - [pageHelper用法](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/pageHelper.md) 5 | - [组件](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/components.md) 6 | - [接口数据模拟](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/mock.md) 7 | - [打包](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/build.md) 8 | - [FAQs](https://github.com/LANIF-UI/dva-boot-admin/blob/master/docs/faqs.md) -------------------------------------------------------------------------------- /templates/routes/CRUD/components/columns.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DataTable from 'components/DataTable'; 3 | import Icon from 'components/Icon'; 4 | import Button from 'components/Button'; 5 | 6 | export default (self) => [ 7 | { 8 | title: '操作', 9 | tableItem: { 10 | width: 180, 11 | render: (text, record) => ( 12 | 13 | 16 | 19 | 20 | ) 21 | } 22 | } 23 | ]; 24 | -------------------------------------------------------------------------------- /src/routes/Register/model/index.js: -------------------------------------------------------------------------------- 1 | import modelEnhance from '@/utils/modelEnhance'; 2 | import { register } from '../service'; 3 | 4 | export default modelEnhance({ 5 | namespace: 'register', 6 | 7 | state: { 8 | status: undefined, 9 | }, 10 | 11 | effects: { 12 | *submit({ payload }, { call, put }) { 13 | console.log(payload) 14 | const response = yield call(register, payload); 15 | yield put({ 16 | type: 'registerHandle', 17 | payload: response, 18 | }); 19 | }, 20 | }, 21 | 22 | reducers: { 23 | registerHandle(state, { payload }) { 24 | return { 25 | ...state, 26 | status: payload.status, 27 | }; 28 | }, 29 | }, 30 | }); -------------------------------------------------------------------------------- /src/components/Clock/style/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .antui-clock { 4 | > .date { 5 | font-size: 20px; 6 | letter-spacing: 0.3px; 7 | text-align: right; 8 | } 9 | > ul { 10 | text-align: right; 11 | list-style: none; 12 | margin: 0; 13 | padding: 0; 14 | > li { 15 | display: inline; 16 | font-family: Roboto; 17 | font-size: 60px; 18 | font-weight: 900; 19 | &.point { 20 | padding: 0 4px; 21 | animation: 1s ease 0s normal none infinite flash; 22 | position: relative; 23 | top: -5px; 24 | } 25 | } 26 | } 27 | } 28 | 29 | @keyframes flash{ 30 | 0% {opacity:1.0;} 31 | 50% {opacity:0; } 32 | 100% {opacity:1.0;} 33 | } -------------------------------------------------------------------------------- /src/components/SkinToolbox/SideBarBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Radio, Tag } from 'antd'; 3 | const RadioGroup = Radio.Group; 4 | 5 | export default ({ theme, onChange }) => ( 6 | 10 | 11 | 深灰 12 | 13 | 14 | 浅灰 15 | 16 | 17 | Antd深蓝 18 | 19 | 20 | 21 | Antd亮白 22 | 23 | 24 | 25 | ) -------------------------------------------------------------------------------- /src/components/SkinToolbox/LayoutBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'antd'; 3 | const CheckGroup = Checkbox.Group; 4 | 5 | /** 6 | * 几种常用布局 7 | */ 8 | export default ({ theme, onChange }) => ( 9 | 10 | 11 | 固定头部 12 | 13 | 14 | 固定边栏 15 | 16 | 17 | 标签模式 18 | 19 | 20 | 固定面包屑 21 | 22 | 23 | 隐藏面包屑 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /src/__mocks__/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 表单form示例中的模拟数据 3 | */ 4 | export default ({ fetchMock, delay, mock, toSuccess, toError }) => { 5 | // 模拟自动完成反回的数据 6 | return { 7 | '/api/form/autoComplete': options => { 8 | const body = JSON.parse(options.body); 9 | const userName = body; 10 | 11 | return toSuccess( 12 | mock({ 13 | 'list|3-10': [{ 14 | 'id': '@id', 15 | 'name': userName + '@cword("零一二三四五六七八九十", 1, 2)', // 张三,赵四 16 | 'age|1-100': 100, // 100以内随机整数 17 | 'birthday': '@date("yyyy-MM-dd")', // 日期 18 | 'city': '@city(true)', // 中国城市 19 | 'phone': /^1[385][1-9]\d{8}/, // 手机号 20 | 'content': '@csentence', 21 | }] 22 | }), 23 | 400 24 | ); 25 | } 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/routes/UI/Modal/components/columns.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Oper } from 'components/DataTable'; 3 | import Icon from 'components/Icon'; 4 | import { Button } from 'antd'; 5 | 6 | export default [ 7 | { 8 | title: '名称', 9 | name: 'name', 10 | tableItem: {} 11 | }, 12 | { 13 | title: '年龄', 14 | name: 'age', 15 | tableItem: {} 16 | }, 17 | { 18 | title: '地址', 19 | name: 'address', 20 | tableItem: {} 21 | }, 22 | { 23 | title: '操作', 24 | tableItem: { 25 | width: 180, 26 | render: (text, record) => ( 27 | 28 | 31 | 34 | 35 | ) 36 | } 37 | } 38 | ]; -------------------------------------------------------------------------------- /src/layouts/styles/card.less: -------------------------------------------------------------------------------- 1 | .card-layout { 2 | .ant-menu { 3 | font-size: 16px; 4 | } 5 | &.full-layout .router-page, 6 | &.full-layout .router-page > .ant-layout { 7 | min-height: ~'calc(100vh - 48px)'; 8 | > .ant-layout-content { 9 | overflow: auto; 10 | } 11 | } 12 | 13 | /* 应用布局&布局设置 */ 14 | &.full-layout { 15 | // 固定所有元素,经典管理界面样式,即固定头,侧栏,面包屑 16 | &.fixed.ant-layout, 17 | &.fixed .full-layout.ant-layout { 18 | position: absolute !important; 19 | top: 0; 20 | left: 0; 21 | right: 0; 22 | bottom: 0; 23 | } 24 | // 只固定头部 25 | &:not(.fixed)&.fixed-header { 26 | > .ant-layout-header { 27 | position: fixed; 28 | z-index: 200; 29 | left: 0; 30 | right: 0; 31 | } 32 | > .ant-layout-content { 33 | padding-top: 46px; 34 | overflow: auto; 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /docs/pageHelper.md: -------------------------------------------------------------------------------- 1 | # 分页功能 2 | PageHelper只是帮我们拼装分页和查询条件,并不会执行实际的查询 3 | 4 | ## 如何使用 5 | 6 | ```js 7 | // 创建pageInfo对像 8 | cosnt pageInfo = PageHelper.create(); 9 | 10 | // 生成第一页每页十条的查询参数 11 | pageInfo.startPage(); // { pageNum: 1, pageSize: 10 } 12 | 13 | // 生成第二页每页二十条的查询参数 14 | pageInfo.startPage(2, 20); // { pageNum: 2, pageSize: 20 } 15 | 16 | // 跳转到第三页 17 | pageInfo.jumpPage(3); // { pageNum: 3, .... } 18 | 19 | // 跳转到下一页,上一页 20 | pageInfo.nextPage(); // { pageNum: 4, .... } 21 | pageInfo.prevPage(); // { pageNum: 3, .... } 22 | 23 | // 跳转到第四页并带着查询条件 24 | pageInfo.filter({ name: 'jonn' }).jumpPage(4); // { pageNum: 4, filters: { name: 'jonn' }, .... } 25 | // 当第二个条件为true时会带着之前的查询条件 26 | pageInfo.filter({ age: '18' }, true) // { pageNum: 4, filters: { name: 'jonn', age: '18' }, .... } 27 | 28 | // 按姓名升序,年龄降序生成 29 | pageInfo.sortBy({name: 'asc', age: 'desc'}); // { sorts: {name: 'asc', age: 'desc'}, .... } 30 | 31 | ``` -------------------------------------------------------------------------------- /src/components/Loading/PageLoading.less: -------------------------------------------------------------------------------- 1 | @base-line-height: 25px; 2 | @color: #000; 3 | @spin-duration: 1s; 4 | 5 | @keyframes spin { 6 | 0% { 7 | transform: rotate(0deg); 8 | } 9 | 100% { 10 | transform: rotate(360deg); 11 | } 12 | } 13 | 14 | .loading-spinner { 15 | position: absolute; 16 | top: 50%; 17 | left: 50%; 18 | border-radius: 50%; 19 | animation: spin @spin-duration infinite linear; 20 | } 21 | 22 | .loading-spinner-style1 { 23 | border-radius: 50%; 24 | margin: -@base-line-height/2 0 0 -@base-line-height/2; 25 | width: @base-line-height; 26 | height: @base-line-height; 27 | border: 4px solid tint(@color, 90%); 28 | border-top-color: @color; 29 | } 30 | 31 | .loading-spinner-style2 { 32 | margin: -@base-line-height 0 0 -@base-line-height; 33 | width: @base-line-height*2; 34 | height: @base-line-height*2; 35 | box-shadow: 0 1px 0 @color; 36 | background-color: transparent; 37 | } 38 | -------------------------------------------------------------------------------- /src/setupProxy.js: -------------------------------------------------------------------------------- 1 | // https://facebook.github.io/create-react-app/docs/proxying-api-requests-in-development#configuring-the-proxy-manually 2 | // https://github.com/chimurai/http-proxy-middleware 3 | 4 | const proxy = require('http-proxy-middleware'); 5 | 6 | module.exports = function(app) { 7 | app.use( 8 | proxy('/api/sub', { 9 | target: 'http://localhost:8080', 10 | changeOrigin: true, 11 | pathRewrite: { 12 | '^/api': '' 13 | } 14 | }) 15 | ); 16 | // app.use( 17 | // proxy('/api', { 18 | // target: 'http://aaa:1000', 19 | // changeOrigin: true, 20 | // pathRewrite: { 21 | // '^/api': '' 22 | // } 23 | // }) 24 | // ); 25 | // app.use( 26 | // proxy('/xxx', { 27 | // target: 'http://bbb:2000', 28 | // changeOrigin: true, 29 | // pathRewrite: { 30 | // '^/xxx': '' 31 | // } 32 | // }) 33 | // ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/Button/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button, Tooltip } from 'antd'; 4 | import './style/index.less'; 5 | 6 | const ButtonGroup = Button.Group; 7 | /** 8 | * Button 9 | */ 10 | export default class extends React.Component { 11 | static Group = ButtonGroup; 12 | 13 | static propTypes = { 14 | /** 15 | * 是否用Tooltip组件显示提示信息 16 | */ 17 | tooltip: PropTypes.oneOfType([PropTypes.bool, PropTypes.node]) 18 | }; 19 | 20 | static defaultProps = { 21 | prefixCls: 'antui-button-tooltip', 22 | }; 23 | 24 | render() { 25 | const { tooltip, prefixCls, ...otherProps } = this.props; 26 | return tooltip ? ( 27 | 28 | 11 | 12 | 13 | ); 14 | 15 | const footer = ( 16 | 17 |

18 | Need More Help? 19 |

20 |

21 | Misc question two? Response Link 22 |

23 |
24 | ); 25 | 26 | const extra =
Yoursite.com
; 27 | 28 | return ( 29 | 30 | 31 | 38 | 激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。 39 | 40 | 41 | 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Image/util.js: -------------------------------------------------------------------------------- 1 | export const download = (url, options = {}) => { 2 | if (!url) return; 3 | 4 | let getBlobImage = (img) => { 5 | let canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'); 6 | canvas.width = img.width; 7 | canvas.height = img.height; 8 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height); 9 | return new Promise((resolve) => { 10 | canvas.toBlob((blob) => { 11 | resolve(blob); 12 | }); 13 | }); 14 | }; 15 | 16 | let img = new Image(); 17 | img.crossOrigin = 'anonymous'; 18 | img.onload = () => { 19 | let promise = getBlobImage(img); 20 | promise.then((blob) => { 21 | let dLink = document.createElement('a'); 22 | dLink.download = options.alt || ''; 23 | let downloadImgUrl = window.URL.createObjectURL(blob); 24 | dLink.href = downloadImgUrl; 25 | dLink.onclick = () => { 26 | window.requestAnimationFrame(() => { 27 | window.URL.revokeObjectURL(downloadImgUrl); 28 | downloadImgUrl = null; 29 | }); 30 | }; 31 | dLink.click(); 32 | }); 33 | }; 34 | // 在URL后添加随机数以避免浏览器缓存,使crossOrigin生效 35 | img.src = url + '?' + +new Date(); 36 | }; -------------------------------------------------------------------------------- /src/routes/Widgets/BaseComponent/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout, Button } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Panel from 'components/Panel'; 6 | const { Content } = Layout; 7 | 8 | @connect() 9 | export default class extends BaseComponent { 10 | render() { 11 | return ( 12 | 13 | 14 | 15 |

BaseComponent

16 |

所有路由页面都可以使用的基类,可以提取公共方法放到此类中,如基本的CRUD方法,路由跳转,基本弹窗等

17 |

Notice

18 | 19 | 20 | 21 | 22 | 23 | 24 |

Router

25 | 26 |
27 |
28 |
29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/enquireScreen.js: -------------------------------------------------------------------------------- 1 | import enquireJS from 'enquire.js'; 2 | 3 | const mobileQuery = 'only screen and (max-width: 767.99px)'; 4 | const tabletQuery = 5 | 'only screen and (min-width: 768px) and (max-width: 1024px)'; 6 | const desktopQuery = 'only screen and (min-width: 1025px)'; 7 | 8 | // 是否是匹配移动窗口大小 9 | export function enquireIsMobile(cb, handlers) { 10 | return enquireScreen(mobileQuery, cb, handlers); 11 | } 12 | 13 | // 是否是匹配Pad窗口大小 14 | export function enquireIsTablet(cb, handlers) { 15 | return enquireScreen(tabletQuery, cb, handlers); 16 | } 17 | 18 | // 是否是匹配桌面窗口大小 19 | export function enquireIsDesktop(cb, handlers) { 20 | return enquireScreen(desktopQuery, cb, handlers); 21 | } 22 | 23 | /** 24 | * enquire.js封装 25 | * @param {*} query media query 26 | * @param {*} cb callback function 27 | * @param {*} handlers enquire.js handlers 28 | * @return 返回 unregister 函数 29 | */ 30 | export function enquireScreen(query, cb, handlers) { 31 | const handler = handlers || { 32 | match: () => { 33 | cb && cb(true); 34 | }, 35 | unmatch: () => { 36 | cb && cb(false); 37 | } 38 | }; 39 | enquireJS.register(query, handler); 40 | return _ => enquireJS.unregister(query); 41 | } 42 | 43 | export default enquireJS; 44 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const { 2 | override, 3 | addDecoratorsLegacy, 4 | disableEsLint, 5 | addBundleVisualizer, 6 | addWebpackAlias, 7 | adjustWorkbox, 8 | addLessLoader, 9 | fixBabelImports, 10 | overrideDevServer 11 | } = require('customize-cra'); 12 | const path = require('path'); 13 | const { addMockServer } = require('./mock-server'); 14 | 15 | module.exports = { 16 | webpack: override( 17 | addDecoratorsLegacy(), 18 | disableEsLint(), 19 | addBundleVisualizer({}, true), 20 | addWebpackAlias({ 21 | '@': path.resolve(__dirname, 'src'), 22 | components: path.resolve(__dirname, 'src/components'), 23 | assets: path.resolve(__dirname, 'src/assets') 24 | }), 25 | adjustWorkbox(wb => 26 | Object.assign(wb, { 27 | skipWaiting: true, 28 | exclude: (wb.exclude || []).concat('index.html') 29 | }) 30 | ), 31 | fixBabelImports('import', { 32 | libraryName: 'antd', 33 | style: true 34 | }), 35 | addLessLoader({ 36 | localIdentName: '[local]--[hash:base64:8]', 37 | javascriptEnabled: true, 38 | modifyVars: {} 39 | }) 40 | ), 41 | devServer: overrideDevServer( 42 | process.env.MOCK === 'SERVER' ? addMockServer() : null 43 | ) 44 | }; 45 | -------------------------------------------------------------------------------- /docs/build.md: -------------------------------------------------------------------------------- 1 | # Build 打包部署 2 | 3 | > `yarn run build` or `npm run build` 4 | 5 | v1.2.0以后,基于最新 react-script v2 的打包部署 6 | 7 | ## 打包体积过大 & 禁用SourceMap 8 | 9 | 在`.env`文件里加入: 10 | ```js 11 | GENERATE_SOURCEMAP=false 12 | ``` 13 | 14 | ## 发布路径 15 | 16 | 设置`package.json`中的`homepage`的值,如把项目发部到服务器的`dva-boot-admin`文件夹下为: 17 | ```json 18 | "homepage": "/dva-boot-admin" 19 | ``` 20 | 若发布到服务器的跟目录下为 21 | ```json 22 | "homepage": "/" 23 | ``` 24 | 使用相对路径 25 | ```json 26 | "homepage": "." 27 | ``` 28 | 配置错则有可能加载不到相关资源 29 | 30 | ## 使用`browser history`,`nginx`的额外设置 31 | 32 | 当使用browser history时,需要在`nginx.conf`下设置所有页面都指向index.html 33 | ```bash 34 | server { 35 | // ... 36 | location / { 37 | index index.html; 38 | # 如果部署到/dva-boot-admin目录下 39 | # try_files $uri $uri/ /dva-boot-admin/index.html; 40 | # 如果部署到根目录 41 | try_files $uri $uri/ /index.html; 42 | } 43 | // ... 44 | } 45 | ``` 46 | 47 | ## 如何配反向代理 nginx (生产环境下) 48 | 49 | 1. 打开nginx配置文件`nginx.conf`,在`server`下增加(按自已项目设置): 50 | ```js 51 | location /api/v1/ { 52 | proxy_pass http://192.168.202.11/v1/; 53 | } 54 | 55 | location /api/v2/ { 56 | proxy_pass http://192.168.202.12/v2/; 57 | } 58 | 59 | location /api/ { 60 | proxy_pass http://192.168.202.13/; 61 | } 62 | ``` 63 | 更多配置自行查找... 64 | 65 | 2. 重启nginx -------------------------------------------------------------------------------- /src/components/SideBar/RightSideBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import { Layout, Drawer } from 'antd'; 4 | import './style/index.less'; 5 | const { Sider } = Layout; 6 | 7 | class RightSideBar extends Component { 8 | static defaultProps = { 9 | fixed: true, 10 | theme: '' 11 | }; 12 | 13 | render() { 14 | const { fixed, theme, collapsed, isMobile, onCollapse } = this.props; 15 | 16 | const classnames = cx('sidebar-right', { 17 | affix: !!fixed, 18 | 'sidebar-right-close': collapsed, 19 | [theme]: !!theme 20 | }); 21 | 22 | const siderBar = ( 23 | 31 |
32 |
33 | ); 34 | 35 | return isMobile ? ( 36 | 43 | {siderBar} 44 | 45 | ) : ( 46 | siderBar 47 | ); 48 | } 49 | } 50 | 51 | export default RightSideBar; 52 | -------------------------------------------------------------------------------- /src/routes/UI/Image/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout, Button } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Panel from 'components/Panel'; 6 | import Image from 'components/Image'; 7 | const { Content } = Layout; 8 | 9 | @connect() 10 | export default class extends BaseComponent { 11 | render() { 12 | return ( 13 | 14 | 15 | 16 |

图片相关组件

17 |
18 | 19 | 28 | 29 |
30 |
31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/NavBar/style/searchbox.less: -------------------------------------------------------------------------------- 1 | .search-box { 2 | padding-top: 45px; 3 | background: linear-gradient(to bottom, rgba(0,0,0,0.95) 20%, rgba(0,0,0,0.75) 60%, transparent) !important; 4 | .search-box-input { 5 | height: 160px; 6 | width: 80%; 7 | margin: 0 auto; 8 | position: relative; 9 | display: flex; 10 | align-items: center; 11 | input { 12 | flex: auto; 13 | transition: all 0.2s; 14 | outline: 0; 15 | font-size: 6rem; 16 | font-weight: 100; 17 | width: 100%; 18 | background: none; 19 | color: #fff; 20 | border: 0; 21 | @media screen and (max-width: 1000px) { 22 | font-size: 3rem; 23 | } 24 | } 25 | @media screen and (max-width: 1000px) { 26 | height: 80px; 27 | } 28 | .search-box-btn { 29 | flex: none; 30 | font-size: 6rem; 31 | color: #444; 32 | cursor: pointer; 33 | &:hover { 34 | color: #888; 35 | } 36 | &:active { 37 | color: #444; 38 | } 39 | @media screen and (max-width: 1000px) { 40 | font-size: 3rem; 41 | } 42 | } 43 | } 44 | .search-box-content { 45 | width: 80%; 46 | margin: 10px auto; 47 | .ant-radio-wrapper { 48 | font-size: 15px; 49 | color: #999; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/components/Notification/antdNotice.js: -------------------------------------------------------------------------------- 1 | import { notification } from 'antd'; 2 | import $$ from 'cmn-utils'; 3 | import Notification from './Notification'; 4 | import './antdNotice.less'; 5 | 6 | const prefixCls = 'antui-notification'; 7 | const defaultConfig = { 8 | }; 9 | 10 | function notice(config, type, title) { 11 | if ($$.isObject(config)) { 12 | notification[type]({ 13 | className: `${prefixCls} ${prefixCls}-${type}`, 14 | ...defaultConfig, 15 | ...config 16 | }); 17 | } else { 18 | notification[type]({ 19 | className: `${prefixCls} ${prefixCls}-${type}`, 20 | message: title, 21 | description: config, 22 | ...defaultConfig 23 | }); 24 | } 25 | } 26 | 27 | export default class extends Notification { 28 | static success(config) { 29 | notice(config, 'success', '成功'); 30 | } 31 | 32 | static error(config) { 33 | notice(config, 'error', '出错了'); 34 | } 35 | 36 | static info(config) { 37 | notice(config, 'info', '提示'); 38 | } 39 | 40 | static warning(config) { 41 | notice(config, 'warning', '注意'); 42 | } 43 | 44 | static warn(config) { 45 | notice(config, 'warning', '注意'); 46 | } 47 | 48 | static close(key) { 49 | notification.close(key); 50 | } 51 | 52 | static destroy() { 53 | notification.destroy(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/routes/Widgets/Banner/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import BannerMng from 'components/BannerMng'; 6 | const { Content } = Layout; 7 | 8 | @connect() 9 | export default class extends BaseComponent { 10 | state = { 11 | dataSource: [ 12 | {title: '1', link: '/abc', file: 'https://files.vlad.studio/sequoia/joy/merry_xmas_moon/thumbs/1024x1024.jpg'}, 13 | {title: '2', link: '/abd', file: 'https://files.vlad.studio/sequoia/joy/autumn_gradient/thumbs/1024x1024.jpg'}, 14 | {title: '3', link: '/abe', file: 'https://files.vlad.studio/sequoia/joy/tinyliving/thumbs/1024x1024.jpg'}, 15 | {title: '4', link: '/abf', file: 'https://files.vlad.studio/sequoia/joy/little_quetzal/thumbs/1024x1024.jpg'} 16 | ] 17 | } 18 | 19 | onChange = datas => { 20 | console.log(datas); 21 | } 22 | 23 | render() { 24 | const { dataSource } = this.state; 25 | return ( 26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/BannerMng/Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import objectAssign from 'object-assign'; 4 | import Form from 'components/Form'; 5 | 6 | const formItemLayout = { 7 | labelCol: { span: 3 }, 8 | wrapperCol: { span: 16 } 9 | }; 10 | 11 | class BannerForm extends React.Component { 12 | static propTypes = { 13 | record: PropTypes.object, 14 | onSubmit: PropTypes.func, 15 | onCancel: PropTypes.func, 16 | columns: PropTypes.array 17 | }; 18 | 19 | handleSubmit = values => { 20 | const { record, onSubmit, imageKey } = this.props; 21 | if (values[imageKey] && values[imageKey].length) { 22 | values[imageKey] = 23 | values[imageKey][0].url || values[imageKey][0].thumbUrl; 24 | onSubmit && onSubmit(objectAssign({}, record, values)); 25 | } else { 26 | console.warn('not found a image field!'); 27 | } 28 | }; 29 | 30 | render() { 31 | const { columns, record } = this.props; 32 | return ( 33 |
34 |
41 |
42 | ); 43 | } 44 | } 45 | 46 | export default BannerForm; 47 | -------------------------------------------------------------------------------- /src/components/SkinToolbox/NavBarBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Radio, Tag } from 'antd'; 3 | const RadioGroup = Radio.Group; 4 | 5 | export default ({ theme, onChange }) => ( 6 | 10 | 11 | 默认 12 | 13 | 14 | 亮白 15 | 16 | 17 | 宝石 18 | 19 | 20 | 阳光 21 | 22 | 23 | 热情 24 | 25 | 26 | 典雅 27 | 28 | 29 | 专业 30 | 31 | 32 | 生命 33 | 34 | 35 | 商务 36 | 37 | 38 | 深蓝 39 | 40 | 41 | ) -------------------------------------------------------------------------------- /src/routes/Widgets/Column/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { BulbOutlined } from '@ant-design/icons'; 4 | import { Layout, Button } from 'antd'; 5 | import BaseComponent from 'components/BaseComponent'; 6 | import Panel from 'components/Panel'; 7 | const { Content } = Layout; 8 | 9 | @connect() 10 | export default class extends BaseComponent { 11 | render() { 12 | return ( 13 | 14 | 15 | 16 |

Column 语法

17 |

18 | 通过配制Column可同时生成我们页面中的三大块元素、搜索条(高级搜索)组件、新增修改的表单组件、带分页的数据表格组件。 19 | 25 | [配置说明] 26 | 27 |

28 | 35 |
36 |
37 |
38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/routes/Widgets/Result/components/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { Result } from 'components/Pages'; 3 | import { Layout, Button } from 'antd'; 4 | const { Content } = Layout; 5 | 6 | export default class extends Component { 7 | render() { 8 | const actions = ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | 16 | const footer = ( 17 | 18 |

19 | Need More Help? 20 |

21 |

22 | Misc question two? Response Link 23 |

24 |
25 | ); 26 | 27 | const extra =
Yoursite.com
; 28 | 29 | return ( 30 | 31 | 32 | 40 | 提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 41 | Message 全局提示反馈即可。 42 | 本文字区域可以展示简单的补充说明,如果有类似展示 43 | “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。 44 | 45 | 46 | 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/routes/UI/Button/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Button from 'components/Button'; 6 | import Panel from 'components/Panel'; 7 | import './index.less'; 8 | const { Content } = Layout; 9 | const Ripple = Button.Ripple; 10 | 11 | @connect() 12 | export default class extends BaseComponent { 13 | render() { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | Default 26 | Primary 27 | Danger 28 |
29 |
30 | Default 31 | Primary 32 | Danger 33 |
34 |
35 |
36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/BaseComponent/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | import config from '@/config'; 5 | 6 | class BaseComponent extends React.Component { 7 | notice = config.notice; // 消息通知 8 | 9 | /** 10 | * history api 路由跳转 11 | */ 12 | get history() { 13 | return this.props.history; 14 | } 15 | 16 | /** 17 | * 新增 18 | */ 19 | onAdd = () => { 20 | this.setState({ 21 | record: null, 22 | visible: true 23 | }); 24 | }; 25 | 26 | /** 27 | * 修改 28 | * @param {object} 表单记录 29 | */ 30 | onUpdate = record => { 31 | this.setState({ 32 | record, 33 | visible: true 34 | }); 35 | }; 36 | 37 | /** 38 | * 删除 39 | * @param {object | array} 表单记录, 批量删除时为数组 40 | */ 41 | onDelete = record => { 42 | if (!record) return; 43 | if ($$.isArray(record) && !record.length) return; 44 | 45 | const content = `您是否要删除这${ 46 | $$.isArray(record) ? record.length : '' 47 | }项?`; 48 | 49 | Modal.confirm({ 50 | title: '注意', 51 | content, 52 | onOk: () => { 53 | this.handleDelete($$.isArray(record) ? record : [record]); 54 | }, 55 | onCancel() {} 56 | }); 57 | }; 58 | 59 | handleAdd() { 60 | /* 子类重写 */ 61 | } 62 | handleUpdate() { 63 | /* 子类重写 */ 64 | } 65 | handleDelete(records) { 66 | /* 子类重写 */ 67 | } 68 | } 69 | 70 | export default BaseComponent; 71 | -------------------------------------------------------------------------------- /src/components/Form/model/number.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { InputNumber } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | /** 5 | * 数字输入框元件 6 | */ 7 | export default ({ 8 | form, 9 | name, 10 | formFieldOptions = {}, 11 | record, 12 | initialValue, 13 | normalize, 14 | rules, 15 | onChange, 16 | preview, 17 | placeholder, 18 | getPopupContainer, 19 | type, 20 | ...otherProps 21 | }) => { 22 | const { getFieldDecorator } = form; 23 | 24 | let initval = initialValue; 25 | 26 | if (record) { 27 | initval = record[name]; 28 | } 29 | 30 | // 如果存在初始值 31 | if (initval !== null && typeof initval !== 'undefined') { 32 | if ($$.isFunction(normalize)) { 33 | formFieldOptions.initialValue = normalize(initval); 34 | } else { 35 | formFieldOptions.initialValue = initval; 36 | } 37 | } 38 | 39 | if (preview) { 40 | return
{initval || ''}
; 41 | } 42 | 43 | // 如果有rules 44 | if (rules && rules.length) { 45 | formFieldOptions.rules = rules; 46 | } 47 | 48 | // 如果需要onChange 49 | if (typeof onChange === 'function') { 50 | formFieldOptions.onChange = value => onChange(form, value); // form, value, event 51 | } 52 | 53 | delete otherProps.render; 54 | 55 | const props = { 56 | placeholder: placeholder || `请输入${otherProps.title}`, 57 | ...otherProps 58 | }; 59 | 60 | return getFieldDecorator(name, formFieldOptions)(); 61 | }; 62 | -------------------------------------------------------------------------------- /src/layouts/styles/tabs.less: -------------------------------------------------------------------------------- 1 | .tabs-layout { 2 | .lanif-tabs-content { 3 | background: #fff; 4 | > .ant-tabs-bar { 5 | margin-bottom: 0; 6 | padding: 4px 8px; 7 | .ant-tabs-nav-container { 8 | display: flex; 9 | align-items: center; 10 | } 11 | .ant-tabs-tab { 12 | border: 1px solid #e8e8e8; 13 | border-radius: 4px; 14 | line-height: 32px; 15 | .tab-title { 16 | display: inline-block; 17 | &::before { 18 | content: ''; 19 | display: inline-block; 20 | position: relative; 21 | width: 8px; 22 | height: 8px; 23 | background: #ddd; 24 | margin-top: 15px; 25 | margin-right: 8px; 26 | border-radius: 8px; 27 | transition: .3s; 28 | } 29 | } 30 | &.ant-tabs-tab-active { 31 | .tab-title { 32 | &::before { 33 | background: #1890ff; 34 | } 35 | } 36 | } 37 | } 38 | .ant-tabs-ink-bar { 39 | visibility: visible; 40 | height: 3px; 41 | border-radius: 0 0 4px 4px; 42 | } 43 | } 44 | > .ant-tabs-content { 45 | position: relative; 46 | height: ~'calc(100% - 44px)'; 47 | overflow: auto; 48 | } 49 | } 50 | } 51 | 52 | .basic-layout { 53 | &.fixed { 54 | .tabs-layout .lanif-tabs-content { 55 | height: 100%; 56 | width: 100%; 57 | position: absolute; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/Form/model/treeselect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TreeSelect } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | 5 | /** 6 | * 下拉树菜单元件 7 | */ 8 | export const TreeSelectForm = ({ 9 | form, 10 | name, 11 | formFieldOptions = {}, 12 | children, 13 | record, 14 | initialValue, 15 | normalize, 16 | rules, 17 | onChange, 18 | getPopupContainer, 19 | placeholder, 20 | ...otherProps 21 | }) => { 22 | // -- 23 | const { getFieldDecorator } = form; 24 | 25 | let initval = initialValue; 26 | 27 | if (record) { 28 | initval = record[name]; 29 | } 30 | 31 | // 如果存在初始值 32 | if (initval !== null && typeof initval !== 'undefined') { 33 | if ($$.isFunction(normalize)) { 34 | formFieldOptions.initialValue = normalize(initval); 35 | } else { 36 | formFieldOptions.initialValue = initval; 37 | } 38 | } 39 | 40 | // 如果有rules 41 | if (rules && rules.length) { 42 | formFieldOptions.rules = rules; 43 | } 44 | 45 | // 如果需要onChange 46 | if (typeof onChange === 'function') { 47 | formFieldOptions.onChange = (value, label, extra) => 48 | onChange(form, value, label, extra); // form, value 49 | } 50 | 51 | const props = { 52 | placeholder: placeholder || `请选择${otherProps.title}`, 53 | ...otherProps 54 | }; 55 | 56 | if (getPopupContainer) { 57 | props.getPopupContainer = getPopupContainer; 58 | } 59 | 60 | return getFieldDecorator(name, formFieldOptions)( 61 | 62 | ); 63 | }; 64 | 65 | export default TreeSelectForm; 66 | -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | import $$ from 'cmn-utils'; 2 | import modelEnhance from '@/utils/modelEnhance'; 3 | 4 | export default modelEnhance({ 5 | namespace: 'global', 6 | 7 | state: { 8 | menu: [], 9 | flatMenu: [], 10 | }, 11 | 12 | effects: { 13 | *getMenu({ payload }, { call, put }) { 14 | const { status, data } = yield call(getMenu, payload); 15 | if (status) { 16 | const loopMenu = (menu, pitem = {}) => { 17 | menu.forEach(item => { 18 | if (pitem.path) { 19 | item.parentPath = pitem.parentPath ? pitem.parentPath.concat(pitem.path) : [pitem.path]; 20 | } 21 | if (item.children && item.children.length) { 22 | loopMenu(item.children, item); 23 | } 24 | }); 25 | } 26 | loopMenu(data); 27 | 28 | yield put({ 29 | type: 'getMenuSuccess', 30 | payload: data, 31 | }); 32 | } 33 | }, 34 | }, 35 | 36 | reducers: { 37 | getMenuSuccess(state, { payload }) { 38 | return { 39 | ...state, 40 | menu: payload, 41 | flatMenu: getFlatMenu(payload), 42 | }; 43 | } 44 | }, 45 | }); 46 | 47 | export function getFlatMenu(menus) { 48 | let menu = []; 49 | menus.forEach(item => { 50 | if (item.children) { 51 | menu = menu.concat(getFlatMenu(item.children)); 52 | } 53 | menu.push(item); 54 | }); 55 | return menu; 56 | } 57 | 58 | export async function getMenu(payload) { 59 | return $$.post('/user/menu', payload); 60 | } -------------------------------------------------------------------------------- /src/components/Modal/style/index.less: -------------------------------------------------------------------------------- 1 | .antui-modalform.ant-modal { 2 | & { 3 | .ant-modal-body { 4 | 5 | } 6 | } 7 | &.full-modal { 8 | position: absolute; 9 | top: 20px; 10 | bottom: 20px; 11 | left: 20px; 12 | right: 20px; 13 | padding: 0; 14 | width: auto !important; 15 | .ant-modal-content { 16 | height: 100%; 17 | position: relative; 18 | .ant-modal-body { 19 | padding: 0; 20 | position: absolute; 21 | top: 55px; 22 | bottom: 53px; 23 | left: 0; 24 | right: 0; 25 | overflow-x: hidden; 26 | overflow-y: auto; 27 | } 28 | .ant-modal-footer { 29 | position: absolute; 30 | left: 0; 31 | right: 0; 32 | bottom: 0; 33 | text-align: center; 34 | } 35 | } 36 | } 37 | &.antui-table-modal { 38 | .ant-modal-header { 39 | .with-search-title { 40 | display: flex; 41 | align-items: center; 42 | .left-title { 43 | flex: auto; 44 | } 45 | .antui-searchbar { 46 | margin-right: 25px; 47 | } 48 | } 49 | } 50 | .ant-modal-body { 51 | padding: 0; 52 | max-height: 418px; 53 | overflow: auto; 54 | .ant-table-small .ant-table-thead > tr > th { 55 | padding: 17px 16px; 56 | } 57 | } 58 | .ant-modal-footer { 59 | display: flex; 60 | align-items: center; 61 | .footer-page { 62 | flex: auto; 63 | text-align: left; 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/components/Pages/style/screenLock.less: -------------------------------------------------------------------------------- 1 | .screen-lock-page { 2 | position: fixed !important; 3 | z-index: 9998; 4 | top: 0; 5 | left: 0; 6 | bottom: 0; 7 | right: 0; 8 | padding-top: 70px; 9 | background: radial-gradient(ellipse at center, #1f3649 0%, #17253d 44%, #040d11 100%); 10 | .container { 11 | width: 80%; 12 | margin: auto; 13 | .pattern-logo { 14 | font-size: 22px; 15 | color: #ddd; 16 | img { 17 | width: 45px; 18 | margin-right: 18px; 19 | } 20 | b { 21 | color: #aaa; 22 | } 23 | > * { 24 | vertical-align: middle; 25 | } 26 | } 27 | .patter-container { 28 | text-align: center; 29 | .patter-title { 30 | color: #fff; 31 | margin-bottom: 20px; 32 | letter-spacing: 0.3px; 33 | font-size: 20px; 34 | } 35 | .patt-holder { 36 | background: rgba(255, 255, 255, 0.1); 37 | border-radius: 14px; 38 | margin: 0 auto 40px; 39 | } 40 | > p { 41 | color: #E0E0E0; 42 | width: 310px; 43 | margin: 13px auto; 44 | } 45 | } 46 | .patter-tip { 47 | text-align: center; 48 | .ant-btn-ghost { 49 | // color: #ddd; 50 | // border-color: #ddd; 51 | } 52 | } 53 | } 54 | .antui-clock { 55 | bottom: 50px; 56 | color: #fff; 57 | padding: 0; 58 | position: absolute; 59 | right: 50px; 60 | width: auto; 61 | } 62 | } 63 | .pattern-tip-modal { 64 | margin: 50px auto; 65 | text-align: center; 66 | } -------------------------------------------------------------------------------- /src/routes/Login/model/index.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva'; 2 | import { login } from '../service'; 3 | import $$ from 'cmn-utils'; 4 | 5 | export default { 6 | namespace: 'login', 7 | 8 | state: { 9 | loggedIn: false, 10 | message: '', 11 | user: {} 12 | }, 13 | 14 | subscriptions: { 15 | setup({ history, dispatch }) { 16 | return history.listen(({ pathname }) => { 17 | if (pathname.indexOf('/sign/login') !== -1) { 18 | $$.removeStore('user'); 19 | } 20 | }); 21 | } 22 | }, 23 | 24 | effects: { 25 | *login({ payload }, { call, put }) { 26 | try { 27 | const { status, message, data } = yield call(login, payload); 28 | if (status) { 29 | $$.setStore('user', data); 30 | yield put(routerRedux.replace('/')); 31 | } else { 32 | yield put({ 33 | type: 'loginError', 34 | payload: { message } 35 | }); 36 | } 37 | } catch (e) { 38 | console.log(e) 39 | yield put({ 40 | type: 'loginError', 41 | payload: { message: e.message } 42 | }); 43 | } 44 | }, 45 | *logout(_, { put }) { } 46 | }, 47 | 48 | reducers: { 49 | loginSuccess(state, { payload }) { 50 | return { 51 | ...state, 52 | loggedIn: true, 53 | message: '', 54 | user: payload 55 | }; 56 | }, 57 | loginError(state, { payload }) { 58 | return { 59 | ...state, 60 | loggedIn: false, 61 | message: payload.message 62 | }; 63 | } 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/Notification/normal.less: -------------------------------------------------------------------------------- 1 | .la-notification { 2 | position:fixed; 3 | color:white; 4 | font-size:12px; 5 | left: 8px; 6 | top: 8px; 7 | right: 8px; 8 | z-index: 1009; 9 | .content { 10 | padding:8px 30px 8px 20px; 11 | position:absolute; 12 | left:0; 13 | right:0; 14 | opacity:0; 15 | color:inherit; 16 | text-decoration:none; 17 | visibility:hidden; 18 | transition:all .6s; 19 | border-radius:3px; 20 | text-shadow:0 0 3px rgba(1,1,1,.3); 21 | background-color: transparent; 22 | line-height:150%; 23 | font-size: 14px; 24 | } 25 | &.active .content.warn {background-color:#ff9800;} 26 | &.active .content.error {background-color:#e51c23;} 27 | &.active .content.default {background-color:#2196f3;} 28 | &.active .content.success {background-color:#8bc34a;} 29 | &.active .content.dark {background-color:#414141;} 30 | &.active .content {opacity:1;visibility:visible;} 31 | &.active { 32 | .close { 33 | position: absolute; 34 | right: 14px; 35 | top: 12px; 36 | display: inline-block; 37 | width: 14px; 38 | height: 14px; 39 | overflow: hidden; 40 | cursor: pointer; 41 | 42 | &::before, &::after { 43 | content: ''; 44 | position: absolute; 45 | height: 1px; 46 | width: 100%; 47 | top: 50%; 48 | left: 0; 49 | margin-top: -1px; 50 | background: #fff; 51 | } 52 | &::before { 53 | transform: rotate(45deg); 54 | } 55 | &::after { 56 | transform: rotate(-45deg); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/components/PatternLock/style/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | /* 4 | patternLock.js v 1.1.1 5 | Author: Sudhanshu Yadav, Nimiq Foundation 6 | Copyright (c) 2015,2016 Sudhanshu Yadav - ignitersworld.com , released under the MIT license. 7 | Copyright (c) 2018 Nimiq Foundation - nimiq.com , released under the MIT license. 8 | */ 9 | @success-color: #009900; 10 | @error-color: #BA1B26; 11 | @main-button-background: #FFF; 12 | 13 | .patt-holder{ 14 | -ms-touch-action: none; 15 | } 16 | .patt-wrap{ 17 | position: relative; 18 | cursor: pointer; 19 | } 20 | .patt-wrap ul, .patt-wrap li { 21 | list-style: none; 22 | margin: 0; 23 | padding: 0; 24 | } 25 | .patt-circ { 26 | position: relative; 27 | float: left; 28 | box-sizing: border-box; 29 | -moz-box-sizing: border-box; 30 | } 31 | .patt-circ.hovered { 32 | border: 3px solid @success-color; 33 | } 34 | 35 | .patt-error .patt-circ.hovered { 36 | border: 3px solid @error-color; 37 | } 38 | 39 | .patt-hidden .patt-circ.hovered { 40 | border: 0; 41 | } 42 | 43 | .patt-dots { 44 | background: @main-button-background; 45 | width: 10px; 46 | height: 10px; 47 | border-radius: 1px; 48 | position:absolute; 49 | top: 50%; 50 | left: 50%; 51 | margin-top: -5px; 52 | margin-left: -5px; 53 | } 54 | .patt-lines { 55 | border-radius: 1px; 56 | height: 10px; 57 | background: @main-button-background; 58 | opacity: 0.3; 59 | position: absolute; 60 | transform-origin: 5px 5px; 61 | -ms-transform-origin: 5px 5px; /* IE 9 */ 62 | -webkit-transform-origin: 5px 5px; 63 | } 64 | 65 | .patt-hidden .patt-lines { 66 | display:none; 67 | } -------------------------------------------------------------------------------- /src/components/Drag/test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Drag from './Drag'; 3 | import 'antd/dist/antd.css'; 4 | import { Modal, Button } from 'antd'; 5 | class BuildTitle extends React.Component { 6 | updateTransform = transformStr => { 7 | this.modalDom.style.transform = transformStr; 8 | }; 9 | componentDidMount() { 10 | this.modalDom = document.getElementsByClassName( 11 | 'ant-modal-wrap' //modal的class是ant-modal-wrap 12 | )[0]; 13 | } 14 | render() { 15 | const { title } = this.props; 16 | return ( 17 | 18 |
{title}
19 |
20 | ); 21 | } 22 | } 23 | export default class AntdModalDrag extends React.Component { 24 | state = { visible: false }; 25 | showModal = () => { 26 | this.setState({ 27 | visible: true 28 | }); 29 | }; 30 | handleOk = e => { 31 | this.setState({ 32 | visible: false 33 | }); 34 | }; 35 | handleCancel = e => { 36 | this.setState({ 37 | visible: false 38 | }); 39 | }; 40 | render() { 41 | const title = ; 42 | return ( 43 |
44 | 47 | 53 |

{this.state.text}

54 |

Some contents...

55 |

Some contents...

56 |
57 |
58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/EC/components/Line.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EC from 'components/Charts/ECharts/EC'; 3 | import 'echarts/lib/chart/line'; 4 | import 'echarts/lib/component/tooltip'; 5 | import 'echarts/lib/component/title'; 6 | 7 | function getOption() { 8 | return { 9 | title: { 10 | text: '堆叠区域图' 11 | }, 12 | tooltip : { 13 | trigger: 'axis' 14 | }, 15 | legend: { 16 | data:['邮件营销','联盟广告','视频广告'] 17 | }, 18 | toolbox: { 19 | feature: { 20 | saveAsImage: {} 21 | } 22 | }, 23 | grid: { 24 | left: '3%', 25 | right: '4%', 26 | bottom: '3%', 27 | containLabel: true 28 | }, 29 | xAxis : [ 30 | { 31 | type : 'category', 32 | boundaryGap : false, 33 | data : ['周一','周二','周三','周四','周五','周六','周日'] 34 | } 35 | ], 36 | yAxis : [ 37 | { 38 | type : 'value' 39 | } 40 | ], 41 | series : [ 42 | { 43 | name:'邮件营销', 44 | type:'line', 45 | stack: '总量', 46 | areaStyle: {normal: {}}, 47 | data:[120, 132, 101, 134, 90, 230, 210] 48 | }, 49 | { 50 | name:'联盟广告', 51 | type:'line', 52 | stack: '总量', 53 | areaStyle: {normal: {}}, 54 | data:[220, 182, 191, 234, 290, 330, 310] 55 | }, 56 | { 57 | name:'视频广告', 58 | type:'line', 59 | stack: '总量', 60 | areaStyle: {normal: {}}, 61 | data:[150, 232, 201, 154, 190, 330, 410] 62 | } 63 | ] 64 | }; 65 | }; 66 | 67 | export default props => ( 68 | 69 | ); 70 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/EC/components/Radar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EC from 'components/Charts/ECharts/EC'; 3 | import 'echarts/lib/chart/radar'; 4 | import 'echarts/lib/component/tooltip'; 5 | import 'echarts/lib/component/title'; 6 | 7 | function getOption() { 8 | return { 9 | title: { 10 | text: '基础雷达图' 11 | }, 12 | tooltip: {}, 13 | legend: { 14 | data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)'] 15 | }, 16 | radar: { 17 | // shape: 'circle', 18 | name: { 19 | textStyle: { 20 | color: '#fff', 21 | backgroundColor: '#999', 22 | borderRadius: 3, 23 | padding: [3, 5] 24 | } 25 | }, 26 | indicator: [ 27 | { name: '销售(sales)', max: 6500 }, 28 | { name: '管理(Administration)', max: 16000 }, 29 | { name: '信息技术(Information Techology)', max: 30000 }, 30 | { name: '客服(Customer Support)', max: 38000 }, 31 | { name: '研发(Development)', max: 52000 }, 32 | { name: '市场(Marketing)', max: 25000 } 33 | ] 34 | }, 35 | series: [ 36 | { 37 | name: '预算 vs 开销(Budget vs spending)', 38 | type: 'radar', 39 | // areaStyle: {normal: {}}, 40 | data: [ 41 | { 42 | value: [4300, 10000, 28000, 35000, 50000, 19000], 43 | name: '预算分配(Allocated Budget)' 44 | }, 45 | { 46 | value: [5000, 14000, 28000, 31000, 42000, 21000], 47 | name: '实际开销(Actual Spending)' 48 | } 49 | ] 50 | } 51 | ] 52 | }; 53 | } 54 | 55 | export default props => ; 56 | -------------------------------------------------------------------------------- /src/components/Form/model/input.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | const { TextArea } = Input; 5 | /** 6 | * 文本框元件 7 | */ 8 | export default ({ 9 | form, 10 | name, 11 | formFieldOptions = {}, 12 | record, 13 | initialValue, 14 | normalize, 15 | rules, 16 | onChange, 17 | type, 18 | preview, 19 | placeholder, 20 | getPopupContainer, 21 | ...otherProps 22 | }) => { 23 | const { getFieldDecorator } = form; 24 | 25 | let initval = initialValue; 26 | 27 | if (record) { 28 | initval = record[name]; 29 | } 30 | 31 | // 如果存在初始值 32 | if (initval !== null && typeof initval !== 'undefined') { 33 | if ($$.isFunction(normalize)) { 34 | formFieldOptions.initialValue = normalize(initval); 35 | } else { 36 | formFieldOptions.initialValue = initval; 37 | } 38 | } 39 | 40 | if (preview) { 41 | if (type === 'hidden') return null; 42 | return
{initval || ''}
; 43 | } 44 | 45 | // 如果有rules 46 | if (rules && rules.length) { 47 | formFieldOptions.rules = rules; 48 | } 49 | 50 | // 如果需要onChange 51 | if (typeof onChange === 'function') { 52 | formFieldOptions.onChange = e => onChange(form, e.target.value, e); // form, value, event 53 | } 54 | 55 | const Comp = type === 'textarea' ? TextArea : Input; 56 | 57 | delete otherProps.render; 58 | 59 | const props = { 60 | autoComplete: 'off', 61 | type, 62 | placeholder: placeholder || `请输入${otherProps.title}`, 63 | ...otherProps 64 | }; 65 | 66 | return getFieldDecorator(name, formFieldOptions)(); 67 | }; 68 | -------------------------------------------------------------------------------- /src/routes/UI/Alerts/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout, Button } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Panel from 'components/Panel'; 6 | import { normal, antdNotice } from 'components/Notification'; 7 | const { Content } = Layout; 8 | 9 | @connect() 10 | export default class extends BaseComponent { 11 | render() { 12 | return ( 13 | 14 | 15 | 16 |

我们默认包含了两种通知样式,当您的组件继承于BaseCompoent时,可使用this.notice发出config.js中配置的默认通知样式,或者可以通过实现Notification接口实现自已的通知类

17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/components/SideLayout.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Layout } from 'antd'; 3 | import CSSAnimate from 'components/CSSAnimate'; 4 | import Icon from 'components/Icon'; 5 | import './index.less'; 6 | const { Content, Sider } = Layout; 7 | 8 | class SideLayout extends Component { 9 | render() { 10 | const { title, site, author, sideContent, children } = this.props; 11 | const sidebarStyle = { 12 | borderRight: '1px solid #ddd', 13 | background: '#f5f5f5' 14 | }; 15 | return ( 16 | 17 | 22 |
23 |

{title}

24 |
    25 |
  • 26 | 27 | 作者:{author} 28 |
  • 29 |
  • 30 |

    31 | 32 | 网站: 33 | 34 | {site} 35 | 36 |

    37 |
  • 38 |
39 |
40 |
41 | {sideContent} 42 |
43 |
44 | 45 | 46 | {children} 47 | 48 | 49 |
50 | ); 51 | } 52 | } 53 | 54 | export default SideLayout; -------------------------------------------------------------------------------- /src/__mocks__/datatable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 模拟CRUD数据 3 | */ 4 | export default ({ fetchMock, delay, mock, toSuccess, toError }) => { 5 | return { 6 | // 表格带分页 7 | '/api/datatable/getList': options => { 8 | const body = JSON.parse(options.body); 9 | const currentPage = body.currentPage; 10 | const sortMap = body.sortMap; 11 | const idbase = (currentPage - 1) * 10 + 1; 12 | let sortField = { 'age|1-100': 1 }; 13 | if (sortMap && sortMap.age) { // 模拟排序 14 | let i = 60; 15 | sortField = 16 | sortMap.age === 'asc' 17 | ? { 'age|+1': new Array(10).fill(0).map(item => i++) } 18 | : { 'age|+1': new Array(10).fill(0).map(item => i--) }; 19 | } 20 | 21 | return toSuccess( 22 | mock({ 23 | currentPage: currentPage, 24 | showCount: body.showCount, 25 | totalResult: 100, 26 | totalPage: 10, 27 | [`dataList|${body.showCount}`]: [ 28 | { 29 | 'id|+1': idbase, 30 | name: '@cname', 31 | address: '@county()', 32 | 'role|1': ['1', '2', '3'], 33 | ...sortField 34 | } 35 | ] 36 | }), 37 | 400 38 | ); 39 | }, 40 | // 前台分页 41 | '/api/datatable/frontPaging': options => { 42 | return toSuccess( 43 | mock({ 44 | [`list|33`]: [ 45 | { 46 | 'id|+1': 1, 47 | name: '@cname', 48 | address: '@county()', 49 | 'age|1-100': 1, 50 | 'role|1': ['1', '2', '3'] 51 | } 52 | ] 53 | }), 54 | 400 55 | ); 56 | } 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/components/TransferTree/Search.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons'; 3 | import { Input } from 'antd'; 4 | import $$ from 'cmn-utils'; 5 | 6 | export default class Search extends React.PureComponent { 7 | static defaultProps = { 8 | placeholder: '' 9 | }; 10 | 11 | state = { 12 | value: '', 13 | } 14 | 15 | constructor(props) { 16 | super(props); 17 | this.onChange = $$.debounce(props.onChange, 500); 18 | } 19 | 20 | handleChange = value => { 21 | const onChange = this.props.onChange; 22 | if (onChange) { 23 | this.onChange(value); 24 | } 25 | this.setState({ 26 | value 27 | }) 28 | }; 29 | 30 | handleClear = e => { 31 | e.preventDefault(); 32 | 33 | const handleClear = this.props.handleClear; 34 | if (handleClear) { 35 | handleClear(e); 36 | } 37 | this.setState({ 38 | value: '' 39 | }) 40 | }; 41 | 42 | render() { 43 | const { placeholder, prefixCls } = this.props; 44 | const icon = 45 | this.state.value && this.state.value.length > 0 ? ( 46 | 47 | 48 | 49 | ) : ( 50 | 51 | 52 | 53 | ); 54 | return ( 55 |
56 | this.handleChange(e.target.value)} 62 | /> 63 | {icon} 64 |
65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import dva, { dynamic, router } from 'dva'; 3 | import createLoading from 'dva-loading'; 4 | import { createHashHistory } from 'history'; 5 | import request from 'cmn-utils/lib/request'; 6 | import createRoutes from '@/routes'; 7 | import 'assets/styles/index.less'; 8 | import config from './config'; 9 | import { ConfigProvider } from 'antd'; 10 | import zh_CN from 'antd/es/locale/zh_CN'; 11 | import 'moment/locale/zh-cn'; 12 | import { homepage } from '../package.json'; 13 | import * as serviceWorker from './serviceWorker'; 14 | 15 | const { Router } = router; 16 | 17 | // -> 初始化 18 | const app = dva({ 19 | history: createHashHistory({ 20 | basename: homepage.startsWith('/') ? homepage : '' 21 | }) 22 | }); 23 | 24 | // -> 插件 25 | app.use(createLoading()); 26 | app.use({ onError: config.exception.global }); 27 | 28 | // -> 请求 29 | request.config(config.request); 30 | 31 | // 使用mock数据 32 | require('./__mocks__'); 33 | // -> Developer mock data 34 | // if (process.env.NODE_ENV === 'development') { 35 | // require('./__mocks__'); 36 | // } 37 | 38 | // -> loading 39 | dynamic.setDefaultLoadingComponent(() => config.router.loading); 40 | 41 | // -> 注册全局模型 42 | app.model(require('./models/global').default); 43 | 44 | // -> 初始化路由 45 | app.router(({ history, app }) => ( 46 | 47 | {createRoutes(app)} 48 | 49 | )); 50 | 51 | // -> Start 52 | app.start('#root'); 53 | 54 | // export global 55 | export default { 56 | app, 57 | store: app._store, 58 | dispatch: app._store.dispatch 59 | }; 60 | 61 | // 如果想可以离线使用,请使用register()代替unregister()。可能会带来一些问题,如缓存等 62 | // 相关资料,可以从 https://bit.ly/CRA-PWA 了解 63 | serviceWorker.unregister(); 64 | -------------------------------------------------------------------------------- /src/routes/Widgets/Print/components/report.less: -------------------------------------------------------------------------------- 1 | .system-audit-pring { 2 | margin: 0 auto; 3 | width: 90vw; 4 | height: 97vh; 5 | .tg { 6 | border-collapse: collapse; 7 | border-spacing: 0; 8 | width: 100%; 9 | height: 100%; 10 | } 11 | .tg td { 12 | font-family: Arial, sans-serif; 13 | font-size: 14px; 14 | padding: 10px 5px; 15 | border-style: solid; 16 | border-width: 1px; 17 | overflow: hidden; 18 | word-break: normal; 19 | border-color: black; 20 | } 21 | .tg th { 22 | font-family: Arial, sans-serif; 23 | font-size: 14px; 24 | font-weight: normal; 25 | padding: 10px 5px; 26 | border: 0px; 27 | overflow: hidden; 28 | word-break: normal; 29 | height: 3em; 30 | h1 { 31 | padding-top: 1em; 32 | } 33 | } 34 | .tg .tg-qtf5 { 35 | text-align: left; 36 | } 37 | .tg .tg-obcv { 38 | text-align: center; 39 | } 40 | .tg .left1 { 41 | width: 3em; 42 | padding: 0 15px; 43 | } 44 | .tg .left2 { 45 | width: 10em; 46 | } 47 | .tg .tg-wp8o { 48 | text-align: center; 49 | vertical-align: middle; 50 | } 51 | .tg .tg-73oq { 52 | text-align: left; 53 | vertical-align: top; 54 | } 55 | .hfixed { 56 | height: 2em; 57 | } 58 | .h20 { 59 | height: 20%; 60 | } 61 | .w50 { 62 | width: 40vw; 63 | height: 100%; 64 | position: relative; 65 | } 66 | .date { 67 | position: absolute; 68 | bottom: 0px; 69 | right: 5px; 70 | } 71 | .sign { 72 | position: absolute; 73 | bottom: 2em; 74 | left: 6em; 75 | } 76 | .xtsm-content { 77 | color: red; 78 | font-size: 12px !important; 79 | } 80 | .version { 81 | text-align: right; 82 | padding-right: 10px; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/__mocks__/crud.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 模拟CRUD数据 3 | */ 4 | export default ({fetchMock, delay, mock, toSuccess, toError}) => { 5 | return { 6 | // 表格带分页 7 | '/api/crud/getList': (options) => { 8 | const body = JSON.parse(options.body); 9 | const currentPage = body.currentPage; 10 | const idbase = (currentPage - 1) * 10 + 1; 11 | const paramMap = body.paramMap; 12 | const deptName = paramMap.deptName; 13 | 14 | if (deptName == 'abcd') { 15 | return toSuccess(mock({ 16 | 'currentPage': currentPage, 17 | 'showCount': body.showCount, 18 | 'totalResult': 0, 19 | 'totalPage': 0, 20 | dataList: [], 21 | }), 400) 22 | } 23 | 24 | return toSuccess(mock({ 25 | 'currentPage': currentPage, 26 | 'showCount': body.showCount, 27 | 'totalResult': 100, 28 | 'totalPage': 10, 29 | [`dataList|${body.showCount}`]: [{ 30 | 'id|+1': idbase, 31 | 'deptName': deptName ? deptName : '@cword(3, 5)', 32 | 'distributionNetwork|1': ['0', '1'], 33 | 'address': '@county()', 34 | 'type': '@cword(3)', 35 | 'planBeginTime': '@date', 36 | 'planEndTime': '@date', 37 | 'workEmployee|1-3': [{ 38 | 'key|+1': 1, 39 | 'title': '@cname', 40 | }], 41 | 'content': '@csentence', 42 | }], 43 | }), 400) 44 | }, 45 | '/api/crud/bathDelete': (options) => toSuccess({options}, 400), 46 | '/api/crud/getWorkEmployee': (options) => mock({ 47 | 'status': true, 48 | 'data|10': [{ 49 | 'key|+1': 1, 50 | 'title': '@cname', 51 | }] 52 | }), 53 | '/api/crud/save': (options) => toSuccess({options}, 800), 54 | } 55 | } -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/components/Line.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import G2 from 'components/Charts/G2'; 3 | import DataSet from '@antv/data-set'; 4 | const { Chart, Axis, Geom, Tooltip, Legend } = G2; 5 | 6 | const data = [ 7 | { month: 'Jan', Tokyo: 7.0, London: 3.9 }, 8 | { month: 'Feb', Tokyo: 6.9, London: 4.2 }, 9 | { month: 'Mar', Tokyo: 9.5, London: 5.7 }, 10 | { month: 'Apr', Tokyo: 14.5, London: 8.5 }, 11 | { month: 'May', Tokyo: 18.4, London: 11.9 }, 12 | { month: 'Jun', Tokyo: 21.5, London: 15.2 }, 13 | { month: 'Jul', Tokyo: 25.2, London: 17.0 }, 14 | { month: 'Aug', Tokyo: 26.5, London: 16.6 }, 15 | { month: 'Sep', Tokyo: 23.3, London: 14.2 }, 16 | { month: 'Oct', Tokyo: 18.3, London: 10.3 }, 17 | { month: 'Nov', Tokyo: 13.9, London: 6.6 }, 18 | { month: 'Dec', Tokyo: 9.6, London: 4.8 } 19 | ]; 20 | const ds = new DataSet(); 21 | const dv = ds.createView().source(data); 22 | dv.transform({ 23 | type: 'fold', 24 | fields: ['Tokyo', 'London'], // 展开字段集 25 | key: 'city', // key字段 26 | value: 'temperature' // value字段 27 | }); 28 | 29 | const cols = { 30 | month: { 31 | range: [0, 1] 32 | } 33 | }; 34 | 35 | export default props => ( 36 | 37 | 38 | 39 | `${val}°C` }} /> 40 | 41 | 48 | 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | React App 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/components/Form/model/radio.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Radio } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | const RadioGroup = Radio.Group; 5 | /** 6 | * 单选框 7 | */ 8 | export default ({ 9 | form, 10 | name, 11 | dict = [], 12 | formFieldOptions = {}, 13 | record, 14 | initialValue, 15 | rules, 16 | onChange, 17 | normalize, 18 | buttonStyle, 19 | getPopupContainer, 20 | preview, 21 | ...otherProps 22 | }) => { 23 | const { getFieldDecorator } = form; 24 | 25 | let initval = initialValue; 26 | 27 | if (record) { 28 | initval = record[name]; 29 | } 30 | 31 | // 如果存在初始值 32 | if (initval !== null && typeof initval !== 'undefined') { 33 | if ($$.isFunction(normalize)) { 34 | formFieldOptions.initialValue = normalize(initval); 35 | } else { 36 | formFieldOptions.initialValue = initval; 37 | } 38 | } 39 | 40 | // 预览视图 41 | if (preview) { 42 | const dictObj = dict.filter(item => item.code === initval)[0]; 43 | let text = ''; 44 | if (dictObj) { 45 | text = dictObj.codeName; 46 | } 47 | return
{text}
; 48 | } 49 | 50 | // 如果有rules 51 | if (rules && rules.length) { 52 | formFieldOptions.rules = rules; 53 | } 54 | 55 | // 如果需要onChange 56 | if (typeof onChange === 'function') { 57 | formFieldOptions.onChange = e => onChange(form, e.target.value, e); // form, value 58 | } 59 | 60 | let RadioComp = Radio; 61 | if (buttonStyle === 'solid') RadioComp = Radio.Button; 62 | 63 | return getFieldDecorator(name, formFieldOptions)( 64 | 65 | {dict.map((dic, i) => ( 66 | 67 | {dic.codeName} 68 | 69 | ))} 70 | 71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /src/components/SideLayout/style/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | // 可滑动的布局组件 3 | .antui-side-layout { 4 | > .ant-layout-sider { 5 | background: #fff; 6 | border: 1px solid #e8e8e8; 7 | .side-handle { 8 | position: absolute; 9 | text-align: center; 10 | cursor: pointer; 11 | right: 0; 12 | top: 0; 13 | width: 13px; 14 | line-height: 39px; 15 | height: 39px; 16 | border-left: 1px solid #e8e8e8; 17 | background: #eee; 18 | z-index: 2; 19 | transition: right .3s; 20 | i { 21 | transform: scale(0.8); 22 | } 23 | } 24 | &.ant-layout-sider-zero-width { 25 | border: 0; 26 | .side-handle { 27 | right: -9px; 28 | border: 0; 29 | background: transparent; 30 | } 31 | } 32 | 33 | .side-body { 34 | overflow: hidden; 35 | position: absolute; 36 | top: 0px; 37 | left: 0px; 38 | right: 0px; 39 | bottom: 0px; 40 | .side-panel { 41 | border-radius: 0; 42 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); 43 | height: 100%; 44 | position: relative; 45 | .panel-header { 46 | height: 39px; 47 | line-height: 39px; 48 | padding-left: 10px; 49 | border-bottom: 1px solid #e8e8e8; 50 | } 51 | .panel-body { 52 | position: absolute; 53 | top: 39px; 54 | bottom: 0; 55 | left: 0; 56 | right: 0; 57 | overflow: auto; 58 | } 59 | } 60 | } 61 | } 62 | > .ant-layout-content { 63 | border: 1px solid #e8e8e8; 64 | margin-left: -1px; 65 | position: relative; 66 | overflow: auto; 67 | .ant-table-small { 68 | border: none; 69 | border-radius: 0px; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/Image/Image.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import cx from 'classnames'; 4 | import ImageViewer from './ImageViewer'; 5 | import './style/index.less'; 6 | 7 | /** 8 | * 图片组件,支持预览等功能 9 | */ 10 | class Image extends React.Component { 11 | static propTypes = { 12 | previewList: PropTypes.array, 13 | onSwitch: PropTypes.func, 14 | onClose: PropTypes.func, 15 | initialIndex: PropTypes.number 16 | }; 17 | 18 | state = { 19 | showViewer: false 20 | } 21 | 22 | onShowViewer = () => { 23 | if (!this.preview) { 24 | return; 25 | } 26 | 27 | this.prevOverflow = document.body.style.overflow; 28 | document.body.style.overflow = 'hidden'; 29 | this.setState({ 30 | showViewer: true 31 | }) 32 | } 33 | 34 | onCloseViewer = () => { 35 | document.body.style.overflow = this.prevOverflow; 36 | this.setState({ 37 | showViewer: false 38 | }) 39 | } 40 | 41 | render() { 42 | const { src, previewList, onSwitch, initialIndex, ...props } = this.props; 43 | const { showViewer } = this.state; 44 | 45 | this.preview = Array.isArray(previewList) && previewList.length; 46 | return ( 47 |
48 | 56 | {this.preview && showViewer ? ( 57 | 63 | ) : null} 64 |
65 | ); 66 | } 67 | } 68 | 69 | export default Image; -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/components/Pie.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import G2 from 'components/Charts/G2'; 3 | import DataSet from '@antv/data-set'; 4 | const { Chart, Axis, Geom, Tooltip, Legend, Coord, Label } = G2; 5 | 6 | const { DataView } = DataSet; 7 | const data = [ 8 | { item: '事例一', count: 40 }, 9 | { item: '事例二', count: 21 }, 10 | { item: '事例三', count: 17 }, 11 | { item: '事例四', count: 13 }, 12 | { item: '事例五', count: 9 } 13 | ]; 14 | const dv = new DataView(); 15 | dv.source(data).transform({ 16 | type: 'percent', 17 | field: 'count', 18 | dimension: 'item', 19 | as: 'percent' 20 | }); 21 | const cols = { 22 | percent: { 23 | formatter: val => { 24 | val = val * 100 + '%'; 25 | return val; 26 | } 27 | } 28 | }; 29 | 30 | export default props => ( 31 | 36 | 37 | 38 | 43 | 47 | { 54 | percent = percent * 100 + '%'; 55 | return { 56 | name: item, 57 | value: percent 58 | }; 59 | } 60 | ]} 61 | style={{ lineWidth: 1, stroke: '#fff' }} 62 | > 63 | 70 | 71 | ); 72 | -------------------------------------------------------------------------------- /src/components/Button/Ripple.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import cx from 'classnames'; 3 | import './style/ripple.less'; 4 | 5 | /** 6 | * 仿 material design ripple 效果 7 | */ 8 | class Ripple extends Component { 9 | onClick = e => { 10 | this.createRipple(e); 11 | }; 12 | 13 | createRipple = e => { 14 | const btnWidth = this.element.clientWidth; 15 | const rect = this.element.getBoundingClientRect(); 16 | const btnOffsetTop = rect.top; 17 | const btnOffsetLeft = rect.left; 18 | const posMouseX = e.pageX; 19 | const posMouseY = e.pageY; 20 | const rippleX = posMouseX - btnOffsetLeft; 21 | const rippleY = posMouseY - btnOffsetTop; 22 | 23 | const rippleAnimate = document.createElement('div'); 24 | rippleAnimate.className = 'ripple-animate'; 25 | const baseStyle = ` 26 | top: ${rippleY - btnWidth}px; 27 | left: ${rippleX - btnWidth}px; 28 | width: ${btnWidth * 2}px; 29 | height: ${btnWidth * 2}px; 30 | `; 31 | rippleAnimate.style.cssText = baseStyle; 32 | this.element.appendChild(rippleAnimate); 33 | 34 | setTimeout(() => { 35 | requestAnimationFrame(() => { 36 | rippleAnimate.style.cssText = 37 | baseStyle + 38 | ' transform: scale(1); -webkit-transition: scale(1); opacity: 0;'; 39 | }); 40 | }, 50); // 如不加延时有时动画不会生效,没找到原因 41 | 42 | setTimeout(() => { 43 | rippleAnimate.remove(); 44 | }, 750); 45 | }; 46 | 47 | render() { 48 | const { children, type, ghost, ...otherProps } = this.props; 49 | return ( 50 | (this.element = node)} 52 | className={cx('ripple-btn', type, { ghost })} 53 | {...otherProps} 54 | onClick={this.onClick} 55 | > 56 | {children} 57 | 58 | ); 59 | } 60 | } 61 | 62 | export default Ripple; 63 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/EC/components/Bar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EC from 'components/Charts/ECharts/EC'; 3 | import 'echarts/lib/chart/bar'; 4 | import 'echarts/lib/component/tooltip'; 5 | import 'echarts/lib/component/title'; 6 | 7 | function getOption() { 8 | return { 9 | color: ['#003366', '#006699', '#4cabce', '#e5323e'], 10 | tooltip: { 11 | trigger: 'axis', 12 | axisPointer: { 13 | type: 'shadow' 14 | } 15 | }, 16 | legend: { 17 | data: ['Forest', 'Steppe', 'Desert', 'Wetland'] 18 | }, 19 | toolbox: { 20 | show: true, 21 | orient: 'vertical', 22 | left: 'right', 23 | top: 'center', 24 | feature: { 25 | mark: { show: true }, 26 | dataView: { show: true, readOnly: false }, 27 | magicType: { show: true, type: ['line', 'bar', 'stack', 'tiled'] }, 28 | restore: { show: true }, 29 | saveAsImage: { show: true } 30 | } 31 | }, 32 | calculable: true, 33 | xAxis: [ 34 | { 35 | type: 'category', 36 | axisTick: { show: false }, 37 | data: ['2012', '2013', '2014', '2015', '2016'] 38 | } 39 | ], 40 | yAxis: [ 41 | { 42 | type: 'value' 43 | } 44 | ], 45 | series: [ 46 | { 47 | name: 'Forest', 48 | type: 'bar', 49 | barGap: 0, 50 | data: [320, 332, 301, 334, 390] 51 | }, 52 | { 53 | name: 'Steppe', 54 | type: 'bar', 55 | data: [220, 182, 191, 234, 290] 56 | }, 57 | { 58 | name: 'Desert', 59 | type: 'bar', 60 | data: [150, 232, 201, 154, 190] 61 | }, 62 | { 63 | name: 'Wetland', 64 | type: 'bar', 65 | data: [98, 77, 101, 99, 40] 66 | } 67 | ] 68 | }; 69 | } 70 | 71 | export default props => ; 72 | -------------------------------------------------------------------------------- /src/components/Pages/Result.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Layout, Row, Col } from 'antd'; 3 | import Icon from '../Icon'; 4 | import cx from 'classnames'; 5 | const { Content } = Layout; 6 | 7 | const type2icon = { 8 | success: 'check', 9 | error: 'close', 10 | warning: 'exclamation', 11 | info: 'info' 12 | }; 13 | /** 14 | * 结果展示组件 15 | */ 16 | export default ({ 17 | title, // 标题 18 | extra, // 标题右边的内容 19 | icon, // 标题左边的图标, 20 | type, // 默认图标 success error warning info 21 | description, // 标题下方的文字 22 | actions, // 内容里面下方的按钮 23 | footer, // 内容下方的文字 24 | style, 25 | children, // 正文 26 | className 27 | }) => { 28 | const classNames = cx('full-layout', 'result-fragment', className); 29 | 30 | let titleIcon = icon; 31 | if (type && type2icon[type] && !icon) { 32 | titleIcon = ; 33 | } 34 | 35 | return ( 36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 | {titleIcon} {title} 44 |
45 | 46 | 47 |
{extra}
48 | 49 |
50 | {description ? ( 51 |
{description}
52 | ) : null} 53 |
54 |
55 | {children} 56 | {actions ?
{actions}
: null} 57 |
58 |
{footer}
59 |
60 |
61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /src/routes/UI/Modal/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout, Button } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Panel from 'components/Panel'; 6 | import { ModalTable } from 'components/Modal'; 7 | import columns from './columns'; 8 | import PageHelper from '@/utils/pageHelper'; 9 | import $$ from 'cmn-utils'; 10 | const { Content } = Layout; 11 | 12 | @connect() 13 | export default class extends BaseComponent { 14 | state = { 15 | visibleLoadTableModal: false 16 | }; 17 | 18 | openLoadTableModal = () => { 19 | this.setState({ 20 | visibleLoadTableModal: true 21 | }); 22 | }; 23 | 24 | onLoadData = pageInfo => { 25 | return $$.post('/datatable/getList', PageHelper.requestFormat(pageInfo)) 26 | .then(resp => { 27 | return PageHelper.responseFormat(resp); 28 | }) 29 | .catch(e => console.error(e)); 30 | }; 31 | 32 | render() { 33 | const { visibleLoadTableModal } = this.state; 34 | 35 | const tableProps = { 36 | loadData: this.onLoadData 37 | }; 38 | const modalProps = {}; 39 | 40 | return ( 41 | 42 | 43 | 44 |

LoadTable组件异步加载数据

45 | 46 | 49 | 50 |
51 |
52 | this.setState({ visibleLoadTableModal: false })} 59 | /> 60 |
61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/Clock/Clock.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style/index.less'; 3 | /** 4 | * 时钟组件 5 | */ 6 | class Clock extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.monthNames = [ 10 | '一月', 11 | '二月', 12 | '三月', 13 | '四月', 14 | '五月', 15 | '六月', 16 | '七月', 17 | '八月', 18 | '九月', 19 | '十月', 20 | '十一月', 21 | '十二月' 22 | ]; 23 | this.dayNames = [ 24 | '星期日', 25 | '星期一', 26 | '星期二', 27 | '星期三', 28 | '星期四', 29 | '星期五', 30 | '星期六' 31 | ]; 32 | this.state = this.formatClock(false); 33 | } 34 | 35 | componentDidMount() { 36 | this.timer = setInterval(this.formatClock, 1000); 37 | } 38 | 39 | componentWillUnmount() { 40 | if (this.timer) clearInterval(this.timer); 41 | } 42 | 43 | formatClock = (render = true) => { 44 | const now = new Date(); 45 | const hours = now.getHours(); 46 | const min = now.getMinutes(); 47 | const sec = now.getSeconds(); 48 | 49 | const state = { 50 | date: `${now.getFullYear()} ${this.monthNames[now.getMonth()]} ${now.getDate()} ${this.dayNames[now.getDay()]}`, 51 | hours: hours < 10 ? '0' + hours : hours, 52 | min: min < 10 ? '0' + min : min, 53 | sec: sec < 10 ? '0' + sec : sec, 54 | }; 55 | 56 | if (render) this.setState(state); 57 | return state 58 | } 59 | 60 | render() { 61 | const {date, hours, min, sec} = this.state; 62 | return ( 63 |
64 |
{date}
65 |
    66 |
  • {hours}
  • 67 |
  • :
  • 68 |
  • {min}
  • 69 |
  • :
  • 70 |
  • {sec}
  • 71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default Clock; 78 | -------------------------------------------------------------------------------- /src/components/Editor/Editor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import E from 'wangeditor'; 4 | import defaultConfig from './config'; 5 | import $$ from 'cmn-utils'; 6 | import './style/index.less'; 7 | const { debounce } = $$; 8 | 9 | class Editor extends Component { 10 | static propTypes = { 11 | value: PropTypes.string, 12 | onLoaded: PropTypes.func, 13 | } 14 | 15 | constructor(props) { 16 | super(); 17 | this.state = { 18 | value: props.value 19 | }; 20 | // 设置这个值控制同步速度,速度过快影响输入体验,过慢可能获取到的是老值 21 | // 如果体验过差,建议不要回传value, 使用onLoaded获取wangeditor实例 22 | this._onChange = debounce(this.onChange, 2000); 23 | } 24 | 25 | componentDidMount() { 26 | const { value, onLoaded, ...otherProps } = this.props; 27 | this.editor = new E(this.editorDom); 28 | this.editor.customConfig = { 29 | ...defaultConfig, 30 | onchange: this._onChange, 31 | ...otherProps 32 | }; 33 | 34 | if (onLoaded && $$.isFunction(otherProps.onChange)) { 35 | this.editor.customConfig.onchange = otherProps.onChange; 36 | } 37 | 38 | this.editor.create(); 39 | this.editor.txt.html(value); 40 | 41 | // 返回wangeditor 42 | if (onLoaded) onLoaded(this.editor); 43 | } 44 | 45 | componentDidUpdate(prevProps, prevState) { 46 | if (prevProps.value !== this.props.value) { 47 | this.setState({ 48 | value: this.props.value 49 | }); 50 | 51 | this.editor.txt.html(this.props.value || ''); 52 | 53 | this._onChange(this.props.value); 54 | } 55 | } 56 | 57 | onChange = html => { 58 | const { onChange } = this.props; 59 | if (onChange) onChange(html); 60 | }; 61 | 62 | render() { 63 | return ( 64 |
{ 67 | this.editorDom = node; 68 | }} 69 | /> 70 | ); 71 | } 72 | } 73 | 74 | export default Editor; 75 | -------------------------------------------------------------------------------- /src/components/Form/model/checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | import omit from 'object.omit'; 5 | 6 | const CheckboxGroup = Checkbox.Group; 7 | /** 8 | * 单选框 9 | */ 10 | export default ({ 11 | form, 12 | name, 13 | dict = [], 14 | formFieldOptions = {}, 15 | record, 16 | initialValue, 17 | rules, 18 | onChange, 19 | normalize, 20 | buttonStyle = 'solid', 21 | getPopupContainer, 22 | preview, 23 | ...otherProps 24 | }) => { 25 | const { getFieldDecorator } = form; 26 | 27 | let initval = initialValue; 28 | 29 | if (record) { 30 | initval = record[name]; 31 | } 32 | 33 | // 如果存在初始值 34 | if (initval !== null && typeof initval !== 'undefined') { 35 | if ($$.isFunction(normalize)) { 36 | formFieldOptions.initialValue = normalize(initval); 37 | } else { 38 | formFieldOptions.initialValue = initval; 39 | } 40 | } 41 | 42 | // 如果有rules 43 | if (rules && rules.length) { 44 | formFieldOptions.rules = rules; 45 | } 46 | 47 | // 预览视图 48 | if (preview) { 49 | const _initval = $$.isArray(initval) ? initval : [initval]; 50 | const dictObj = dict.filter(item => _initval.indexOf(item.code) !== -1); 51 | let text = ''; 52 | if (dictObj.length) { 53 | text = dictObj.map(item => item.codeName).join(','); 54 | } 55 | return
{text}
; 56 | } 57 | 58 | // 如果需要onChange 59 | if (typeof onChange === 'function') { 60 | formFieldOptions.onChange = e => onChange(form, e.target.value, e); // form, value 61 | } 62 | 63 | const checkboxProps = omit(otherProps, 'allowClear'); 64 | return getFieldDecorator(name, formFieldOptions)( 65 | 66 | {dict.map((dic, i) => ( 67 | 68 | {dic.codeName} 69 | 70 | ))} 71 | 72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/Icon/Icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import AntdIcon, { BorderOutlined } from '@ant-design/icons'; 5 | 6 | /** 7 | * 字体图标,兼容antd的图标 8 | */ 9 | class Icon extends React.Component { 10 | static propTypes = { 11 | prefixCls: PropTypes.string, 12 | type: PropTypes.any.isRequired, 13 | className: PropTypes.string, 14 | children: PropTypes.node, 15 | font: PropTypes.string, 16 | antd: PropTypes.bool, 17 | spin: PropTypes.bool 18 | }; 19 | 20 | static defaultProps = { 21 | prefixCls: 'antui-icon', 22 | className: '', 23 | font: 'ad' 24 | }; 25 | 26 | render() { 27 | const { 28 | prefixCls, 29 | type, 30 | className, 31 | children, 32 | font, 33 | antd, 34 | spin, 35 | ...props 36 | } = this.props; 37 | const cn = classnames( 38 | prefixCls, 39 | { 40 | [font]: font, 41 | [font + '-' + type]: font && type, 42 | spin 43 | }, 44 | className 45 | ); 46 | if (/^&#x.+;$/.test(type)) { 47 | return ( 48 | 53 | ); 54 | } 55 | if (antd) { 56 | const antdcn = classnames(prefixCls, className); 57 | if (typeof type === 'string') { 58 | const Icons = require('@ant-design/icons')[type] || BorderOutlined; 59 | return ; 60 | } else if (React.isValidElement(type)) { 61 | return ( 62 | type} 64 | className={antdcn} 65 | spin={spin} 66 | {...props} 67 | /> 68 | ); 69 | } 70 | } 71 | return ( 72 | 73 | {children} 74 | 75 | ); 76 | } 77 | } 78 | 79 | export default Icon; 80 | -------------------------------------------------------------------------------- /src/components/Form/model/select.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Select } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | /** 5 | * 下拉菜单元件 6 | */ 7 | export default ({ 8 | form, 9 | name, 10 | dict = [], 11 | formFieldOptions = {}, 12 | record, 13 | initialValue, 14 | rules, 15 | onChange, 16 | normalize, 17 | getPopupContainer, 18 | placeholder, 19 | preview, 20 | ...otherProps 21 | }) => { 22 | const { getFieldDecorator } = form; 23 | 24 | let initval = initialValue; 25 | 26 | if (record) { 27 | initval = record[name]; 28 | } 29 | 30 | // 如果存在初始值 31 | if (initval !== null && typeof initval !== 'undefined') { 32 | if ($$.isFunction(normalize)) { 33 | formFieldOptions.initialValue = normalize(initval); 34 | } else { 35 | formFieldOptions.initialValue = initval; 36 | } 37 | } 38 | 39 | // 预览视图 40 | if (preview) { 41 | const _initval = $$.isArray(initval) ? initval : [initval]; 42 | const dictObj = dict.filter(item => _initval.indexOf(item.code) !== -1); 43 | let text = ''; 44 | if (dictObj.length) { 45 | text = dictObj.map(item => item.codeName).join(','); 46 | } 47 | return
{text}
; 48 | } 49 | 50 | // 如果有rules 51 | if (rules && rules.length) { 52 | formFieldOptions.rules = rules; 53 | } 54 | 55 | // 如果需要onChange 56 | if (typeof onChange === 'function') { 57 | formFieldOptions.onChange = value => onChange(form, value); // form, value 58 | } 59 | 60 | const props = { 61 | placeholder: placeholder || `请选择${otherProps.title}`, 62 | ...otherProps 63 | }; 64 | 65 | if (getPopupContainer) { 66 | props.getPopupContainer = getPopupContainer; 67 | } 68 | 69 | return getFieldDecorator(name, formFieldOptions)( 70 | 77 | ); 78 | }; 79 | -------------------------------------------------------------------------------- /src/components/Pages/style/result.less: -------------------------------------------------------------------------------- 1 | .result-fragment { 2 | background: url('../../../assets/images/bg.jpg'); 3 | position: absolute; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | .center-block { 9 | margin: 70px auto; 10 | max-width: 625px; 11 | .result-header { 12 | .title { 13 | font-size: 24px; 14 | margin-top: 19px; 15 | font-weight: 600; 16 | line-height: 1.1; 17 | i { 18 | font-size: 32px; 19 | width: 46px; 20 | text-align: center; 21 | background: #eee; 22 | border: 1px solid #DDD; 23 | border-radius: 50%; 24 | padding: 6px; 25 | margin-right: 9px; 26 | position: relative; 27 | } 28 | &.success i { 29 | color: @success-color; 30 | } 31 | &.error i { 32 | color: @error-color; 33 | } 34 | &.warning i { 35 | color: @warning-color; 36 | } 37 | &.info i { 38 | color: @info-color; 39 | } 40 | } 41 | .extra { 42 | color: #aaa; 43 | text-align: right; 44 | padding-right: 10px; 45 | } 46 | .description { 47 | margin-top: 10px; 48 | } 49 | } 50 | .result-body { 51 | position: relative; 52 | margin: 20px 0; 53 | background-color: #fff; 54 | border-radius: 4px; 55 | padding: 25px; 56 | border: 1px solid #e5e5e5; 57 | color: #999; 58 | font-size: 15px; 59 | line-height: 1.8; 60 | .action-btns { 61 | margin-top: 20px; 62 | text-align: right; 63 | > button, 64 | > .ant-btn { 65 | margin-left: 8px; 66 | } 67 | } 68 | } 69 | .result-footer { 70 | color: #999; 71 | font-size: 13px; 72 | text-align: center; 73 | a { 74 | color: @link-color; 75 | font-weight: 600; 76 | } 77 | a:hover { 78 | color: @primary-5; 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/__mocks__/demo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 模拟请求数据 3 | * @param {FetchMock} fetchMock 当现有条件不满足时,可以使用fetchMock来进行扩展 4 | * @param {function} delay 增加延迟时间 ms 例: delay(mockData) 或 delay(mockData, 200) 5 | * @param {function} mock 使用mock生成数据,例: 6 | 7 | mock({ 8 | 'string|1-10': '★' // 生成最少1颗,最多10颗星字符 9 | }) 10 | 11 | // {'string': '★★★★★★'} 12 | 13 | 更多用法参考 http://mockjs.com/examples.html 14 | */ 15 | export default ({fetchMock, delay, mock, toSuccess, toError}) => { 16 | // 如果现有扩展不满足需求,可以直接使用fetchMock方法 17 | // fetchMock.mock(/httpbin.org\/post/, {/* response */}, {/* options */}); 18 | 19 | return { 20 | // 支持方法头 21 | 'GET /api/getUserInfo': { 22 | name: '小雨', 23 | sex: '男', 24 | age: 18, 25 | }, 26 | // 模拟真实请求延迟效果 27 | '/api/getUsers': delay([ 28 | { name: 'jonn' }, 29 | { name: 'weiq' }, 30 | ]), 31 | // Match regexp 32 | 'regexp:/api/aaa/.*': {}, 33 | // Match a url beginning with a string 34 | 'begin:http://www.site.com': {}, 35 | // Match a url ending with a string 36 | 'end:.jpg': {}, 37 | // Match a url using a glob pattern 38 | 'glob:http://*.*': {}, 39 | // Match a url that satisfies an express style path 40 | 'express:/user/:user': {}, 41 | // 表格带分页, 写成函数形式可以使用请求参数, 42 | // 更真实的模拟后端数据处理业务 43 | '/api/userInfo/getList': (options) => { 44 | const body = JSON.parse(options.body); 45 | const pageNum = body.pageNum; 46 | const idbase = (pageNum - 1) * 10 + 1; 47 | return toSuccess(mock({ 48 | 'pageNum': pageNum, 49 | 'pageSize': 10, 50 | 'size': 10, 51 | 'total': 100, 52 | 'totalPages': 10, 53 | 'list|10': [{ 54 | 'id|+1': idbase, 55 | 'name': '@cname', // 中文名称 56 | 'age|1-100': 100, // 100以内随机整数 57 | 'birthday': '@date("yyyy-MM-dd")', // 日期 58 | 'city': '@city(true)', // 中国城市 59 | 'phone': /^1[385][1-9]\d{8}/ // 手机号 60 | }], 61 | }), 400) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/components/Scatter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import G2 from 'components/Charts/G2'; 3 | const { Chart, Axis, Geom, Tooltip, Legend } = G2; 4 | 5 | const gender = ['male', 'female']; 6 | function getNumberInNormalDistribution(mean, std_dev) { 7 | return mean + randomNormalDistribution() * std_dev; 8 | } 9 | 10 | function randomNormalDistribution() { 11 | var u = 0.0, 12 | v = 0.0, 13 | w = 0.0, 14 | c = 0.0; 15 | do { 16 | //获得两个(-1,1)的独立随机变量 17 | u = Math.random() * 2 - 1.0; 18 | v = Math.random() * 2 - 1.0; 19 | w = u * u + v * v; 20 | } while (w === 0.0 || w >= 1.0); 21 | //这里就是 Box-Muller转换 22 | c = Math.sqrt((-2 * Math.log(w)) / w); 23 | //返回2个标准正态分布的随机数,封装进一个数组返回 24 | //当然,因为这个函数运行较快,也可以扔掉一个 25 | //return [u*c,v*c]; 26 | return (u * c).toFixed(2); 27 | } 28 | 29 | function genData(n) { 30 | const data = []; 31 | for (let index = 0; index < n; index++) { 32 | data.push({ 33 | gender: gender[index % 2], 34 | height: getNumberInNormalDistribution(180, 20), 35 | weight: getNumberInNormalDistribution(70, 20) 36 | }); 37 | } 38 | return data; 39 | } 40 | 41 | export default props => ( 42 | 43 | 48 | 49 | 50 | 51 | { 61 | return { 62 | name: gender, 63 | value: height + '(cm), ' + weight + '(kg)' 64 | }; 65 | } 66 | ]} 67 | /> 68 | 69 | ); 70 | -------------------------------------------------------------------------------- /src/layouts/styles/basic.less: -------------------------------------------------------------------------------- 1 | .basic-layout { 2 | &.fixed-header { 3 | > .ant-layout-header { 4 | z-index: 3; 5 | box-shadow: 0 3px 3px rgba(0, 0, 0, 0.05); 6 | } 7 | } 8 | > .ant-layout.ant-layout-has-sider { 9 | min-height: ~'calc(100vh - 60px)'; // 排除顶部 10 | } 11 | /* 应用布局&布局设置 */ 12 | &.full-layout { 13 | // 固定所有元素,经典管理界面样式,即固定头,侧栏,面包屑 14 | &.fixed.ant-layout, 15 | &.fixed .full-layout.ant-layout { 16 | position: absolute !important; 17 | top: 0; 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | .ant-layout-sider > .ant-layout-sider-children { 22 | overflow-y: auto; 23 | overflow-x: hidden; 24 | } 25 | } 26 | // 只固定头部 27 | &:not(.fixed)&.fixed-header { 28 | > .ant-layout-header { 29 | position: fixed; 30 | z-index: 200; 31 | left: 0; 32 | right: 0; 33 | } 34 | > .ant-layout { 35 | padding-top: 60px; 36 | overflow: auto; 37 | } 38 | .ant-layout.ant-layout-has-sider > .ant-layout, 39 | .ant-layout.ant-layout-has-sider > .ant-layout-content { 40 | overflow-x: visible; 41 | } 42 | } 43 | // 只固定面包屑,必需要先固定头部后才可生效 44 | &.fixed-breadcrumbs { 45 | & > .ant-layout-header { 46 | box-shadow: none; 47 | z-index: 2; 48 | } 49 | & > .ant-layout > .ant-layout-content > .ant-layout { 50 | .router-page.ant-layout-content { 51 | overflow: auto; 52 | } 53 | > .ant-layout-header { 54 | z-index: 3; 55 | box-shadow: 0 3px 3px rgba(0, 0, 0, 0.05); 56 | } 57 | } 58 | } 59 | // 隐藏面包屑 60 | &.hided-breadcrumbs .topbar-content { 61 | display: none; 62 | } 63 | &.hided-breadcrumbs { 64 | .router-page, 65 | .router-page > .ant-layout { 66 | min-height: ~'calc(100vh - 62px)'; // 排除顶部 67 | } 68 | } 69 | .router-page, 70 | .router-page > .ant-layout { 71 | min-height: ~'calc(100vh - 110px)'; // 排除顶部和面包屑 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Editor/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // debug: true, // 定义 debug 模式 3 | zIndex: 1, 4 | // 下面两个配置,使用其中一个即可显示“上传图片”的tab。但是两者不要同时使用!!! 5 | uploadImgShowBase64: true, // 使用 base64 保存图片 6 | // uploadImgServer: '/upload', // 上传图片到服务器 7 | // uploadImgMaxSize: 3 * 1024 * 1024, // 将图片大小限制为 3M 8 | // uploadImgMaxLength: 5, // 限制一次最多上传 5 张图片 9 | /* 10 | // 自定义上传参数 11 | uploadImgParams: { 12 | token: 'abcdef12345' // 属性值会自动进行 encode ,此处无需 encode 13 | }, 14 | uploadImgHeaders: { 15 | 'Accept': 'text/x-json' 16 | }, 17 | */ 18 | // uploadFileName: 'yourFileName', // 自定义 fileName 19 | // withCredentials: true, // withCredentials(跨域传递 cookie) 20 | // uploadImgTimeout: 3000, // 将 timeout 时间改为 3s 21 | /* //自定义提示方法 22 | customAlert = (info) => { 23 | alert('自定义提示:' + info) 24 | }, 25 | */ 26 | 27 | // 自定义 onchange 触发的延迟时间,默认为 200 ms 28 | // onchangeTimeout: 1000, // 单位 ms 29 | // onchange = html => html, 30 | 31 | // onfocus = _ => {}; // 点击富文本区域会触发onfocus函数执行 32 | // onblur = html => (); // 点击富文本以外的区域 33 | 34 | // pasteFilterStyle: false, // 关闭掉粘贴样式的过滤 35 | // pasteIgnoreImg: true, // 忽略粘贴内容中的图片 36 | // pasteTextHandle: context => content, // 自定义处理粘贴的文本内容 37 | 38 | /* 39 | // 自定义配置颜色(字体颜色、背景色) 40 | colors: [ 41 | '#000000', '#eeece0', '#1c487f', '#4d80bf', '#c24f4a', 42 | '#8baa4a', '#7b5ba1', '#46acc8', '#f9963b', '#ffffff' 43 | ], 44 | */ 45 | 46 | /* 47 | // 自定义字体 48 | fontNames: [ 49 | '宋体', 50 | '微软雅黑', 51 | 'Arial', 52 | 'Tahoma', 53 | 'Verdana' 54 | ], 55 | */ 56 | // menus: [], // 自定义菜单配置 57 | /* 58 | [ 59 | 'head', // 标题 60 | 'bold', // 粗体 61 | 'italic', // 斜体 62 | 'underline', // 下划线 63 | 'strikeThrough', // 删除线 64 | 'foreColor', // 文字颜色 65 | 'backColor', // 背景颜色 66 | 'link', // 插入链接 67 | 'list', // 列表 68 | 'justify', // 对齐方式 69 | 'quote', // 引用 70 | 'emoticon', // 表情 71 | 'image', // 插入图片 72 | 'table', // 表格 73 | 'video', // 插入视频 74 | 'code', // 插入代码 75 | 'undo', // 撤销 76 | 'redo', // 重复 77 | ], 78 | */ 79 | } -------------------------------------------------------------------------------- /src/components/Form/model/cascade.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Cascader } from 'antd'; 3 | import $$ from 'cmn-utils'; 4 | 5 | /** 6 | * 级联表单元件 7 | * initialValue 初始值 8 | */ 9 | export default ({ 10 | name, 11 | form, 12 | record, 13 | formFieldOptions = {}, 14 | normalize, 15 | initialValue, 16 | rules, 17 | onChange, 18 | preview, 19 | getPopupContainer, 20 | placeholder, 21 | ...otherProps 22 | }) => { 23 | const { getFieldDecorator } = form; 24 | 25 | let initval = initialValue; 26 | 27 | if (record) { 28 | initval = record[name]; 29 | } 30 | 31 | // 如果存在初始值 32 | if (initval !== null && typeof initval !== 'undefined') { 33 | if ($$.isFunction(normalize)) { 34 | formFieldOptions.initialValue = normalize(initval); 35 | } else { 36 | formFieldOptions.initialValue = initval; 37 | } 38 | } 39 | 40 | if (preview) { 41 | if (otherProps.options && initval) { 42 | const data = []; 43 | let level = 0; 44 | const loop = opts => { 45 | opts.forEach(item => { 46 | if (item.value === initval[level]) { 47 | data.push(item.label); 48 | if (item.children && initval[++level]) { 49 | loop(item.children); 50 | } 51 | } 52 | }); 53 | }; 54 | loop(otherProps.options); 55 | return
{data.join(' / ')}
; 56 | } 57 | return null; 58 | } 59 | 60 | // 如果有rules 61 | if (rules && rules.length) { 62 | formFieldOptions.rules = rules; 63 | } 64 | 65 | // 如果需要onChange 66 | if (typeof onChange === 'function') { 67 | formFieldOptions.onChange = (value, selectedOptions) => 68 | onChange(form, value, selectedOptions); // form, value, selectedOptions 69 | } 70 | 71 | const props = { 72 | placeholder: placeholder || `请选择${otherProps.title}`, 73 | ...otherProps 74 | }; 75 | 76 | if (getPopupContainer) { 77 | props.getPopupContainer = getPopupContainer; 78 | } 79 | 80 | return getFieldDecorator(name, formFieldOptions)( 81 | 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/components/Panel/style/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | @panel-prefix-cls: antui-panel; 4 | 5 | // panel styles 6 | // ----------------------------- 7 | .@{panel-prefix-cls} { 8 | position: relative; 9 | margin-bottom: 20px; 10 | background-color: #ffffff; 11 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); 12 | display: flex; 13 | flex-direction: column; 14 | border-radius: 2px; 15 | &-header { 16 | flex: none; 17 | position: relative; 18 | height: 40px; 19 | line-height: 36px; 20 | background: #fafafa; 21 | color: #666666; 22 | font-size: 13px; 23 | font-weight: 600; 24 | padding: 0 8px; 25 | border: 1px solid #e5e5e5; 26 | border-bottom: 0; 27 | overflow: hidden; 28 | &-title { 29 | padding-left: 6px; 30 | float: left; 31 | } 32 | &-controls { 33 | float: right; 34 | display: block; 35 | cursor: pointer; 36 | > a { 37 | padding-top: 1px; 38 | display: inline-block; 39 | opacity: 0.7; 40 | width: 30px; 41 | color: #999; 42 | font-size: 14px; 43 | font-weight: normal; 44 | text-align: center; 45 | &:hover { 46 | opacity: 1; 47 | color: @primary-color; 48 | } 49 | } 50 | } 51 | } 52 | &-body { 53 | flex: auto; 54 | display: flex; 55 | position: relative; 56 | border: 1px solid #e5e5e5; 57 | transition: .2s ease-out; 58 | max-height: 800px; 59 | overflow: hidden; 60 | .panel-content { 61 | padding: 15px; 62 | width: 100%; 63 | flex: auto; 64 | } 65 | } 66 | &.panel-fullscreen { 67 | z-index: 999 !important; 68 | position: fixed !important; 69 | width: 100% !important; 70 | height: 100% !important; 71 | top: 0 !important; 72 | right: 0 !important; 73 | left: 0 !important; 74 | bottom: 0 !important; 75 | } 76 | &.panel-collapsed { 77 | .antui-panel-body { 78 | max-height: 10px; 79 | } 80 | } 81 | &.cover { 82 | .panel-content { 83 | padding: 0; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/components/Radar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import G2 from 'components/Charts/G2'; 3 | import DataSet from '@antv/data-set'; 4 | const { Chart, Axis, Geom, Tooltip, Legend, Coord } = G2; 5 | const { DataView } = DataSet; 6 | const data = [ 7 | { item: 'Design', a: 70, b: 30 }, 8 | { item: 'Development', a: 60, b: 70 }, 9 | { item: 'Marketing', a: 50, b: 60 }, 10 | { item: 'Users', a: 40, b: 50 }, 11 | { item: 'Test', a: 60, b: 70 }, 12 | { item: 'Language', a: 70, b: 50 }, 13 | { item: 'Technology', a: 50, b: 40 }, 14 | { item: 'Support', a: 30, b: 40 }, 15 | { item: 'Sales', a: 60, b: 40 }, 16 | { item: 'UX', a: 50, b: 60 } 17 | ]; 18 | const dv = new DataView().source(data); 19 | dv.transform({ 20 | type: 'fold', 21 | fields: ['a', 'b'], // 展开字段集 22 | key: 'user', // key字段 23 | value: 'score' // value字段 24 | }); 25 | 26 | const cols = { 27 | score: { 28 | min: 0, 29 | max: 80 30 | } 31 | }; 32 | 33 | export default props => ( 34 | 39 | 40 | 51 | 52 | 64 | 65 | 66 | 67 | 79 | 80 | ); 81 | -------------------------------------------------------------------------------- /src/routes/Dashboard/components/index.less: -------------------------------------------------------------------------------- 1 | .dashboard-page { 2 | .antui-panel { 3 | &.qq, 4 | &.wechat, 5 | &.skype, 6 | &.github, 7 | &.qq { 8 | .panel-content { 9 | padding: 5px; 10 | padding-left: 20px; 11 | h2 { 12 | position: relative; 13 | color: #f5f2fd; 14 | font-size: 24px; 15 | line-height: 15px; 16 | margin-top: 15px; 17 | } 18 | h5 { 19 | position: relative; 20 | color: #f5f2fd; 21 | font-size: 13px; 22 | } 23 | .antui-icon { 24 | position: absolute; 25 | opacity: 0.8; 26 | right: 0; 27 | top: 10px; 28 | line-height: 100px; 29 | font-size: 100px; 30 | } 31 | } 32 | } 33 | &.qq { 34 | background: #967adc; 35 | .antui-icon { 36 | color: tint(#967adc); 37 | } 38 | } 39 | &.wechat { 40 | background: #70ca63; 41 | .antui-icon { 42 | color: tint(#70ca63); 43 | } 44 | } 45 | &.skype { 46 | background: #e9573f; 47 | .antui-icon { 48 | color: tint(#e9573f); 49 | } 50 | } 51 | &.github { 52 | background: #3bafda; 53 | .antui-icon { 54 | color: tint(#3bafda); 55 | } 56 | } 57 | } 58 | @media (min-width: 768px) { 59 | .sales-order { 60 | display: block !important; 61 | } 62 | } 63 | .sales-order { 64 | display: none; 65 | width: 300px; 66 | padding: 0 10px 0 40px; 67 | ul { 68 | margin-top: 20px; 69 | li { 70 | margin-top: 10px; 71 | span:first-child { 72 | margin-right: 20px; 73 | display: inline-block; 74 | width: 20px; 75 | height: 20px; 76 | border-radius: 20px; 77 | background: #efefef; 78 | line-height: 20px; 79 | text-align: center; 80 | font-size: 12px; 81 | color: #333; 82 | font-weight: 700; 83 | } 84 | span:last-child { 85 | float: right; 86 | } 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/routes/UI/Mask/components/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Layout, Button } from 'antd'; 4 | import BaseComponent from 'components/BaseComponent'; 5 | import Panel from 'components/Panel'; 6 | import Mask from 'components/Mask'; 7 | import avatar from 'assets/images/avatar.jpg'; 8 | const { Content } = Layout; 9 | 10 | @connect() 11 | export default class extends BaseComponent { 12 | state = { 13 | mask: { 14 | visible: false 15 | } 16 | }; 17 | 18 | toggleMask = props => { 19 | this.setState({ 20 | mask: { 21 | ...props, 22 | visible: !this.state.mask.visible 23 | } 24 | }); 25 | }; 26 | 27 | render() { 28 | return ( 29 | 30 | 31 | 32 |

Mask组件可以包含任意组件,形成遮罩效果

33 | 34 | 35 | 38 | 48 | 51 | 52 |
53 |
54 | this.toggleMask()} 56 | {...this.state.mask} 57 | > 58 | logo 70 | 71 |
72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Drag/Drag.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | /** 5 | * 允许拖拽组件 6 | */ 7 | export default class Drag extends React.Component { 8 | static propTypes = { 9 | children: PropTypes.element.isRequired 10 | }; 11 | static defaultProps = { 12 | //默认是移动children dom,覆盖该方法,可以把tranform行为同步给外部 13 | updateTransform: (transformStr, tx, ty, tdom) => { 14 | tdom.style.transform = transformStr; 15 | } 16 | }; 17 | position = { 18 | startX: 0, 19 | startY: 0, 20 | dx: 0, 21 | dy: 0, 22 | tx: 0, 23 | ty: 0 24 | }; 25 | start = event => { 26 | if (event.button !== 0) { 27 | //只允许左键,右键问题在于不选择conextmenu就不会触发mouseup事件 28 | return; 29 | } 30 | document.addEventListener('mousemove', this.docMove); 31 | this.position.startX = event.pageX - this.position.dx; 32 | this.position.startY = event.pageY - this.position.dy; 33 | }; 34 | docMove = event => { 35 | const tx = event.pageX - this.position.startX; 36 | const ty = event.pageY - this.position.startY; 37 | const transformStr = `translate(${tx}px,${ty}px)`; 38 | this.props.updateTransform(transformStr, tx, ty, this.tdom); 39 | this.position.dx = tx; 40 | this.position.dy = ty; 41 | }; 42 | docMouseUp = event => { 43 | document.removeEventListener('mousemove', this.docMove); 44 | }; 45 | componentDidMount() { 46 | this.tdom.addEventListener('mousedown', this.start); 47 | //用document移除对mousemove事件的监听 48 | document.addEventListener('mouseup', this.docMouseUp); 49 | } 50 | componentWillUnmount() { 51 | this.tdom.removeEventListener('mousedown', this.start); 52 | document.removeEventListener('mouseup', this.docMouseUp); 53 | document.removeEventListener('mousemove', this.docMove); 54 | } 55 | render() { 56 | const { children } = this.props; 57 | const newStyle = { 58 | ...children.props.style, 59 | cursor: 'move', 60 | userSelect: 'none' 61 | }; 62 | return React.cloneElement(React.Children.only(children), { 63 | ref: tdom => { 64 | return (this.tdom = tdom); 65 | }, 66 | style: newStyle 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/CSSAnimate/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ReactDOM from 'react-dom'; 4 | import cssAnimate, { isCssAnimationSupported } from 'css-animation'; 5 | import cx from 'classnames'; 6 | import omit from 'object.omit'; 7 | 8 | class CSSAnimate extends PureComponent { 9 | static defaultProps = { 10 | component: 'div' 11 | }; 12 | static propTypes = { 13 | type: PropTypes.string, // 动画名称 14 | callback: PropTypes.func, // 动画结束的回调函数 15 | duration: PropTypes.number, // 动画持续时间 16 | delay: PropTypes.number, // 动画延时 17 | component: PropTypes.string // 容器 18 | }; 19 | 20 | componentDidMount() { 21 | const { type, callback } = this.props; 22 | this.animate(type, callback); 23 | } 24 | 25 | componentDidUpdate(prevProps, prevState) { 26 | const { type, callback } = this.props; 27 | this.animate(type, callback); 28 | } 29 | 30 | animate = (type, callback) => { 31 | const node = ReactDOM.findDOMNode(this); 32 | 33 | if (isCssAnimationSupported && type) { 34 | cssAnimate(node, type, callback); 35 | } else if (!isCssAnimationSupported) { 36 | console.warn('不支持css动画'); 37 | } 38 | }; 39 | 40 | render() { 41 | const { 42 | className, 43 | children, 44 | delay, 45 | duration, 46 | style, 47 | component, 48 | ...otherProps 49 | } = this.props; 50 | const Component = component; 51 | const classnames = cx('animated', className); 52 | const _style = { ...style }; 53 | if (duration) { 54 | _style.animationDuration = duration + 'ms'; 55 | _style.WebkitAnimationDuration = duration + 'ms'; 56 | } 57 | 58 | if (delay) { 59 | _style.animationDelay = delay + 'ms'; 60 | _style.WebkitAnimationDelay = delay + 'ms'; 61 | } 62 | 63 | const divProps = omit(otherProps, [ 64 | 'type', 65 | 'callback', 66 | 'delay', 67 | 'duration' 68 | ]); 69 | 70 | return ( 71 | 72 | {children} 73 | 74 | ); 75 | } 76 | } 77 | 78 | export default CSSAnimate; 79 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/EC/components/Pie.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import EC from 'components/Charts/ECharts/EC'; 3 | import 'echarts/lib/chart/pie'; 4 | import 'echarts/lib/component/tooltip'; 5 | import 'echarts/lib/component/title'; 6 | 7 | export default class Events extends React.PureComponent { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | cnt: 0 12 | }; 13 | } 14 | getOption = () => ({ 15 | title: { 16 | text: '某站点用户访问来源', 17 | subtext: '纯属虚构', 18 | x: 'center' 19 | }, 20 | tooltip: { 21 | trigger: 'item', 22 | formatter: '{a}
{b} : {c} ({d}%)' 23 | }, 24 | legend: { 25 | orient: 'vertical', 26 | left: 'left', 27 | data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'] 28 | }, 29 | series: [ 30 | { 31 | name: '访问来源', 32 | type: 'pie', 33 | radius: '55%', 34 | center: ['50%', '60%'], 35 | data: [ 36 | { value: 335, name: '直接访问' }, 37 | { value: 310, name: '邮件营销' }, 38 | { value: 234, name: '联盟广告' }, 39 | { value: 135, name: '视频广告' }, 40 | { value: 1548, name: '搜索引擎' } 41 | ], 42 | itemStyle: { 43 | emphasis: { 44 | shadowBlur: 10, 45 | shadowOffsetX: 0, 46 | shadowColor: 'rgba(0, 0, 0, 0.5)' 47 | } 48 | } 49 | } 50 | ] 51 | }); 52 | 53 | onChartClick = (param, echarts) => { 54 | console.log(param, echarts); 55 | alert('chart click'); 56 | this.setState({ 57 | cnt: this.state.cnt + 1 58 | }); 59 | }; 60 | 61 | onChartLegendselectchanged = (param, echart) => { 62 | console.log(param, echart); 63 | alert('chart legendselectchanged'); 64 | }; 65 | 66 | onChartReady = echarts => { 67 | console.log('echart is ready', echarts); 68 | }; 69 | 70 | render() { 71 | let onEvents = { 72 | click: this.onChartClick, 73 | legendselectchanged: this.onChartLegendselectchanged 74 | }; 75 | 76 | return ( 77 | 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/components/SideLayout/SideLayout.js: -------------------------------------------------------------------------------- 1 | import './style/index.less'; 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { Layout } from 'antd'; 5 | import cx from 'classnames'; 6 | import Icon from '../Icon'; 7 | const { Content, Sider } = Layout; 8 | 9 | class SideLayout extends Component { 10 | static propTypes = { 11 | prefix: PropTypes.string, 12 | width: PropTypes.number, 13 | title: PropTypes.string, 14 | sideContent: PropTypes.node, 15 | children: PropTypes.node, 16 | fixed: PropTypes.bool 17 | }; 18 | 19 | static defaultProps = { 20 | prefixCls: 'antui-side-layout', 21 | width: 180 22 | }; 23 | 24 | state = { 25 | openSide: true 26 | }; 27 | 28 | toggle = e => { 29 | e.stopPropagation(); 30 | e.preventDefault(); 31 | 32 | this.setState({ 33 | openSide: !this.state.openSide 34 | }); 35 | }; 36 | 37 | render() { 38 | const { 39 | prefixCls, 40 | className, 41 | sideContent, 42 | children, 43 | title, 44 | width 45 | } = this.props; 46 | const { openSide } = this.state; 47 | return ( 48 | 49 | 56 | 57 | 58 | 59 |
63 |
64 |
65 | 66 |   67 | {title} 68 |
69 |
{sideContent}
70 |
71 |
72 |
73 | {children} 74 |
75 | ); 76 | } 77 | } 78 | 79 | export default SideLayout; 80 | -------------------------------------------------------------------------------- /docs/mock.md: -------------------------------------------------------------------------------- 1 | # Mock 数据写法 2 | 3 | 我们使用Mock.js生成随机数据,所以**mock**数据前先到其[官网](http://mockjs.com)了解一下。 4 | 5 | ## 如何注册模拟接口 6 | 7 | 0. 在`src/__mocks__`下新建一个文件,例如demo.js。 8 | 1. 在`src/index.js`中注册新建的demo.js。 9 | ```js 10 | import packMock from '@/utils/packMock'; 11 | import demo from './demo'; 12 | 13 | packMock( 14 | demo, 15 | ); 16 | ``` 17 | 2. 在demo.js中模拟需要的接口。 18 | 19 | ## 使用方式 20 | 21 | ### 直接暴露简单对象 22 | demo.js 23 | ```js 24 | export default { 25 | // Get方式 后跟接口地址 26 | 'GET /api/getUserInfo': { 27 | name: '小雨', 28 | sex: '男', 29 | age: 18, 30 | }, 31 | // 可以不指定请求方式 32 | '/api/getUserInfo1': ['one', 'two', 'three'], 33 | } 34 | ``` 35 | 36 | ### 使用API函数,可生成随机数据 37 | demo.js 38 | ```js 39 | export default ({ fetchMock, delay, mock, toSuccess, toError }) => { 40 | return { 41 | '/api/charts/bar1': options => { 42 | return toSuccess( 43 | mock([ 44 | { year: '1951 年', "sales|1-100": 100 }, // 1-100 的随机数 45 | { year: '1952 年', "sales|1-100": 100 }, 46 | { year: '1956 年', "sales|1-100": 100 }, 47 | { year: '1957 年', "sales|1-100": 100 }, 48 | { year: '1958 年', "sales|1-100": 100 }, 49 | ]), 50 | 400 51 | ); 52 | }, 53 | }; 54 | }; 55 | ``` 56 | 57 | ## API 58 | 59 | ### `delay` 60 | 为了让模拟接口更直实,可以增加一个延时,单位ms,例如: 61 | ```js 62 | // 随机延时 63 | delay({ no: 123 }) // { no: 123 } 64 | 65 | // 200毫秒延时 66 | delay({ no: 123 }, 200) // { no: 123 } 67 | ``` 68 | 69 | ### `mock` 70 | 如果需要生成随机数,需要使用用`mock`函数,mock函数写法参考 http://mockjs.com/examples.html 71 | ```js 72 | mock({ "string|1-10": "★" }) // { "string": "★★★★★★★" } 73 | ``` 74 | 75 | ### toSuccess | toError 76 | 这个即为全局配置文件`src/config.js`中的`mock`下的`toSuccess,toError`,可以让我们模拟接口时少写几行代码。 77 | ```js 78 | // 随机延时 79 | toSuccess(mock({ 80 | "string|1-10": "★" 81 | })) 82 | // { status: true, data: { string: "★★" } } 83 | 84 | // 增加400ms延时 85 | toSuccess(mock({ 86 | "string|1-10": "★" 87 | }), 400) 88 | // { status: true, data: { string: "★★★★" } } 89 | ``` 90 | 91 | ### `fetchMock` 92 | fetchMock可以拦截请求,使用模拟数据代替真实接口数据,框架本身已装包装好了fetchMock的实现,如果现有封装不满足,可以自行扩展,[FetchMock官网](http://www.wheresrhys.co.uk/fetch-mock/api),一般不需要扩展。 93 | 94 | 95 | ## 对接直实接口 96 | 当后端提供给我们真实接口的时候,我们需要替换我们的模拟接口,这里我只是简单的把模拟接口进行注释掉,或在`__mocks__/index.js`中注释掉这个文件,希望大家提供一个更好的方式。 97 | 98 | -------------------------------------------------------------------------------- /src/routes/Widgets/Charts/G2/components/Pie2.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import G2 from 'components/Charts/G2'; 3 | const { Chart, Axis, Geom, Tooltip, Legend, Coord, Label } = G2; 4 | 5 | const data = [ 6 | { country: '中国', cost: 96 }, 7 | { country: '德国', cost: 121 }, 8 | { country: '美国', cost: 100 }, 9 | { country: '日本', cost: 111 }, 10 | { country: '韩国', cost: 102 }, 11 | { country: '法国', cost: 124 }, 12 | { country: '意大利', cost: 123 }, 13 | { country: '荷兰', cost: 111 }, 14 | { country: '比利时', cost: 123 }, 15 | { country: '英国', cost: 109 }, 16 | { country: '加拿大', cost: 115 }, 17 | { country: '俄罗斯', cost: 99 }, 18 | { country: '墨西哥', cost: 91 }, 19 | { country: '印度', cost: 87 }, 20 | { country: '瑞士', cost: 125 }, 21 | { country: '澳大利亚', cost: 130 }, 22 | { country: '西班牙', cost: 109 }, 23 | { country: '巴西', cost: 123 }, 24 | { country: '泰国', cost: 91 }, 25 | { country: '印尼', cost: 83 }, 26 | { country: '波兰', cost: 101 }, 27 | { country: '瑞典', cost: 116 }, 28 | { country: '奥地利', cost: 111 }, 29 | { country: '捷克', cost: 107 } 30 | ]; 31 | const cols = { 32 | cost: { 33 | min: 0 34 | } 35 | }; 36 | 37 | export default props => ( 38 | 43 | 44 | 53 | 66 | 67 | 68 | 77 | 87 | 88 | ); 89 | -------------------------------------------------------------------------------- /docs/columns.md: -------------------------------------------------------------------------------- 1 | ## column.js 2 | 3 | column.js 可以同时作用于DataTable组件,Form组件,SearchBar组件,定义其数据结构。 4 | 5 | ## 说明 6 | 7 | column 反回为一个json数组 8 | ```javascript 9 | [ 10 | { 11 | title: '年龄', // 列名 12 | name: 'age', // 唯一标识 13 | dict: [], // 下拉或级联中用到的数据 14 | formItem: { // 生成表单结构 15 | type: 'number', // 表单元素类型 16 | width: 100, // 表单元素宽度 17 | rules: [], // 表单验证规则 18 | render: (record, form) => (), // 当type:custom时,自定义渲染 19 | ...other // 其它附加属性,会被注入对应的元素中 20 | }, 21 | tableItem: { // 生成表格结构 22 | width: 100, // 表格元素宽度 23 | type: 'oper|default' // 这个列 24 | render: (text, record) => (), // 自定义渲染 25 | ...other // 其它附加属性,会被注入对应的元素中, 参考antd Table的column配置 26 | }, 27 | searchItem: { // 生成搜索项结构 28 | type: 'number', // 搜索项类型 29 | width: 100, // 搜索项元素宽度 30 | rules: [], // 搜索项验证规则 31 | render: (record, form) => (), // 当type:custom时,自定义渲染 32 | ...other // 其它附加属性,会被注入对应的元素中 33 | } 34 | }, 35 | { 36 | ... 37 | } 38 | ] 39 | 40 | ``` 41 | 42 | ## 例子 43 | 44 | ```javascript 45 | // app.js 46 | 47 | import React from 'react'; 48 | import {Link} from 'react-router'; 49 | import {Button, Icon} from '../../../components'; 50 | 51 | export default (clazz) => { 52 | let columns = [ 53 | { 54 | name: "cnId", 55 | formItem: { 56 | type: "hidden" 57 | } 58 | }, 59 | { 60 | title: "字典类型", 61 | name: "codeType", 62 | formItem: { 63 | rules: [{ 64 | required: true, 65 | message: '请输入字典值' 66 | }, { 67 | pattern: /^[a-zA-Z0-9_]{1,20}$/, 68 | message: '字典类型不允许输入特殊字符' 69 | }] 70 | }, 71 | tableItem: {}, 72 | searchable: { 73 | width: 120 74 | }, 75 | }, 76 | { 77 | title: "操作", 78 | tableItem: { 79 | width: 120, 80 | render: (text, record) => ( 81 | 82 | 85 | 88 | 89 | ) 90 | }, 91 | } 92 | ]; 93 | 94 | return columns; 95 | }; 96 | 97 | ``` 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dva-boot-admin", 3 | "version": "3.0.0", 4 | "description": "dva-boot-admin", 5 | "homepage": "/dva-boot-admin", 6 | "repository": { 7 | "type": "git", 8 | "url": "git@github.com:LANIF-UI/dva-boot-admin.git" 9 | }, 10 | "license": "MIT", 11 | "scripts": { 12 | "start": "react-app-rewired start", 13 | "build": "react-app-rewired build", 14 | "test": "react-app-rewired test --env=jsdom", 15 | "eject": "react-scripts eject", 16 | "gh-pages": "react-app-rewired build && gh-pages -d build" 17 | }, 18 | "dependencies": { 19 | "@ant-design/compatible": "^1.0.1", 20 | "@ant-design/icons": "^4.0.3", 21 | "@antv/data-set": "^0.8.9", 22 | "antd": "^4.0.3", 23 | "bizcharts": "^3.5.9", 24 | "classnames": "^2.2.5", 25 | "cmn-utils": "^1.0.10", 26 | "css-animation": "^1.5.0", 27 | "css-element-queries": "^1.0.2", 28 | "dva": "^2.6.0-beta.20", 29 | "dva-loading": "^3.0.20", 30 | "echarts": "^4.1.0", 31 | "enquire.js": "^2.1.6", 32 | "lazysizes": "^4.0.4", 33 | "masonry-layout": "^4.2.1", 34 | "object.omit": "^3.0.0", 35 | "path-to-regexp": "^2.2.1", 36 | "react": "^16.13.0", 37 | "react-dom": "^16.13.0", 38 | "react-fast-compare": "^3.0.1", 39 | "react-transition-group": "^4.3.0", 40 | "wangeditor": "^3.1.1" 41 | }, 42 | "devDependencies": { 43 | "@babel/plugin-proposal-decorators": "^7.3.0", 44 | "babel-plugin-import": "^1.11.0", 45 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 46 | "customize-cra": "^1.0.0-alpha.0", 47 | "fetch-mock": "^6.4.3", 48 | "gh-pages": "^2.0.1", 49 | "http-proxy-middleware": "^0.19.1", 50 | "less": "^3.8.1", 51 | "less-loader": "^4.1.0", 52 | "mockjs": "^1.1.0", 53 | "prop-types": "^15.6.1", 54 | "react-app-rewired": "^2.1.5", 55 | "react-scripts": "^3.4.0", 56 | "webpack-bundle-analyzer": "^3.3.2" 57 | }, 58 | "create-template": { 59 | "ignoreScript": [ 60 | "gh-pages" 61 | ], 62 | "ignoreDevDependencies": [ 63 | "gh-pages" 64 | ], 65 | "ignore": [ 66 | ".git", 67 | "CHANGELOG.md", 68 | "LICENSE", 69 | "README.*.md" 70 | ], 71 | "route": { 72 | "nameCase": "pascal", 73 | "importStyle": "func" 74 | } 75 | }, 76 | "browserslist": [ 77 | "> 1%", 78 | "last 2 versions", 79 | "not ie <= 11" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /src/components/Button/style/ripple.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @default-color: rgba(0, 0 ,0, 0.08); 3 | 4 | .ripple-btn { 5 | position: relative; 6 | display: inline-block; 7 | padding: 8px 16px; 8 | font-size: 14px; 9 | min-width: 64px; 10 | min-height: 36px; 11 | box-sizing: border-box; 12 | line-height: 1.5; 13 | border-radius: 4px; 14 | font-weight: 500; 15 | overflow: hidden; 16 | transition: background-color 250ms; 17 | border: none; 18 | > span { 19 | user-select: none; 20 | position: relative; 21 | z-index: 1; 22 | } 23 | > .ripple-animate { 24 | position: absolute; 25 | z-index: 0; 26 | border-radius: 50%; 27 | transition: transform 700ms, opacity 700ms; 28 | transition-timing-function: cubic-bezier(0.250, 0.460, 0.450, 0.940); 29 | background-position: center; 30 | background-repeat: no-repeat; 31 | background-size: 100%; 32 | transform: scale(0); 33 | pointer-events: none; 34 | } 35 | color: #000; 36 | background: transparent; 37 | } 38 | 39 | .ripple-btn { 40 | &:not(.ghost) { 41 | & > .ripple-animate { 42 | background: rgba(0, 0, 0, 0.4); 43 | } 44 | color: #000; 45 | background: #d5d5d5; 46 | &.primary { 47 | color: #fff; 48 | background: @primary-color; 49 | & > .ripple-animate { 50 | background: rgba(255, 255, 255, .4); 51 | } 52 | } 53 | &.danger { 54 | color: #fff; 55 | background: @error-color; 56 | & > .ripple-animate { 57 | background: rgba(255, 255, 255, .4); 58 | } 59 | } 60 | } 61 | &.ghost { 62 | & > .ripple-animate { 63 | background: rgba(0, 0, 0, 0.4); 64 | } 65 | color: #000; 66 | background: transparent; 67 | &:hover, 68 | &:active { 69 | background: rgba(0, 0, 0, 0.08);; 70 | } 71 | &.primary { 72 | color: @primary-color; 73 | background: transparent; 74 | &:hover, 75 | &:active { 76 | background: fadeOut(@primary-color, 80%); 77 | } 78 | & > .ripple-animate { 79 | background: fadeOut(@primary-color, 60%); 80 | } 81 | } 82 | &.danger { 83 | color: @error-color; 84 | background: transparent; 85 | &:hover, 86 | &:active { 87 | background: fadeOut(@error-color, 80%); 88 | } 89 | & > .ripple-animate { 90 | background: fadeOut(@error-color, 60%); 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/components/Form/model/editor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Editor from '../../Editor'; 4 | import $$ from 'cmn-utils'; 5 | import omit from 'object.omit'; 6 | 7 | class EditorControlled extends Component { 8 | static propTypes = { 9 | value: PropTypes.string, 10 | onChange: PropTypes.func 11 | }; 12 | 13 | constructor(props) { 14 | super(props); 15 | const { value } = props; 16 | this.state = { 17 | value 18 | }; 19 | } 20 | 21 | componentDidUpdate(prevProps, prevState) { 22 | if (this.props.value !== prevProps.value) { 23 | this.setState({ value: this.props.value }); 24 | } 25 | } 26 | 27 | triggerChange = value => { 28 | const onChange = this.props.onChange; 29 | if (onChange) { 30 | onChange(value); 31 | } 32 | }; 33 | 34 | render() { 35 | const { value } = this.state; 36 | const otherProps = omit(this.props, 'value'); 37 | 38 | return ( 39 | 40 | ); 41 | } 42 | } 43 | 44 | /** 45 | * EditorForm组件 46 | */ 47 | export default ({ 48 | form, 49 | name, 50 | formFieldOptions = {}, 51 | record, 52 | initialValue, 53 | rules, 54 | onChange, 55 | normalize, 56 | preview, 57 | getPopupContainer, 58 | ...otherProps 59 | }) => { 60 | const { getFieldDecorator } = form; 61 | 62 | let initval = initialValue; 63 | 64 | if (record) { 65 | initval = record[name]; 66 | } 67 | 68 | // 如果存在初始值 69 | if (initval !== null && typeof initval !== 'undefined') { 70 | if ($$.isFunction(normalize)) { 71 | formFieldOptions.initialValue = normalize(initval); 72 | } else { 73 | formFieldOptions.initialValue = initval; 74 | } 75 | } 76 | 77 | if (preview) { 78 | return ( 79 |
83 | ); 84 | } 85 | 86 | // 如果有rules 87 | if (rules && rules.length) { 88 | formFieldOptions.rules = rules; 89 | } 90 | 91 | // 如果需要onChange 92 | if (typeof onChange === 'function') { 93 | formFieldOptions.onChange = value => onChange(form, value); // form, value 94 | } 95 | 96 | return getFieldDecorator(name, formFieldOptions)( 97 | 98 | ); 99 | }; 100 | --------------------------------------------------------------------------------