├── mock ├── .gitkeep ├── .DS_Store └── Report │ └── dynamic.js ├── src ├── components │ ├── Compare │ │ ├── index.less │ │ └── index.js │ ├── Charts │ │ ├── Bar │ │ │ ├── shape │ │ │ │ ├── index.js │ │ │ │ ├── shape.js │ │ │ │ ├── borderRadius.js │ │ │ │ └── baseLine.js │ │ │ └── index.d.ts │ │ ├── LineOrArea │ │ │ ├── index.less │ │ │ ├── index.d.ts │ │ │ └── index.js │ │ ├── index.js │ │ ├── g2.js │ │ ├── WaterWave │ │ │ ├── index.d.ts │ │ │ └── index.less │ │ ├── index.less │ │ ├── Pie │ │ │ ├── index.d.ts │ │ │ └── index.less │ │ ├── index.d.ts │ │ └── autoHeight.js │ ├── .DS_Store │ ├── CountCard │ │ ├── .DS_Store │ │ ├── DailyCard │ │ │ ├── index.less │ │ │ └── index.js │ │ └── MonthlyCard │ │ │ ├── index.less │ │ │ └── index.js │ ├── TabNav │ │ ├── index.less │ │ └── index.js │ ├── NoticeIcon │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.less │ │ ├── NoticeIconTab.d.ts │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── NoticeList.less │ │ ├── NoticeList.js │ │ └── index.js │ ├── Exception │ │ ├── demo │ │ │ ├── 404.md │ │ │ ├── 500.md │ │ │ └── 403.md │ │ ├── index.d.ts │ │ ├── typeConfig.js │ │ ├── index.md │ │ ├── index.js │ │ └── index.less │ ├── Empty │ │ ├── index.less │ │ └── index.js │ ├── utils │ │ ├── pathTools.js │ │ └── pathTools.test.js │ ├── Progress │ │ ├── index.less │ │ └── index.js │ ├── Result │ │ ├── index.d.ts │ │ ├── demo │ │ │ ├── structure.md │ │ │ ├── error.md │ │ │ └── classic.md │ │ ├── index.md │ │ ├── index.js │ │ └── index.less │ ├── GlobalFooter │ │ ├── index.md │ │ ├── index.d.ts │ │ ├── index.less │ │ ├── index.js │ │ └── demo │ │ │ └── basic.md │ ├── NoData │ │ ├── index.less │ │ └── index.js │ ├── HeaderSearch │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── index.less │ │ ├── demo │ │ │ └── basic.md │ │ └── index.js │ ├── Authorized │ │ ├── Authorized.js │ │ ├── demo │ │ │ ├── basic.md │ │ │ ├── secured.md │ │ │ ├── AuthorizedArray.md │ │ │ └── AuthorizedFunction.md │ │ ├── AuthorizedRoute.js │ │ ├── index.js │ │ ├── index.d.ts │ │ ├── PromiseRender.js │ │ ├── CheckPermissions.test.js │ │ ├── CheckPermissions.js │ │ ├── Secured.js │ │ └── index.md │ ├── NumberInfo │ │ ├── demo │ │ │ └── basic.md │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── index.js │ │ └── index.less │ ├── Login │ │ ├── LoginSubmit.js │ │ ├── LoginTab.js │ │ ├── index.less │ │ ├── index.d.ts │ │ ├── index.zh-CN.md │ │ ├── map.js │ │ ├── index.en-US.md │ │ ├── LoginItem.js │ │ ├── index.js │ │ └── demo │ │ │ └── basic.md │ ├── SiderMenu │ │ ├── index.js │ │ ├── SilderMenu.test.js │ │ └── index.less │ ├── PageHeader │ │ ├── demo │ │ │ ├── simple.md │ │ │ ├── structure.md │ │ │ ├── image.md │ │ │ └── standard.md │ │ ├── index.d.ts │ │ ├── index.md │ │ ├── index.test.js │ │ └── index.less │ └── GlobalHeader │ │ ├── index.less │ │ └── index.js ├── routes │ ├── Dashboard │ │ ├── Indicator │ │ │ ├── Quality.less │ │ │ ├── Surgery.less │ │ │ ├── Bedspace.less │ │ │ ├── index.js │ │ │ ├── Consumable.less │ │ │ ├── Appointment.less │ │ │ ├── Emphasis.less │ │ │ └── Consumable.js │ │ ├── Report │ │ │ ├── Inpatient.less │ │ │ ├── Prescription.less │ │ │ ├── Revenue.less │ │ │ ├── index.js │ │ │ ├── Clinic.less │ │ │ ├── Dynamic.less │ │ │ └── NonDrug.less │ │ └── .DS_Store │ ├── .DS_Store │ └── Exception │ │ ├── style.less │ │ ├── 403.js │ │ ├── 404.js │ │ ├── 500.js │ │ └── triggerException.js ├── .DS_Store ├── assets │ ├── logo.png │ └── empty.png ├── layouts │ ├── BlankLayout.js │ ├── PageHeaderLayout.less │ └── PageHeaderLayout.js ├── theme.js ├── services │ ├── user.js │ └── error.js ├── models │ ├── index.js │ ├── consumable.js │ ├── surgery.js │ ├── quality.js │ ├── nonDrug.js │ ├── inpatient.js │ ├── appointment.js │ ├── prescription.js │ ├── emphasis.js │ ├── error.js │ ├── income.js │ ├── bedspace.js │ ├── user.js │ ├── dynamic.js │ ├── clinic.js │ ├── tab.js │ ├── date.js │ └── global.js ├── utils │ ├── authority.js │ ├── Authorized.js │ ├── utils.less │ └── request.js ├── rollbar.js ├── e2e │ ├── home.e2e.js │ └── login.e2e.js ├── index.ejs ├── index.js ├── common │ └── menu.js └── router.js ├── .ga ├── .DS_Store ├── dist ├── favicon.png ├── iconfonts │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ └── iconfont.css ├── index.f6d17bdf.js.gz ├── iconfonts_antd │ ├── iconfont.eot │ ├── iconfont.ttf │ └── iconfont.woff ├── static │ └── empty.a2074254.png └── index.html ├── public ├── .DS_Store ├── favicon.png ├── iconfonts │ ├── iconfont.eot │ ├── iconfont.ttf │ └── iconfont.woff └── iconfonts_antd │ ├── iconfont.eot │ ├── iconfont.ttf │ └── iconfont.woff ├── operation-data-center.png ├── .editorconfig ├── .gitignore ├── .webpackrc.js ├── appveyor.yml ├── .travis.yml ├── .stylelintrc ├── README.md ├── tests └── run-tests.js ├── .roadhogrc.mock.js ├── .eslintrc └── package.json /mock/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Compare/index.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Quality.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/Inpatient.less: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ga: -------------------------------------------------------------------------------- 1 | { 2 | "code":"UA-72788897-6" 3 | } 4 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Surgery.less: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/.DS_Store -------------------------------------------------------------------------------- /mock/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/mock/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /dist/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/favicon.png -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/.DS_Store -------------------------------------------------------------------------------- /src/components/Charts/Bar/shape/index.js: -------------------------------------------------------------------------------- 1 | import shape from './shape'; 2 | export default shape; -------------------------------------------------------------------------------- /src/components/Charts/LineOrArea/index.less: -------------------------------------------------------------------------------- 1 | .timelineChart { 2 | background: #fff; 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/assets/empty.png -------------------------------------------------------------------------------- /src/routes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/routes/.DS_Store -------------------------------------------------------------------------------- /src/components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/components/.DS_Store -------------------------------------------------------------------------------- /src/layouts/BlankLayout.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default props =>
; 4 | -------------------------------------------------------------------------------- /dist/iconfonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts/iconfont.eot -------------------------------------------------------------------------------- /dist/iconfonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts/iconfont.ttf -------------------------------------------------------------------------------- /dist/index.f6d17bdf.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/index.f6d17bdf.js.gz -------------------------------------------------------------------------------- /operation-data-center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/operation-data-center.png -------------------------------------------------------------------------------- /dist/iconfonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts/iconfont.woff -------------------------------------------------------------------------------- /public/iconfonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts/iconfont.eot -------------------------------------------------------------------------------- /public/iconfonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts/iconfont.ttf -------------------------------------------------------------------------------- /dist/iconfonts_antd/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts_antd/iconfont.eot -------------------------------------------------------------------------------- /dist/iconfonts_antd/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts_antd/iconfont.ttf -------------------------------------------------------------------------------- /dist/static/empty.a2074254.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/static/empty.a2074254.png -------------------------------------------------------------------------------- /public/iconfonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts/iconfont.woff -------------------------------------------------------------------------------- /src/routes/Dashboard/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/routes/Dashboard/.DS_Store -------------------------------------------------------------------------------- /dist/iconfonts_antd/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/dist/iconfonts_antd/iconfont.woff -------------------------------------------------------------------------------- /public/iconfonts_antd/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts_antd/iconfont.eot -------------------------------------------------------------------------------- /public/iconfonts_antd/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts_antd/iconfont.ttf -------------------------------------------------------------------------------- /src/components/CountCard/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/src/components/CountCard/.DS_Store -------------------------------------------------------------------------------- /public/iconfonts_antd/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pandly/operation-data-center/HEAD/public/iconfonts_antd/iconfont.woff -------------------------------------------------------------------------------- /src/routes/Exception/style.less: -------------------------------------------------------------------------------- 1 | .trigger { 2 | background: "red"; 3 | :global(.ant-btn) { 4 | margin-right: 8px; 5 | margin-bottom: 12px; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less 2 | module.exports = { 3 | 'primary-color': '#15984B', 4 | 'card-actions-background': '#f5f8fa', 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/TabNav/index.less: -------------------------------------------------------------------------------- 1 | .tabNav { 2 | background: #f9f9f9 !important; 3 | :global { 4 | .ant-tabs.ant-tabs-card > .ant-tabs-bar .ant-tabs-tab-active { 5 | background: #f3f5f4 !important; 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/services/user.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export async function query() { 4 | return request('/api/users'); 5 | } 6 | 7 | export async function queryCurrent() { 8 | return request('/api/currentUser'); 9 | } 10 | -------------------------------------------------------------------------------- /src/layouts/PageHeaderLayout.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .content { 4 | margin: 24px 24px 0; 5 | } 6 | 7 | @media screen and (max-width: @screen-sm) { 8 | .content { 9 | margin: 24px 0 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 通知图标 4 | --- 5 | 6 | 通常用在导航工具栏上。 7 | 8 | ````jsx 9 | import NoticeIcon from 'ant-design-pro/lib/NoticeIcon'; 10 | 11 | ReactDOM.render(, mountNode); 12 | ```` 13 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/Prescription.less: -------------------------------------------------------------------------------- 1 | .countCard { 2 | background: #F8FDFF; 3 | border: 1px solid #C5EAF9; 4 | border-radius: 6px; 5 | :global { 6 | .ant-card-body:before, .ant-card-body:after { 7 | content: none; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/Exception/demo/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 404 4 | --- 5 | 6 | 404 页面。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | 11 | ReactDOM.render( 12 | 13 | , mountNode); 14 | ```` 15 | -------------------------------------------------------------------------------- /src/components/Exception/demo/500.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 500 4 | --- 5 | 6 | 500 页面。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | 11 | ReactDOM.render( 12 | 13 | , mountNode); 14 | ```` 15 | -------------------------------------------------------------------------------- /src/components/Empty/index.less: -------------------------------------------------------------------------------- 1 | .empty { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | } 8 | .img { 9 | position: absolute; 10 | top: 50%; 11 | transform: translate(-50%, -50%); 12 | left: 50%; 13 | width: 1500px; 14 | } -------------------------------------------------------------------------------- /src/routes/Exception/403.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/Exception/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/routes/Exception/500.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import Exception from 'components/Exception'; 4 | 5 | export default () => ( 6 | 7 | ); 8 | -------------------------------------------------------------------------------- /src/components/Charts/index.js: -------------------------------------------------------------------------------- 1 | import './g2'; 2 | import Bar from './Bar'; 3 | import Pie from './Pie'; 4 | import WaterWave from './WaterWave'; 5 | import LineOrArea from './LineOrArea'; 6 | 7 | export { 8 | Bar, 9 | Pie, 10 | WaterWave, 11 | LineOrArea, 12 | }; 13 | -------------------------------------------------------------------------------- /src/components/Charts/g2.js: -------------------------------------------------------------------------------- 1 | // 全局 G2 设置 2 | import { track, setTheme } from 'bizcharts'; 3 | 4 | track(false); 5 | 6 | const config = { 7 | defaultColor: '#1089ff', 8 | shape: { 9 | interval: { 10 | fillOpacity: 1, 11 | }, 12 | }, 13 | }; 14 | 15 | setTheme(config); 16 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | // Use require.context to require reducers automatically 2 | // Ref: https://webpack.js.org/guides/dependency-management/#require-context 3 | const context = require.context('./', false, /\.js$/); 4 | export default context.keys().filter(item => item !== './index.js').map(key => context(key)); 5 | -------------------------------------------------------------------------------- /src/components/utils/pathTools.js: -------------------------------------------------------------------------------- 1 | // /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id'] 2 | export function urlToList(url) { 3 | const urllist = url.split('/').filter(i => i); 4 | return urllist.map((urlItem, index) => { 5 | return `/${urllist.slice(0, index + 1).join('/')}`; 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /src/components/Progress/index.less: -------------------------------------------------------------------------------- 1 | .progress { 2 | display: inline-flex; 3 | flex: 1; 4 | } 5 | .progressOuter { 6 | display: inline-block; 7 | transition: all .3s; 8 | } 9 | .progressInner { 10 | transition: all .3s; 11 | } 12 | .progressText { 13 | display: inline-block; 14 | text-align: center; 15 | width: 40px; 16 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # dependencies 3 | /node_modules 4 | # roadhog-api-doc ignore 5 | /src/utils/request-temp.js 6 | _roadhog-api-doc 7 | 8 | # production 9 | 10 | # misc 11 | .DS_Store 12 | npm-debug.log* 13 | yarn-error.log 14 | 15 | /coverage 16 | .idea 17 | yarn.lock 18 | package-lock.json 19 | *bak 20 | jsconfig.json 21 | .prettierrc 22 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IWaterWaveProps { 3 | title: React.ReactNode; 4 | color?: string; 5 | height: number; 6 | percent: number; 7 | style?: React.CSSProperties; 8 | } 9 | 10 | export default class WaterWave extends React.Component {} 11 | -------------------------------------------------------------------------------- /src/utils/authority.js: -------------------------------------------------------------------------------- 1 | // use localStorage to store the authority info, which might be sent from server in actual project. 2 | export function getAuthority() { 3 | return localStorage.getItem('antd-pro-authority') || 'admin'; 4 | } 5 | 6 | export function setAuthority(authority) { 7 | return localStorage.setItem('antd-pro-authority', authority); 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Empty/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import empty from '../../assets/empty.png'; 3 | import styles from './index.less'; 4 | 5 | const Empty = ({ 6 | style 7 | }) => { 8 | return ( 9 |
10 | empty 11 |
12 | ) 13 | } 14 | 15 | export default Empty; 16 | -------------------------------------------------------------------------------- /src/rollbar.js: -------------------------------------------------------------------------------- 1 | import Rollbar from 'rollbar'; 2 | 3 | // Track error by rollbar.com 4 | if (location.host === 'preview.pro.ant.design') { 5 | Rollbar.init({ 6 | accessToken: '033ca6d7c0eb4cc1831cf470c2649971', 7 | captureUncaught: true, 8 | captureUnhandledRejections: true, 9 | payload: { 10 | environment: 'production', 11 | }, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Result/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IResultProps { 3 | type: 'success' | 'error'; 4 | title: React.ReactNode; 5 | description?: React.ReactNode; 6 | extra?: React.ReactNode; 7 | actions?: React.ReactNode; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class Result extends React.Component {} 12 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: GlobalFooter 4 | zh-CN: GlobalFooter 5 | subtitle: 全局页脚 6 | cols: 1 7 | order: 7 8 | --- 9 | 10 | 页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - 17 | copyright | 版权信息 | ReactNode | - 18 | -------------------------------------------------------------------------------- /src/components/NoData/index.less: -------------------------------------------------------------------------------- 1 | .noData { 2 | position: absolute; 3 | top: 55%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | flex-direction: column; 10 | } 11 | .icon { 12 | font-size: 70px; 13 | color: #ddd; 14 | margin-bottom: 20px; 15 | } 16 | .text { 17 | font-size: 18px; 18 | color: #ccc; 19 | } -------------------------------------------------------------------------------- /src/services/error.js: -------------------------------------------------------------------------------- 1 | import request from '../utils/request'; 2 | 3 | export async function query404() { 4 | return request('/api/404'); 5 | } 6 | 7 | export async function query401() { 8 | return request('/api/401'); 9 | } 10 | 11 | export async function query403() { 12 | return request('/api/403'); 13 | } 14 | 15 | export async function query500() { 16 | return request('/api/500'); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Charts/index.less: -------------------------------------------------------------------------------- 1 | .miniChart { 2 | position: relative; 3 | width: 100%; 4 | .chartContent { 5 | position: absolute; 6 | bottom: -28px; 7 | width: 100%; 8 | > div { 9 | margin: 0 -5px; 10 | overflow: hidden; 11 | } 12 | } 13 | .chartLoading { 14 | position: absolute; 15 | top: 16px; 16 | left: 50%; 17 | margin-left: -7px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | export interface IGlobalFooterProps { 3 | links?: Array<{ 4 | title: React.ReactNode; 5 | href: string; 6 | blankTarget?: boolean; 7 | }>; 8 | copyright?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class GlobalFooter extends React.Component< 13 | IGlobalFooterProps, 14 | any 15 | > {} 16 | -------------------------------------------------------------------------------- /src/utils/Authorized.js: -------------------------------------------------------------------------------- 1 | import RenderAuthorized from '../components/Authorized'; 2 | import { getAuthority } from './authority'; 3 | 4 | let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line 5 | 6 | // Reload the rights component 7 | const reloadAuthorized = () => { 8 | Authorized = RenderAuthorized(getAuthority()); 9 | }; 10 | 11 | export { reloadAuthorized }; 12 | export default Authorized; 13 | -------------------------------------------------------------------------------- /src/components/Exception/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | export interface IExceptionProps { 3 | type?: "403" | "404" | "500"; 4 | title?: React.ReactNode; 5 | desc?: React.ReactNode; 6 | img?: string; 7 | actions?: React.ReactNode; 8 | linkElement?: React.ReactNode; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export default class Exception extends React.Component {} 13 | -------------------------------------------------------------------------------- /src/components/NoData/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from 'antd'; 3 | 4 | import styles from './index.less'; 5 | 6 | const NoData = ({ 7 | style 8 | }) => { 9 | return ( 10 |
11 | 12 |
暂无数据
13 |
14 | ) 15 | } 16 | 17 | export default NoData; -------------------------------------------------------------------------------- /src/components/Exception/demo/403.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 403 4 | --- 5 | 6 | 403 页面,配合自定义操作。 7 | 8 | ````jsx 9 | import Exception from 'ant-design-pro/lib/Exception'; 10 | import { Button } from 'antd'; 11 | 12 | const actions = ( 13 |
14 | 15 | 16 |
17 | ); 18 | ReactDOM.render( 19 | 20 | , mountNode); 21 | ```` 22 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IHeaderSearchProps { 3 | placeholder?: string; 4 | dataSource?: string[]; 5 | onSearch?: (value: string) => void; 6 | onChange?: (value: string) => void; 7 | onPressEnter?: (value: string) => void; 8 | style?: React.CSSProperties; 9 | } 10 | 11 | export default class HeaderSearch extends React.Component< 12 | IHeaderSearchProps, 13 | any 14 | > {} 15 | -------------------------------------------------------------------------------- /src/components/Authorized/Authorized.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CheckPermissions from './CheckPermissions'; 3 | 4 | class Authorized extends React.Component { 5 | render() { 6 | const { children, authority, noMatch = null } = this.props; 7 | const childrenRender = typeof children === 'undefined' ? null : children; 8 | return CheckPermissions(authority, childrenRender, noMatch); 9 | } 10 | } 11 | 12 | export default Authorized; 13 | -------------------------------------------------------------------------------- /src/components/NumberInfo/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 演示 4 | --- 5 | 6 | 各种数据文案的展现方式。 7 | 8 | ````jsx 9 | import NumberInfo from 'ant-design-pro/lib/NumberInfo'; 10 | import numeral from 'numeral'; 11 | 12 | ReactDOM.render( 13 |
14 | 本周访问} 16 | total={numeral(12321).format('0,0')} 17 | status="up" 18 | subTotal={17.1} 19 | /> 20 |
21 | , mountNode); 22 | ```` 23 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface INumberInfoProps { 3 | title?: React.ReactNode | string; 4 | subTitle?: React.ReactNode | string; 5 | total?: React.ReactNode | string; 6 | status?: 'up' | 'down'; 7 | theme?: string; 8 | gap?: number; 9 | subTotal?: number; 10 | style?: React.CSSProperties; 11 | } 12 | 13 | export default class NumberInfo extends React.Component< 14 | INumberInfoProps, 15 | any 16 | > {} 17 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: HeaderSearch 4 | zh-CN: HeaderSearch 5 | subtitle: 顶部搜索框 6 | cols: 1 7 | order: 8 8 | --- 9 | 10 | 通常作为全局搜索的入口,放置在导航工具条右侧。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | placeholder | 占位文字 | string | - 17 | dataSource | 当前提示内容列表 | string[] | - 18 | onSearch | 选择某项或按下回车时的回调 | function(value) | - 19 | onChange | 输入搜索字符的回调 | function(value) | - 20 | onPressEnter | 按下回车时的回调 | function(value) | - 21 | -------------------------------------------------------------------------------- /src/components/Login/LoginSubmit.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button, Form } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | const FormItem = Form.Item; 7 | 8 | export default ({ className, ...rest }) => { 9 | const clsString = classNames(styles.submit, className); 10 | return ( 11 | 12 |
} 15 | description={
结果描述
} 16 | extra="其他补充信息,自带灰底效果" 17 | actions={
操作建议,一般放置按钮组
} 18 | /> 19 | , mountNode); 20 | ```` 21 | -------------------------------------------------------------------------------- /src/components/Result/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Result 4 | zh-CN: Result 5 | subtitle: 处理结果 6 | cols: 1 7 | order: 12 8 | --- 9 | 10 | 结果页用于对用户进行的一系列任务处理结果进行反馈。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | type | 类型,不同类型自带对应的图标 | Enum {'success', 'error'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | description | 结果描述 | ReactNode | - | 19 | | extra | 补充信息,有默认的灰色背景 | ReactNode | - | 20 | | actions | 操作建议,推荐放置跳转链接,按钮组等 | ReactNode | - | 21 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedArray.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: 4 | zh-CN: 使用数组作为参数 5 | en-US: Use Array as a parameter 6 | --- 7 | 8 | Use Array as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | ReactDOM.render( 18 | 19 | 20 | , 21 | mountNode, 22 | ); 23 | ``` 24 | -------------------------------------------------------------------------------- /src/components/Charts/WaterWave/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .waterWave { 4 | display: inline-block; 5 | position: relative; 6 | transform-origin: left; 7 | .text { 8 | position: absolute; 9 | left: 0; 10 | top: 32px; 11 | text-align: center; 12 | width: 100%; 13 | span { 14 | color: @text-color-secondary; 15 | font-size: 14px; 16 | line-height: 22px; 17 | } 18 | h4 { 19 | color: @heading-color; 20 | line-height: 32px; 21 | font-size: 24px; 22 | } 23 | } 24 | .waterWaveCanvasWrapper { 25 | transform: scale(.5); 26 | transform-origin: 0 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Compare/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatPercent } from '../../utils/utils'; 3 | 4 | const Compare = ({ 5 | type = null, value = '', style 6 | }) => { 7 | return ( 8 | 9 | {type} 10 | {value > 0 ? ( 11 | 12 | ) : ( 13 | value < 0 ? ( 14 | 15 | ) : null 16 | )} 17 | 18 | {typeof value === 'number' ? formatPercent(value) : '--'} 19 | 20 | 21 | ) 22 | } 23 | 24 | export default Compare; -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 新华医院-运营数据中心 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/Revenue.less: -------------------------------------------------------------------------------- 1 | .wrapCard { 2 | width: 20%; 3 | padding: 0 12px; 4 | display: inline-block; 5 | margin-bottom: 20px; 6 | .countPart { 7 | font-size: 20px; 8 | color: #333; 9 | margin-bottom: 5px; 10 | color: #15984B; 11 | font-weight: 500; 12 | } 13 | .itemPart { 14 | height: 40px; 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | align-items: center; 19 | margin-bottom: 10px; 20 | } 21 | .compare { 22 | width: 100%; 23 | height: 40px; 24 | line-height: 40px; 25 | text-align: center; 26 | border-top: 1px #e8e8e8 solid; 27 | } 28 | } -------------------------------------------------------------------------------- /.webpackrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | export default { 4 | entry: 'src/index.js', 5 | extraBabelPlugins: [ 6 | 'transform-decorators-legacy', 7 | ['import', { libraryName: 'antd', libraryDirectory: 'es', style: true }], 8 | ], 9 | env: { 10 | development: { 11 | extraBabelPlugins: ['dva-hmr'], 12 | NODE_ENV: '"development"' 13 | }, 14 | production: { 15 | NODE_ENV: '"production"' 16 | } 17 | }, 18 | alias: { 19 | 'components': path.resolve(__dirname, 'src/components/'), 20 | }, 21 | ignoreMomentLocale: true, 22 | theme: './src/theme.js', 23 | html: { 24 | template: './src/index.ejs', 25 | }, 26 | publicPath: '/', 27 | disableDynamicImport: true, 28 | hash: true, 29 | }; 30 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | nodejs_version: "8" 4 | 5 | # this is how to allow failing jobs in the matrix 6 | matrix: 7 | fast_finish: true # set this flag to immediately finish build once one of the jobs fails. 8 | 9 | # Install scripts. (runs after repo cloning) 10 | install: 11 | # Get the latest stable version of Node.js or io.js 12 | - ps: Install-Product node $env:nodejs_version 13 | # install modules 14 | - npm install 15 | # Output useful info for debugging. 16 | - node --version 17 | - npm --version 18 | 19 | # Post-install test scripts. 20 | test_script: 21 | - npm run lint 22 | - npm run test:all 23 | - npm run build 24 | 25 | # Don't actually build. 26 | build: off 27 | -------------------------------------------------------------------------------- /src/components/Authorized/demo/AuthorizedFunction.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: 4 | zh-CN: 使用方法作为参数 5 | en-US: Use function as a parameter 6 | --- 7 | 8 | Use Function as a parameter 9 | 10 | ```jsx 11 | import RenderAuthorized from 'ant-design-pro/lib/Authorized'; 12 | import { Alert } from 'antd'; 13 | 14 | const Authorized = RenderAuthorized('user'); 15 | const noMatch = ; 16 | 17 | const havePermission = () => { 18 | return false; 19 | }; 20 | 21 | ReactDOM.render( 22 | 23 | 28 | , 29 | mountNode, 30 | ); 31 | ``` 32 | -------------------------------------------------------------------------------- /src/components/Charts/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as numeral from 'numeral'; 2 | export { default as ChartCard } from './ChartCard'; 3 | export { default as Bar } from './Bar'; 4 | export { default as Pie } from './Pie'; 5 | export { default as Radar } from './Radar'; 6 | export { default as Gauge } from './Gauge'; 7 | export { default as MiniArea } from './MiniArea'; 8 | export { default as MiniBar } from './MiniBar'; 9 | export { default as MiniProgress } from './MiniProgress'; 10 | export { default as Field } from './Field'; 11 | export { default as WaterWave } from './WaterWave'; 12 | export { default as TagCloud } from './TagCloud'; 13 | export { default as TimelineChart } from './TimelineChart'; 14 | 15 | declare const yuan: (value: number | string) => string; 16 | 17 | export { yuan }; 18 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .headerSearch { 4 | :global(.anticon-search) { 5 | cursor: pointer; 6 | font-size: 16px; 7 | } 8 | .input { 9 | transition: width .3s, margin-left .3s; 10 | width: 0; 11 | background: transparent; 12 | border-radius: 0; 13 | :global(.ant-select-selection) { 14 | background: transparent; 15 | } 16 | input { 17 | border: 0; 18 | padding-left: 0; 19 | padding-right: 0; 20 | box-shadow: none !important; 21 | } 22 | &, 23 | &:hover, 24 | &:focus { 25 | border-bottom: 1px solid @border-color-base; 26 | } 27 | &.show { 28 | width: 210px; 29 | margin-left: 8px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | export interface IPageHeaderProps { 3 | title?: React.ReactNode | string; 4 | logo?: React.ReactNode | string; 5 | action?: React.ReactNode | string; 6 | content?: React.ReactNode; 7 | extraContent?: React.ReactNode; 8 | routes?: any[]; 9 | params?: any; 10 | breadcrumbList?: Array<{ title: React.ReactNode; href?: string }>; 11 | tabList?: Array<{ key: string; tab: React.ReactNode }>; 12 | tabActiveKey?: string; 13 | tabDefaultActiveKey?: string; 14 | onTabChange?: (key: string) => void; 15 | tabBarExtraContent?: React.ReactNode; 16 | linkElement?: React.ReactNode; 17 | style?: React.CSSProperties; 18 | } 19 | 20 | export default class PageHeader extends React.Component {} 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | import 'url-polyfill'; 3 | import dva from 'dva'; 4 | 5 | import createHistory from 'history/createHashHistory'; 6 | // user BrowserHistory 7 | // import createHistory from 'history/createBrowserHistory'; 8 | import createLoading from 'dva-loading'; 9 | import 'moment/locale/zh-cn'; 10 | import './rollbar'; 11 | 12 | import './index.less'; 13 | 14 | // 1. Initialize 15 | const app = dva({ 16 | history: createHistory(), 17 | }); 18 | 19 | // 2. Plugins 20 | app.use(createLoading()); 21 | 22 | // 3. Register global model 23 | app.model(require('./models/global').default); 24 | 25 | // 4. Router 26 | app.router(require('./router').default); 27 | 28 | // 5. Start 29 | app.start('#root'); 30 | 31 | export default app._store; // eslint-disable-line 32 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Bedspace.less: -------------------------------------------------------------------------------- 1 | .leftCard { 2 | height: 75%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | margin-right: 40px; 7 | } 8 | .rowCardContent { 9 | position: relative; 10 | display: flex; 11 | width: 100%; 12 | margin: 20px 0 40px 0; 13 | .part { 14 | flex: 1; 15 | text-align: center; 16 | .partOne { 17 | color: #333; 18 | padding: 10px 0; 19 | } 20 | .partTwo { 21 | font-size: 20px; 22 | padding-bottom: 10px; 23 | } 24 | .partThree { 25 | display: inline-block; 26 | } 27 | } 28 | .bedVertical { 29 | position: absolute; 30 | top: 50%; 31 | transform: translateY(-50%); 32 | width: 1px; 33 | height: 70px; 34 | background: #e8e8e8; 35 | } 36 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | 6 | env: 7 | matrix: 8 | - TEST_TYPE=lint 9 | - TEST_TYPE=build 10 | - TEST_TYPE=test-all 11 | - TEST_TYPE=test-dist 12 | 13 | addons: 14 | apt: 15 | packages: 16 | - xvfb 17 | 18 | install: 19 | - export DISPLAY=':99.0' 20 | - Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & 21 | - npm install 22 | 23 | script: 24 | - | 25 | if [ "$TEST_TYPE" = lint ]; then 26 | npm run lint 27 | elif [ "$TEST_TYPE" = build ]; then 28 | npm run build 29 | elif [ "$TEST_TYPE" = test-all ]; then 30 | npm run test:all 31 | elif [ "$TEST_TYPE" = test-dist ]; then 32 | npm run site 33 | mv dist/* ./ 34 | php -S localhost:8000 & 35 | DEBUG=* npm test .e2e.js 36 | fi 37 | -------------------------------------------------------------------------------- /src/components/Exception/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Exception 4 | zh-CN: Exception 5 | subtitle: 异常 6 | cols: 1 7 | order: 5 8 | --- 9 | 10 | 异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |-------------|------------------------------------------|-------------|-------| 16 | | type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - | 17 | | title | 标题 | ReactNode | - | 18 | | desc | 补充描述 | ReactNode | - | 19 | | img | 背景图片地址 | string | - | 20 | | actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - | 21 | | linkElement | 定义链接的元素,默认为 `a` | string\|ReactElement | - | 22 | -------------------------------------------------------------------------------- /src/models/consumable.js: -------------------------------------------------------------------------------- 1 | import { fetchConsumableData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'consumable', 5 | 6 | state: { 7 | diffDepartSupplyIncomeModule: [], //不同科室运行病历按时完成率 8 | loading: false 9 | }, 10 | 11 | effects: { 12 | *fetch({ payload }, { call, put }) { 13 | const response = yield call(fetchConsumableData, payload); 14 | yield put({ 15 | type: 'save', 16 | payload: response.result, 17 | }); 18 | }, 19 | }, 20 | 21 | reducers: { 22 | save(state, { payload }) { 23 | return payload ? { 24 | ...state, 25 | ...payload, 26 | } : {}; 27 | }, 28 | clear() { 29 | return { 30 | diffDepartSupplyIncomeModule: [] //不同科室运行病历按时完成率 31 | }; 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 运营数据中心 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/Login/LoginTab.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Tabs } from 'antd'; 4 | 5 | const { TabPane } = Tabs; 6 | 7 | const generateId = (() => { 8 | let i = 0; 9 | return (prefix = '') => { 10 | i += 1; 11 | return `${prefix}${i}`; 12 | }; 13 | })(); 14 | 15 | export default class LoginTab extends Component { 16 | static __ANT_PRO_LOGIN_TAB = true; 17 | static contextTypes = { 18 | tabUtil: PropTypes.object, 19 | }; 20 | constructor(props) { 21 | super(props); 22 | this.uniqueId = generateId('login-tab-'); 23 | } 24 | componentWillMount() { 25 | if (this.context.tabUtil) { 26 | this.context.tabUtil.addTab(this.uniqueId); 27 | } 28 | } 29 | render() { 30 | return ; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/models/surgery.js: -------------------------------------------------------------------------------- 1 | import { fetchSurgeryData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'surgery', 5 | 6 | state: { 7 | diffCategorySurgeryModule: [], 8 | diffLevelSurgeryModule: [], 9 | loading: false 10 | }, 11 | 12 | effects: { 13 | *fetch({ payload }, { call, put }) { 14 | const response = yield call(fetchSurgeryData, payload); 15 | yield put({ 16 | type: 'save', 17 | payload: response.result, 18 | }); 19 | } 20 | }, 21 | 22 | reducers: { 23 | save(state, { payload }) { 24 | return payload ? { 25 | ...state, 26 | ...payload, 27 | } : {}; 28 | }, 29 | clear() { 30 | return { 31 | diffCategorySurgeryModule: [], 32 | diffLevelSurgeryModule: [] 33 | }; 34 | }, 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 全局搜索 4 | --- 5 | 6 | 通常放置在导航工具条右侧。(点击搜索图标预览效果) 7 | 8 | ````jsx 9 | import HeaderSearch from 'ant-design-pro/lib/HeaderSearch'; 10 | 11 | ReactDOM.render( 12 |
22 | { 26 | console.log('input', value); // eslint-disable-line 27 | }} 28 | onPressEnter={(value) => { 29 | console.log('enter', value); // eslint-disable-line 30 | }} 31 | /> 32 |
33 | , mountNode); 34 | ```` 35 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import styles from './index.less'; 4 | 5 | export default ({ className, links, copyright }) => { 6 | const clsString = classNames(styles.globalFooter, className); 7 | return ( 8 |
9 | { 10 | links && ( 11 |
12 | {links.map(link => ( 13 | 18 | {link.title} 19 | 20 | ))} 21 |
22 | ) 23 | } 24 | {copyright &&
{copyright}
} 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/models/quality.js: -------------------------------------------------------------------------------- 1 | import { routerRedux } from 'dva/router'; 2 | import { fetchQualityData } from '../services/api'; 3 | 4 | export default { 5 | namespace: 'quality', 6 | 7 | state: { 8 | dailyStatisticInfoModule: [], 9 | diffDepartStatisticInfoModule: [], 10 | loading: false 11 | }, 12 | 13 | effects: { 14 | *fetch({ payload }, { call, put }) { 15 | const response = yield call(fetchQualityData, payload); 16 | yield put({ 17 | type: 'save', 18 | payload: response.result, 19 | }); 20 | }, 21 | }, 22 | 23 | reducers: { 24 | save(state, { payload }) { 25 | return { 26 | ...state, 27 | ...payload 28 | }; 29 | }, 30 | clear() { 31 | return { 32 | dailyStatisticInfoModule: [], 33 | diffDepartStatisticInfoModule: [], 34 | } 35 | } 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/components/GlobalFooter/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 演示 4 | iframe: 400 5 | --- 6 | 7 | 基本页脚。 8 | 9 | ````jsx 10 | import GlobalFooter from 'ant-design-pro/lib/GlobalFooter'; 11 | import { Icon } from 'antd'; 12 | 13 | const links = [{ 14 | key: '帮助', 15 | title: '帮助', 16 | href: '', 17 | }, { 18 | key: 'github', 19 | title: , 20 | href: 'https://github.com/ant-design/ant-design-pro', 21 | blankTarget: true, 22 | }, { 23 | key: '条款', 24 | title: '条款', 25 | href: '', 26 | blankTarget: true, 27 | }]; 28 | 29 | const copyright =
Copyright 2017 蚂蚁金服体验技术部出品
; 30 | 31 | ReactDOM.render( 32 |
33 |
34 | 35 |
36 | , mountNode); 37 | ```` 38 | -------------------------------------------------------------------------------- /src/components/Login/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .login { 4 | 5 | .tabs { 6 | padding: 0 2px; 7 | margin: 0 -2px; 8 | :global { 9 | .ant-tabs-tab { 10 | font-size: 16px; 11 | line-height: 24px; 12 | } 13 | .ant-input-affix-wrapper .ant-input:not(:first-child) { 14 | padding-left: 34px; 15 | } 16 | } 17 | } 18 | 19 | :global { 20 | .ant-tabs .ant-tabs-bar { 21 | border-bottom: 0; 22 | margin-bottom: 24px; 23 | text-align: center; 24 | } 25 | 26 | .ant-form-item { 27 | margin-bottom: 24px; 28 | } 29 | } 30 | 31 | .prefixIcon { 32 | font-size: @font-size-base; 33 | color: @disabled-color; 34 | } 35 | 36 | .getCaptcha { 37 | display: block; 38 | width: 100%; 39 | } 40 | 41 | .submit { 42 | width: 100%; 43 | margin-top: 24px; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Authorized/AuthorizedRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import Authorized from './Authorized'; 4 | 5 | class AuthorizedRoute extends React.Component { 6 | render() { 7 | const { 8 | component: Component, 9 | render, 10 | authority, 11 | redirectPath, 12 | ...rest 13 | } = this.props; 14 | return ( 15 | } 21 | /> 22 | } 23 | > 24 | 27 | (Component ? : render(props)) 28 | } 29 | /> 30 | 31 | ); 32 | } 33 | } 34 | 35 | export default AuthorizedRoute; 36 | -------------------------------------------------------------------------------- /src/components/Result/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classNames from 'classnames'; 3 | import { Icon } from 'antd'; 4 | import styles from './index.less'; 5 | 6 | export default function Result({ 7 | className, type, title, description, extra, actions, ...restProps 8 | }) { 9 | const iconMap = { 10 | error: , 11 | success: , 12 | }; 13 | const clsString = classNames(styles.result, className); 14 | return ( 15 |
16 |
{iconMap[type]}
17 |
{title}
18 | {description &&
{description}
} 19 | {extra &&
{extra}
} 20 | {actions &&
{actions}
} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/common/menu.js: -------------------------------------------------------------------------------- 1 | import { isUrl } from '../utils/utils'; 2 | 3 | const menuData = [ 4 | { 5 | name: '院长驾驶舱', 6 | icon: 'dashboard', 7 | path: 'dashboard', 8 | children: [ 9 | { 10 | name: '今日动态', 11 | path: 'report', 12 | }, 13 | { 14 | name: '重点指标', 15 | path: 'indicator', 16 | }, 17 | ], 18 | }, 19 | ]; 20 | 21 | function formatter(data, parentPath = '/', parentAuthority) { 22 | return data.map((item) => { 23 | let { path } = item; 24 | if (!isUrl(path)) { 25 | path = parentPath + item.path; 26 | } 27 | const result = { 28 | ...item, 29 | path, 30 | authority: item.authority || parentAuthority, 31 | }; 32 | if (item.children) { 33 | result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority); 34 | } 35 | return result; 36 | }); 37 | } 38 | 39 | export const getMenuData = () => formatter(menuData); 40 | -------------------------------------------------------------------------------- /src/components/Authorized/index.js: -------------------------------------------------------------------------------- 1 | import Authorized from './Authorized'; 2 | import AuthorizedRoute from './AuthorizedRoute'; 3 | import Secured from './Secured'; 4 | import check from './CheckPermissions.js'; 5 | 6 | /* eslint-disable import/no-mutable-exports */ 7 | let CURRENT = 'NULL'; 8 | 9 | Authorized.Secured = Secured; 10 | Authorized.AuthorizedRoute = AuthorizedRoute; 11 | Authorized.check = check; 12 | 13 | /** 14 | * use authority or getAuthority 15 | * @param {string|()=>String} currentAuthority 16 | */ 17 | const renderAuthorize = (currentAuthority) => { 18 | if (currentAuthority) { 19 | if (currentAuthority.constructor.name === 'Function') { 20 | CURRENT = currentAuthority(); 21 | } 22 | if (currentAuthority.constructor.name === 'String') { 23 | CURRENT = currentAuthority; 24 | } 25 | } else { 26 | CURRENT = 'NULL'; 27 | } 28 | return Authorized; 29 | }; 30 | 31 | export { CURRENT }; 32 | export default renderAuthorize; 33 | -------------------------------------------------------------------------------- /src/models/nonDrug.js: -------------------------------------------------------------------------------- 1 | import { fetchNonDrugData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'nonDrug', 5 | 6 | state: { 7 | diffDepartNonDrugModule: [], //不同科室非药物中治疗率 8 | topTenTherapyModule: [], //非药物中治最多的前十疗法、次数和环比数据 9 | loading: false, 10 | }, 11 | 12 | effects: { 13 | *fetch({ payload }, { call, put }) { 14 | //call function 15 | const response = yield call(fetchNonDrugData, payload); 16 | //dispatch reducers 17 | yield put({ 18 | type: 'save', 19 | payload: response.result, 20 | }); 21 | }, 22 | }, 23 | //负责修改model的数据 24 | reducers: { 25 | save(state, { payload }) { 26 | return payload ? { 27 | ...state, 28 | ...payload, 29 | } : {}; 30 | }, 31 | clear() { 32 | return { 33 | diffDepartNonDrugModule: [], //不同科室非药物中治疗率 34 | topTenTherapyModule: [], //非药物中治最多的前十疗法、次数和环比数据 35 | }; 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/models/inpatient.js: -------------------------------------------------------------------------------- 1 | import { fetchInpatientData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'inpatient', 5 | 6 | state: { 7 | diffDepartStatisticInfoModule: [], //不同科室在院人数 8 | dailyStatisticInfoModule: [], //本期每天入院、出院人数 9 | loading: false, 10 | }, 11 | 12 | effects: { 13 | *fetch({ payload }, { call, put }) { 14 | //call function 15 | const response = yield call(fetchInpatientData, payload); 16 | //dispatch reducers 17 | yield put({ 18 | type: 'save', 19 | payload: response.result, 20 | }); 21 | }, 22 | }, 23 | //负责修改model的数据 24 | reducers: { 25 | save(state, { payload }) { 26 | return payload ? { 27 | ...state, 28 | ...payload, 29 | } : {}; 30 | }, 31 | clear() { 32 | return { 33 | diffDepartStatisticInfoModule: [], //不同科室在院人数 34 | dailyStatisticInfoModule: [], //本期每天入院、出院人数 35 | }; 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/Charts/Bar/shape/shape.js: -------------------------------------------------------------------------------- 1 | import { 2 | Shape, 3 | } from 'bizcharts'; 4 | import borderRadius from './borderRadius'; 5 | import baseLine from './baseLine'; 6 | 7 | const types = { 8 | borderRadius, 9 | baseLine, 10 | }; 11 | const shape = (transpose, shapeTypes) => { 12 | shape[Symbol.for('name')] = transpose ? `transpose${shapeTypes.join('')}` : shapeTypes.join(''); 13 | 14 | Shape.registerShape('interval', shape[Symbol.for('name')], { 15 | draw(cfg, container) { 16 | for (let i = 0; i < shapeTypes.length; i++) { 17 | const setting = types[shapeTypes[i]](cfg, container, this, transpose); 18 | if (i === shapeTypes.length - 1) { 19 | return container.addShape('rect', { 20 | attrs: setting, 21 | }); 22 | } else { 23 | container.addShape('rect', { 24 | attrs: setting, 25 | }); 26 | } 27 | } 28 | }, 29 | }); 30 | 31 | }; 32 | 33 | export default shape; 34 | -------------------------------------------------------------------------------- /src/models/appointment.js: -------------------------------------------------------------------------------- 1 | import { fetchAppointmentData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'appointment', 5 | 6 | state: { 7 | diffWayReservationRegistrationModule: [], //不同途径预约人数 8 | diffDepartReservationVisitsRateModule: [], //不同科室预约数量和预约就诊率 9 | reservationVisitsInfoModule: [] 10 | }, 11 | 12 | effects: { 13 | *fetch({ payload }, { call, put }) { 14 | const response = yield call(fetchAppointmentData, payload); 15 | yield put({ 16 | type: 'save', 17 | payload: response.result, 18 | }); 19 | }, 20 | }, 21 | 22 | reducers: { 23 | save(state, { payload }) { 24 | return payload ? { 25 | ...state, 26 | ...payload 27 | } : {}; 28 | }, 29 | clear() { 30 | return { 31 | diffWayReservationRegistrationModule: [], 32 | diffDepartReservationVisitsRateModule: [], 33 | reservationVisitsInfoModule: [] 34 | }; 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Route, Redirect, Switch } from 'dva/router'; 3 | import Empty from '../../../components/Empty'; 4 | import { getRoutes } from '../../../utils/utils'; 5 | 6 | export default class Report extends PureComponent { 7 | 8 | render() { 9 | const { match, routerData } = this.props; 10 | const path = { 11 | pathname: '/dashboard/report/dynamic', 12 | } 13 | return ( 14 | 15 | { 16 | getRoutes(match.path, routerData).map(item => { 17 | return ( 18 | 24 | ) 25 | }) 26 | } 27 | 28 | 29 | 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/SiderMenu/SilderMenu.test.js: -------------------------------------------------------------------------------- 1 | import { getMeunMatcheys } from './SiderMenu'; 2 | 3 | const meun = [ 4 | '/dashboard', 5 | '/userinfo', 6 | '/dashboard/name', 7 | '/userinfo/:id', 8 | '/userinfo/:id/info', 9 | ]; 10 | 11 | describe('test meun match', () => { 12 | it('simple path', () => { 13 | expect(getMeunMatcheys(meun, '/dashboard')).toEqual(['/dashboard']); 14 | }); 15 | it('error path', () => { 16 | expect(getMeunMatcheys(meun, '/dashboardname')).toEqual([]); 17 | }); 18 | 19 | it('Secondary path', () => { 20 | expect(getMeunMatcheys(meun, '/dashboard/name')).toEqual([ 21 | '/dashboard/name', 22 | ]); 23 | }); 24 | 25 | it('Parameter path', () => { 26 | expect(getMeunMatcheys(meun, '/userinfo/2144')).toEqual([ 27 | '/userinfo/:id', 28 | ]); 29 | }); 30 | 31 | it('three parameter path', () => { 32 | expect(getMeunMatcheys(meun, '/userinfo/2144/info')).toEqual([ 33 | '/userinfo/:id/info', 34 | ]); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/models/prescription.js: -------------------------------------------------------------------------------- 1 | import { fetchPrescriptionData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'prescription', 5 | 6 | state: { 7 | departmentPrescriptionInfo: [], //不同科室所开处方数量 8 | prescriptionSingleStatisticMonthInfo: [], //处方单向统计 9 | loading: false, 10 | }, 11 | 12 | effects: { 13 | *fetch({ payload }, { call, put }) { 14 | //call function 15 | const response = yield call(fetchPrescriptionData, payload); 16 | //dispatch reducers 17 | yield put({ 18 | type: 'save', 19 | payload: response.result, 20 | }); 21 | }, 22 | }, 23 | //负责修改model的数据 24 | reducers: { 25 | save(state, { payload }) { 26 | return payload ? { 27 | ...state, 28 | ...payload, 29 | } : {}; 30 | }, 31 | clear() { 32 | return { 33 | departmentPrescriptionInfo: [], //不同科室所开处方数量 34 | prescriptionSingleStatisticMonthInfo: [], //处方单向统计 35 | }; 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import NoticeIconTab, { INoticeIconData } from './NoticeIconTab'; 3 | 4 | export interface INoticeIconProps { 5 | count?: number; 6 | className?: string; 7 | loading?: boolean; 8 | onClear?: (tableTile: string) => void; 9 | onItemClick?: (item: INoticeIconData, tabProps: INoticeIconProps) => void; 10 | onTabChange?: (tableTile: string) => void; 11 | popupAlign?: { 12 | points?: [string, string]; 13 | offset?: [number, number]; 14 | targetOffset?: [number, number]; 15 | overflow?: any; 16 | useCssRight?: boolean; 17 | useCssBottom?: boolean; 18 | useCssTransform?: boolean; 19 | }; 20 | style?: React.CSSProperties; 21 | onPopupVisibleChange?: (visible: boolean) => void; 22 | popupVisible?: boolean; 23 | locale?: { emptyText: string; clear: string }; 24 | } 25 | 26 | export default class NoticeIcon extends React.Component { 27 | public static Tab: typeof NoticeIconTab; 28 | } 29 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Route, Redirect, Switch } from 'dva/router'; 3 | import Empty from '../../../components/Empty'; 4 | import { getRoutes } from '../../../utils/utils'; 5 | 6 | export default class Indicator extends PureComponent { 7 | 8 | render() { 9 | const { match, routerData } = this.props; 10 | const path = { 11 | pathname: '/dashboard/indicator/emphasis', 12 | } 13 | return ( 14 | 15 | { 16 | getRoutes(match.path, routerData).map(item => { 17 | return ( 18 | 24 | ) 25 | }) 26 | } 27 | 28 | 29 | 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Login/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import Button from "antd/lib/button"; 3 | export interface LoginProps { 4 | defaultActiveKey?: string; 5 | onTabChange?: (key: string) => void; 6 | style?: React.CSSProperties; 7 | onSubmit?: (error: any, values: any) => void; 8 | } 9 | 10 | export interface TabProps { 11 | key?: string; 12 | tab?: React.ReactNode; 13 | } 14 | export class Tab extends React.Component {} 15 | 16 | export interface LoginItemProps { 17 | name?: string; 18 | rules?: any[]; 19 | style?: React.CSSProperties; 20 | onGetCaptcha?: () => void; 21 | } 22 | 23 | export class LoginItem extends React.Component {} 24 | 25 | export default class Login extends React.Component { 26 | static Tab: typeof Tab; 27 | static UserName: typeof LoginItem; 28 | static Password: typeof LoginItem; 29 | static Mobile: typeof LoginItem; 30 | static Captcha: typeof LoginItem; 31 | static Submit: typeof Button; 32 | } 33 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "selector-pseudo-class-no-unknown": null, 5 | "shorthand-property-no-redundant-values": null, 6 | "at-rule-empty-line-before": null, 7 | "at-rule-name-space-after": null, 8 | "comment-empty-line-before": null, 9 | "declaration-bang-space-before": null, 10 | "declaration-empty-line-before": null, 11 | "function-comma-newline-after": null, 12 | "function-name-case": null, 13 | "function-parentheses-newline-inside": null, 14 | "function-max-empty-lines": null, 15 | "function-whitespace-after": null, 16 | "number-leading-zero": null, 17 | "number-no-trailing-zeros": null, 18 | "rule-empty-line-before": null, 19 | "selector-combinator-space-after": null, 20 | "selector-list-comma-newline-after": null, 21 | "selector-pseudo-element-colon-notation": null, 22 | "unit-no-unknown": null, 23 | "no-descending-specificity": null, 24 | "value-list-max-empty-lines": null 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/CountCard/DailyCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card } from 'antd'; 3 | 4 | import styles from './index.less'; 5 | 6 | const DailyCard = ({ 7 | iconName, color, title, content, unit = '', loading, onCardClick 8 | }) => { 9 | return ( 10 | 16 |
17 | 18 |
19 |
{title}
20 |
21 | { content == undefined ? '--' : content } 22 | {unit ? ( 23 | {unit} 24 | ) : null} 25 |
26 |
27 |
28 |
29 | ) 30 | } 31 | 32 | export default DailyCard; -------------------------------------------------------------------------------- /src/components/Result/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .result { 4 | text-align: center; 5 | width: 72%; 6 | margin: 0 auto; 7 | 8 | .icon { 9 | font-size: 72px; 10 | line-height: 72px; 11 | margin-bottom: 24px; 12 | 13 | & > .success { 14 | color: @success-color; 15 | } 16 | 17 | & > .error { 18 | color: @error-color; 19 | } 20 | } 21 | 22 | .title { 23 | font-size: 24px; 24 | color: @heading-color; 25 | font-weight: 500; 26 | line-height: 32px; 27 | margin-bottom: 16px; 28 | } 29 | 30 | .description { 31 | font-size: 14px; 32 | line-height: 22px; 33 | color: @text-color-secondary; 34 | margin-bottom: 24px; 35 | } 36 | 37 | .extra { 38 | background: #fafafa; 39 | padding: 24px 40px; 40 | border-radius: @border-radius-sm; 41 | text-align: left; 42 | } 43 | 44 | .actions { 45 | margin-top: 32px; 46 | 47 | button:not(:last-child) { 48 | margin-right: 8px; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/models/emphasis.js: -------------------------------------------------------------------------------- 1 | import { fetchEmphasisData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'emphasis', 5 | 6 | state: { 7 | supplyModule: [], 8 | medicalQuantityModule: {}, 9 | bedModule: {}, 10 | medicalTechnologyModule: [], 11 | reservationModule: {}, 12 | surgeryModule: {} 13 | }, 14 | 15 | effects: { 16 | *fetch({ payload }, { call, put }) { 17 | const response = yield call(fetchEmphasisData, payload); 18 | yield put({ 19 | type: 'save', 20 | payload: response.result, 21 | }); 22 | }, 23 | }, 24 | 25 | reducers: { 26 | save(state, { payload }) { 27 | return payload ? { 28 | ...state, 29 | ...payload, 30 | } : {}; 31 | }, 32 | clear() { 33 | return { 34 | supplyModule: [], 35 | medicalQuantityModule: {}, 36 | bedModule: {}, 37 | medicalTechnologyModule: [], 38 | reservationModule: {}, 39 | surgeryModule: {} 40 | } 41 | } 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 简体中文 2 | 3 | # Operation Data Center 4 | 5 | 一套为医院提供数据可视化方案。提供了柱状图、饼图、折线图、面积图等多种可视化形式。支持多页面切换 6 | 7 | ## 预览 8 | ![preview](operation-data-center.png) 9 | 10 | ## 内容 11 | 12 | ``` 13 | - 院长驾驶舱 14 | - 今日动态 15 | - 总门急诊人次 16 | - 中医处方数量 17 | - 非药物中治率 18 | - 出院人数 19 | - 收入 20 | - 重点指标 21 | - 耗材 22 | - 医疗质量 23 | - 手术 24 | - 预约 25 | - 床位 26 | - 工作台 27 | - 异常 28 | - 403 无权限 29 | - 404 找不到 30 | - 500 服务器出错 31 | ``` 32 | 33 | ## Demo 开发 34 | 35 | ```bash 36 | $ git clone https://github.com/pandly/operation-data-center.git 37 | $ cd operation-data-center 38 | $ npm install 39 | $ npm run start # 访问 http://localhost:8000,启动时代理开发环境中的真实数据,如需更改代理接口地址,可以在.roadhogrc.mock.js中修改 40 | $ npm run mock # 访问 http://localhost:8000,启动时访问mock数据 41 | $ npm run build # 文件打包 42 | ``` 43 | [demo地址](https://pandly.github.io/operation-data-center/dist/) 44 | 45 | ## 使用 46 | 47 | ```bash 48 | - 通过选择时间范围,显示不同时间段的统计结果图。ps: 大于30天的数据和小于30天的数据会以不同的形式呈现。 49 | - 一级页面点击相应的模块可以进入二级页面查看相应的详细信息。 50 | ``` 51 | -------------------------------------------------------------------------------- /src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | word-break: break-all; 5 | white-space: nowrap; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | overflow: hidden; 10 | position: relative; 11 | line-height: 1.5em; 12 | max-height: @line * 1.5em; 13 | text-align: justify; 14 | margin-right: -1em; 15 | padding-right: 1em; 16 | &:before { 17 | background: @bg; 18 | content: '...'; 19 | padding: 0 1px; 20 | position: absolute; 21 | right: 14px; 22 | bottom: 0; 23 | } 24 | &:after { 25 | background: white; 26 | content: ''; 27 | margin-top: 0.2em; 28 | position: absolute; 29 | right: 14px; 30 | width: 1em; 31 | height: 1em; 32 | } 33 | } 34 | 35 | // mixins for clearfix 36 | // ------------------------ 37 | .clearfix() { 38 | zoom: 1; 39 | &:before, 40 | &:after { 41 | content: " "; 42 | display: table; 43 | } 44 | &:after { 45 | clear: both; 46 | visibility: hidden; 47 | font-size: 0; 48 | height: 0; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Result/demo/error.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: Failed 4 | --- 5 | 6 | 提交失败。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | import { Button, Icon } from 'antd'; 11 | 12 | const extra = ( 13 |
14 |
15 | 您提交的内容有如下错误: 16 |
17 |
18 | 您的账户已被冻结 19 | 立即解冻 20 |
21 |
22 | 您的账户还不具备申请资格 23 | 立即升级 24 |
25 |
26 | ); 27 | 28 | const actions = ; 29 | 30 | ReactDOM.render( 31 | 38 | , mountNode); 39 | ```` 40 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Icon } from 'antd'; 3 | import classNames from 'classnames'; 4 | import styles from './index.less'; 5 | 6 | export default ({ 7 | theme, title, subTitle, total, subTotal, status, suffix, gap, ...rest 8 | }) => ( 9 |
17 | {title &&
{title}
} 18 | {subTitle &&
{subTitle}
} 19 |
20 | 21 | {total} 22 | {suffix && {suffix}} 23 | 24 | { 25 | (status || subTotal) && ( 26 | 27 | {subTotal} 28 | {status && } 29 | 30 | ) 31 | } 32 |
33 |
34 | ); 35 | -------------------------------------------------------------------------------- /src/models/error.js: -------------------------------------------------------------------------------- 1 | import { query403, query401, query404, query500 } from '../services/error'; 2 | 3 | export default { 4 | namespace: 'error', 5 | 6 | state: { 7 | error: '', 8 | isloading: false, 9 | }, 10 | 11 | effects: { 12 | *query403(_, { call, put }) { 13 | yield call(query403); 14 | yield put({ 15 | type: 'trigger', 16 | payload: '403', 17 | }); 18 | }, 19 | *query401(_, { call, put }) { 20 | yield call(query401); 21 | yield put({ 22 | type: 'trigger', 23 | payload: '401', 24 | }); 25 | }, 26 | *query500(_, { call, put }) { 27 | yield call(query500); 28 | yield put({ 29 | type: 'trigger', 30 | payload: '500', 31 | }); 32 | }, 33 | *query404(_, { call, put }) { 34 | yield call(query404); 35 | yield put({ 36 | type: 'trigger', 37 | payload: '404', 38 | }); 39 | }, 40 | }, 41 | 42 | reducers: { 43 | trigger(state, action) { 44 | return { 45 | error: action.payload, 46 | }; 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /src/components/Charts/Bar/shape/borderRadius.js: -------------------------------------------------------------------------------- 1 | const borderRadius = (cfg, container, shape, transpose) => { 2 | const points = cfg.points; 3 | let path = []; 4 | path.push(['M', points[0].x, points[0].y]); 5 | path.push(['L', points[1].x, points[1].y]); 6 | path.push(['L', points[2].x, points[2].y]); 7 | path.push(['L', points[3].x, points[3].y]); 8 | path.push('Z'); 9 | path = shape.parsePath(path); // 将 0 - 1 转化为画布坐标 10 | const setting = transpose ? { 11 | x: path[0][1], 12 | y: path[2][2], 13 | width: path[2][1] - path[0][1], 14 | height: path[0][2] - path[2][2], 15 | fill: cfg.color, 16 | radius: path[2][1] - path[0][1] > path[0][2] - path[2][2] ? (path[0][2] - path[2][2]) / 2 : 0, 17 | opacity: cfg.opacity, 18 | } : { 19 | x: path[1][1], 20 | y: path[1][2], 21 | width: path[2][1] - path[1][1], 22 | height: path[0][2] - path[1][2], 23 | fill: cfg.color, 24 | radius: path[0][2] - path[1][2] > path[2][1] - path[1][1] ? (path[2][1] - path[1][1]) / 2 : 0, 25 | opacity: cfg.opacity, 26 | }; 27 | return setting; 28 | }; 29 | 30 | export default borderRadius; 31 | -------------------------------------------------------------------------------- /src/models/income.js: -------------------------------------------------------------------------------- 1 | import { fetchIncomeData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'income', 5 | 6 | state: { 7 | baseStatisticModule: [], //十项数据 8 | diffDepartIncomeModule: [], //不同科室的收入明细 9 | drugIncomeDetailsModule: {}, //药品收入明细 10 | nonDrugIncomeDetailsModule: [], //非药品收入明细 11 | loading: false, 12 | }, 13 | 14 | effects: { 15 | *fetch({ payload }, { call, put }) { 16 | //call function 17 | const response = yield call(fetchIncomeData, payload); 18 | //dispatch reducers 19 | yield put({ 20 | type: 'save', 21 | payload: response.result, 22 | }); 23 | }, 24 | }, 25 | //负责修改model的数据 26 | reducers: { 27 | save(state, { payload }) { 28 | return payload ? { 29 | ...state, 30 | ...payload, 31 | } : {}; 32 | }, 33 | clear() { 34 | return { 35 | baseStatisticModule: [], //十项数据 36 | diffDepartIncomeModule: [], //不同科室的收入明细 37 | drugIncomeDetailsModule: {}, //药品收入明细 38 | nonDrugIncomeDetailsModule: [], //非药品收入明细 39 | }; 40 | }, 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/models/bedspace.js: -------------------------------------------------------------------------------- 1 | import { fetchBedData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'bedspace', 5 | 6 | state: { 7 | internalSliceBedInfoModule: {}, //内科片床位使用率 8 | surgicalSliceBedInfoModule: {}, //外科片床位使用率 9 | specialSliceBedInfoModule: {}, //特殊科室片区床位使用率 10 | diffSickBlockInfoModule: [], 11 | highestTurnoverTopTenModule: [], 12 | lowestTurnoverTopTenModule: [] 13 | }, 14 | 15 | effects: { 16 | *fetch({ payload }, { call, put }) { 17 | const response = yield call(fetchBedData, payload); 18 | yield put({ 19 | type: 'save', 20 | payload: response.result 21 | }) 22 | }, 23 | }, 24 | 25 | reducers: { 26 | save(state, { payload }) { 27 | return { 28 | ...state, 29 | ...payload 30 | }; 31 | }, 32 | clear() { 33 | return { 34 | internalSliceBedInfoModule: {}, //内科片床位使用率 35 | surgicalSliceBedInfoModule: {}, //外科片床位使用率 36 | specialSliceBedInfoModule: {}, //特殊科室片区床位使用率 37 | diffSickBlockInfoModule: [], 38 | highestTurnoverTopTenModule: [], 39 | lowestTurnoverTopTenModule: [] 40 | } 41 | } 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /tests/run-tests.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process'); 2 | const { kill } = require('cross-port-killer'); 3 | 4 | const env = Object.create(process.env); 5 | env.BROWSER = 'none'; 6 | const startServer = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['start'], { 7 | env, 8 | }); 9 | 10 | startServer.stderr.on('data', (data) => { 11 | // eslint-disable-next-line 12 | console.log(data); 13 | }); 14 | 15 | startServer.on('exit', () => { 16 | kill(process.env.PORT || 8000); 17 | }); 18 | 19 | // eslint-disable-next-line 20 | console.log('Starting development server for e2e tests...'); 21 | startServer.stdout.on('data', (data) => { 22 | // eslint-disable-next-line 23 | console.log(data.toString()); 24 | if (data.toString().indexOf('Compiled successfully') >= 0 || 25 | data.toString().indexOf('Compiled with warnings') >= 0) { 26 | // eslint-disable-next-line 27 | console.log('Development server is started, ready to run tests.'); 28 | const testCmd = spawn(/^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['test'], { 29 | stdio: 'inherit', 30 | }); 31 | testCmd.on('exit', () => { 32 | startServer.kill(); 33 | }); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | import { query as queryUsers, queryCurrent } from '../services/user'; 2 | 3 | export default { 4 | namespace: 'user', 5 | 6 | state: { 7 | list: [], 8 | currentUser: {}, 9 | }, 10 | 11 | effects: { 12 | *fetch(_, { call, put }) { 13 | const response = yield call(queryUsers); 14 | yield put({ 15 | type: 'save', 16 | payload: response, 17 | }); 18 | }, 19 | *fetchCurrent(_, { call, put }) { 20 | const response = yield call(queryCurrent); 21 | yield put({ 22 | type: 'saveCurrentUser', 23 | payload: response, 24 | }); 25 | }, 26 | }, 27 | 28 | reducers: { 29 | save(state, action) { 30 | return { 31 | ...state, 32 | list: action.payload, 33 | }; 34 | }, 35 | saveCurrentUser(state, action) { 36 | return { 37 | ...state, 38 | currentUser: action.payload, 39 | }; 40 | }, 41 | changeNotifyCount(state, action) { 42 | return { 43 | ...state, 44 | currentUser: { 45 | ...state.currentUser, 46 | notifyCount: action.payload, 47 | }, 48 | }; 49 | }, 50 | }, 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/Login/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Login 3 | subtitle: 登录 4 | cols: 1 5 | order: 15 6 | --- 7 | 8 | 支持多种登录方式切换,内置了几种常见的登录控件,可以灵活组合,也支持和自定义控件配合使用。 9 | 10 | ## API 11 | 12 | ### Login 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | defaultActiveKey | 默认激活 tab 面板的 key | String | - 17 | onTabChange | 切换页签时的回调 | (key) => void | - 18 | onSubmit | 点击提交时的回调 | (err, values) => void | - 19 | 20 | ### Login.Tab 21 | 22 | 参数 | 说明 | 类型 | 默认值 23 | ----|------|-----|------ 24 | key | 对应选项卡的 key | String | - 25 | tab | 选项卡头显示文字 | ReactNode | - 26 | 27 | ### Login.UserName 28 | 29 | 参数 | 说明 | 类型 | 默认值 30 | ----|------|-----|------ 31 | name | 控件标记,提交数据中同样以此为 key | String | - 32 | rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | - 33 | 34 | 除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。 35 | 36 | ### Login.Password、Login.Mobile 同 Login.UserName 37 | 38 | ### Login.Captcha 39 | 40 | 参数 | 说明 | 类型 | 默认值 41 | ----|------|-----|------ 42 | onGetCaptcha | 点击获取校验码的回调 | () => void | - 43 | 44 | 除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。 45 | 46 | ### Login.Submit 47 | 48 | 支持 antd.Button 的所有属性。 49 | -------------------------------------------------------------------------------- /src/components/Exception/index.js: -------------------------------------------------------------------------------- 1 | import React, { createElement } from 'react'; 2 | import classNames from 'classnames'; 3 | import { Button } from 'antd'; 4 | import config from './typeConfig'; 5 | import styles from './index.less'; 6 | 7 | export default ({ className, linkElement = 'a', type, title, desc, img, actions, ...rest }) => { 8 | const pageType = type in config ? type : '404'; 9 | const clsString = classNames(styles.exception, className); 10 | return ( 11 |
12 |
13 |
17 |
18 |
19 |

{title || config[pageType].title}

20 |
{desc || config[pageType].desc}
21 |
22 | { 23 | actions || 24 | createElement(linkElement, { 25 | to: '/', 26 | href: '/', 27 | }, ) 28 | } 29 |
30 |
31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/SiderMenu/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | @ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86); 3 | .logo { 4 | height: 64px; 5 | position: relative; 6 | line-height: 64px; 7 | padding-left: (@menu-collapsed-width - 32px) / 2; 8 | transition: all 0.3s; 9 | background: #fff; 10 | overflow: hidden; 11 | img { 12 | display: inline-block; 13 | vertical-align: middle; 14 | height: 32px; 15 | } 16 | h1 { 17 | color: white; 18 | display: inline-block; 19 | vertical-align: middle; 20 | font-size: 20px; 21 | margin: 0 0 0 12px; 22 | font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif; 23 | font-weight: 600; 24 | } 25 | } 26 | 27 | .sider { 28 | min-height: 100vh; 29 | box-shadow: 0 0 6px rgba(0, 21, 41, 0.35); 30 | position: relative; 31 | z-index: 10; 32 | background: #fff; 33 | &.ligth { 34 | background-color: white; 35 | .logo { 36 | background: white; 37 | h1 { 38 | color: #002140; 39 | } 40 | } 41 | } 42 | } 43 | 44 | .icon { 45 | width: 14px; 46 | margin-right: 10px; 47 | } 48 | 49 | :global { 50 | .drawer .drawer-content { 51 | background: #001529; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/Clinic.less: -------------------------------------------------------------------------------- 1 | .registeringWrap { 2 | display: inline-block; 3 | width: 50%; 4 | padding: 34px 0; 5 | .registering { 6 | text-align: center; 7 | } 8 | .registeringNum { 9 | color: #15984B; 10 | font-size: 20px; 11 | font-weight: 500; 12 | } 13 | .registeringTypes { 14 | color: #999; 15 | font-size: 14px; 16 | } 17 | } 18 | .horizontal { 19 | position: absolute; 20 | height: 1px; 21 | background: #E8E8E8; 22 | left: 20px; 23 | right: 20px; 24 | top: 47%; 25 | transform: translateY(-50%); 26 | 27 | } 28 | .vertical { 29 | position: absolute; 30 | width: 1px; 31 | background: #E8E8E8; 32 | top: 0; 33 | bottom: 20px; 34 | left: 50%; 35 | transform: translateX(-50%); 36 | } 37 | .wrapCard { 38 | display: inline-block; 39 | width: 20%; 40 | padding: 0 10px; 41 | margin-bottom: 20px; 42 | .countCard { 43 | background-color: #FAFFF9; 44 | border-color: #B4D9C4; 45 | border-radius: 8px; 46 | } 47 | .itemPart { 48 | font-size: 14px; 49 | text-align: center; 50 | color: #666; 51 | height: 40px; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | } 56 | .countPart { 57 | text-align: center; 58 | color: #333; 59 | font-size: 20px; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/e2e/login.e2e.js: -------------------------------------------------------------------------------- 1 | import puppeteer from 'puppeteer'; 2 | 3 | describe('Login', () => { 4 | let browser; 5 | let page; 6 | 7 | beforeAll(async () => { 8 | browser = await puppeteer.launch(); 9 | }); 10 | 11 | beforeEach(async () => { 12 | page = await browser.newPage(); 13 | await page.goto('http://localhost:8000/#/user/login'); 14 | await page.evaluate(() => window.localStorage.setItem('antd-pro-authority', 'guest')); 15 | }); 16 | 17 | afterEach(() => page.close()); 18 | 19 | it('should login with failure', async () => { 20 | await page.type('#userName', 'mockuser'); 21 | await page.type('#password', 'wrong_password'); 22 | await page.click('button[type="submit"]'); 23 | await page.waitForSelector('.ant-alert-error'); // should display error 24 | }); 25 | 26 | it('should login successfully', async () => { 27 | await page.type('#userName', 'admin'); 28 | await page.type('#password', '888888'); 29 | await page.click('button[type="submit"]'); 30 | await page.waitForSelector('.ant-layout-sider h1'); // should display error 31 | const text = await page.evaluate(() => document.body.innerHTML); 32 | expect(text).toContain('

Ant Design Pro

'); 33 | }); 34 | 35 | afterAll(() => browser.close()); 36 | }); 37 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Consumable.less: -------------------------------------------------------------------------------- 1 | .registeringWrap { 2 | display: inline-block; 3 | width: 50%; 4 | padding: 34px 0; 5 | .registering { 6 | text-align: center; 7 | } 8 | .registeringNum { 9 | color: #15984B; 10 | font-size: 20px; 11 | font-weight: 500; 12 | } 13 | .registeringTypes { 14 | color: #999; 15 | font-size: 14px; 16 | } 17 | } 18 | .horizontal { 19 | position: absolute; 20 | height: 1px; 21 | background: #E8E8E8; 22 | left: 20px; 23 | right: 20px; 24 | top: 47%; 25 | transform: translateY(-50%); 26 | 27 | } 28 | .vertical { 29 | position: absolute; 30 | width: 1px; 31 | background: #E8E8E8; 32 | top: 0; 33 | bottom: 20px; 34 | left: 50%; 35 | transform: translateX(-50%); 36 | } 37 | .wrapCard { 38 | display: inline-block; 39 | width: 20%; 40 | padding: 0 10px; 41 | margin-bottom: 20px; 42 | .countCard { 43 | background-color: #FAFFF9; 44 | border-color: #B4D9C4; 45 | border-radius: 8px; 46 | } 47 | .itemPart { 48 | font-size: 14px; 49 | text-align: center; 50 | color: #666; 51 | height: 40px; 52 | display: flex; 53 | justify-content: center; 54 | align-items: center; 55 | } 56 | .countPart { 57 | text-align: center; 58 | color: #333; 59 | font-size: 20px; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: NoticeIcon 4 | zh-CN: NoticeIcon 5 | subtitle: 通知菜单 6 | cols: 1 7 | order: 9 8 | --- 9 | 10 | 用在导航工具栏上,作为整个产品统一的通知中心。 11 | 12 | ## API 13 | 14 | 参数 | 说明 | 类型 | 默认值 15 | ----|------|-----|------ 16 | count | 图标上的消息总数 | number | - 17 | loading | 弹出卡片加载状态 | boolean | false 18 | onClear | 点击清空按钮的回调 | function(tabTitle) | - 19 | onItemClick | 点击列表项的回调 | function(item, tabProps) | - 20 | onTabChange | 切换页签的回调 | function(tabTitle) | - 21 | popupAlign | 弹出卡片的位置配置 | Object [alignConfig](https://github.com/yiminghe/dom-align#alignconfig-object-details) | - 22 | onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | - 23 | popupVisible | 控制弹层显隐 | boolean | - 24 | locale | 默认文案 | Object | `{ emptyText: '暂无数据', clear: '清空' }` 25 | 26 | ### NoticeIcon.Tab 27 | 28 | 参数 | 说明 | 类型 | 默认值 29 | ----|------|-----|------ 30 | title | 消息分类的页签标题 | string | - 31 | list | 列表数据,格式参照下表 | Array | `[]` 32 | emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | - 33 | emptyImage | 针对每个 Tab 定制空数据图片 | string | - 34 | 35 | ### Tab data 36 | 37 | 参数 | 说明 | 类型 | 默认值 38 | ----|------|-----|------ 39 | avatar | 头像图片链接 | string | - 40 | title | 标题 | ReactNode | - 41 | description | 描述信息 | ReactNode | - 42 | datetime | 时间戳 | ReactNode | - 43 | extra | 额外信息,在列表项右上角 | ReactNode | - 44 | -------------------------------------------------------------------------------- /src/models/dynamic.js: -------------------------------------------------------------------------------- 1 | import { fetchDynamicData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'dynamic', 5 | 6 | state: { 7 | patientBurdenModule: [], //患者负担 8 | totalEmergencyVisitsModule: {} , //总门急诊人次 9 | chinaPrescriptionsModule: {}, //中医处方数量 10 | nondrugTreatmentRateModule: {}, //非药物中治率(门诊) 11 | inHospitalModule: [], //在院模块 12 | totalIncomeModule: [], //总收入模块 13 | loading: false, 14 | }, 15 | 16 | effects: { 17 | *fetch({ payload }, { call, put }) { 18 | //call function 19 | const response = yield call(fetchDynamicData, payload); 20 | //dispatch reducers 21 | yield put({ 22 | type: 'save', 23 | payload: response.result, 24 | }); 25 | }, 26 | }, 27 | //负责修改model的数据 28 | reducers: { 29 | save(state, { payload }) { 30 | return payload ? { 31 | ...state, 32 | ...payload, 33 | } : {}; 34 | }, 35 | clear() { 36 | return { 37 | patientBurdenModule: [], //患者负担 38 | totalEmergencyVisitsModule: {} , //总门急诊人次 39 | chinaPrescriptionsModule: {}, //中医处方数量 40 | nondrugTreatmentRateModule: {}, //非药物中治率(门诊) 41 | inHospitalModule: [], //在院院模块 42 | totalIncomeModule: [], //总收入模块 43 | }; 44 | }, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/CountCard/MonthlyCard/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, Divider } from 'antd'; 3 | import Compare from '../../../components/Compare'; 4 | 5 | import styles from './index.less'; 6 | 7 | const MonthlyCard = ({ 8 | color, title, content, increase, decrease, unit = '', loading, onCardClick 9 | }) => { 10 | return ( 11 | 19 |
20 |
{title}
21 |
22 | { content == undefined ? '--' : content } 23 | {unit ? ( 24 | {unit} 25 | ) : null} 26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 | ) 35 | } 36 | 37 | export default MonthlyCard; -------------------------------------------------------------------------------- /src/models/clinic.js: -------------------------------------------------------------------------------- 1 | import { fetchClinicData } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'clinic', 5 | 6 | state: { 7 | outpatientSlice: [], //门诊片 8 | surgricalSlice: [], //外科片 9 | internalSlice: [], //内科片 10 | otherSlice: [], //不参与考核科室的门急诊人次 11 | dailyOutpatientEmergencyInfo: [], //本期每天门急诊人次 12 | registrationStatistic: [], //不同挂号类别数量 13 | outpatientEmergencyStatistic: [], //门急诊单项统计 14 | loading: false, 15 | }, 16 | 17 | effects: { 18 | *fetch({ payload }, { call, put }) { 19 | //call function 20 | const response = yield call(fetchClinicData, payload); 21 | //dispatch reducers 22 | yield put({ 23 | type: 'save', 24 | payload: response.result, 25 | }); 26 | }, 27 | }, 28 | //负责修改model的数据 29 | reducers: { 30 | save(state, { payload }) { 31 | return payload ? { 32 | ...state, 33 | ...payload, 34 | } : {}; 35 | }, 36 | clear() { 37 | return { 38 | outpatientSlice: [], //外科片 39 | surgricalSlice: [], //门诊片 40 | internalSlice: [], //内科片 41 | otherSlice: [], //不参与考核科室的门急诊人次 42 | dailyOutpatientEmergencyInfo: [], 43 | registrationStatistic: [], 44 | outpatientEmergencyStatistic: [] 45 | }; 46 | }, 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { routerRedux, Route, Switch } from 'dva/router'; 3 | import { LocaleProvider, Spin } from 'antd'; 4 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 5 | import dynamic from 'dva/dynamic'; 6 | import { getRouterData } from './common/router'; 7 | import Authorized from './utils/Authorized'; 8 | import styles from './index.less'; 9 | 10 | const { ConnectedRouter } = routerRedux; 11 | const { AuthorizedRoute } = Authorized; 12 | dynamic.setDefaultLoadingComponent(() => { 13 | return ; 14 | }); 15 | 16 | function RouterConfig({ history, app }) { 17 | const routerData = getRouterData(app); 18 | // const UserLayout = routerData['/user'].component; 19 | const BasicLayout = routerData['/'].component; 20 | return ( 21 | 22 | 23 | 24 | {/**/} 28 | } 31 | authority={['admin', 'user']} 32 | redirectPath="/user/login" 33 | /> 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export default RouterConfig; 41 | -------------------------------------------------------------------------------- /src/components/Authorized/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteProps } from 'react-router'; 3 | 4 | type authorityFN = (currentAuthority?: string) => boolean; 5 | 6 | type authority = string | Array | authorityFN | Promise; 7 | 8 | export type IReactComponent

= 9 | | React.StatelessComponent

10 | | React.ComponentClass

11 | | React.ClassicComponentClass

; 12 | 13 | interface Secured { 14 | (authority: authority, error?: React.ReactNode): ( 15 | target: T, 16 | ) => T; 17 | } 18 | 19 | export interface AuthorizedRouteProps extends RouteProps { 20 | authority: authority; 21 | } 22 | export class AuthorizedRoute extends React.Component< 23 | AuthorizedRouteProps, 24 | any 25 | > {} 26 | 27 | interface check { 28 | ( 29 | authority: authority, 30 | target: T, 31 | Exception: S, 32 | ): T | S; 33 | } 34 | 35 | interface AuthorizedProps { 36 | authority: authority; 37 | noMatch?: React.ReactNode; 38 | } 39 | 40 | export class Authorized extends React.Component { 41 | static Secured: Secured; 42 | static AuthorizedRoute: typeof AuthorizedRoute; 43 | static check: check; 44 | } 45 | 46 | declare function renderAuthorize(currentAuthority: string): typeof Authorized; 47 | 48 | export default renderAuthorize; 49 | -------------------------------------------------------------------------------- /src/components/Charts/Bar/shape/baseLine.js: -------------------------------------------------------------------------------- 1 | const baseLine = (cfg, container, shape, transpose) => { 2 | const points = cfg.points; 3 | let path = []; 4 | 5 | if(transpose){ 6 | path.push(['M', points[0].x, points[0].y]); 7 | path.push(['L', points[1].x, 1]); 8 | path.push(['L', points[2].x, 1]); 9 | path.push(['L', points[3].x, points[3].y]); 10 | }else{ 11 | path.push(['M', points[0].x, 1]); 12 | path.push(['L', points[1].x, points[1].y]); 13 | path.push(['L', points[2].x, points[2].y]); 14 | path.push(['L', points[3].x, 1]); 15 | } 16 | path.push('Z'); 17 | path = shape.parsePath(path); // 将 0 - 1 转化为画布坐标 18 | const setting = transpose ? { 19 | x: path[0][1], 20 | y: path[2][2], 21 | width: path[2][1] - path[0][1], 22 | height: path[0][2] - path[2][2], 23 | fill: cfg.color, 24 | radius: path[2][1] - path[0][1] > path[0][2] - path[2][2] ? (path[0][2] - path[2][2]) / 2:0, 25 | opacity: 0.3, 26 | } : { 27 | x: path[1][1], 28 | y: path[1][2], 29 | width: path[2][1] - path[1][1], 30 | height: path[0][2] - path[1][2], 31 | fill: cfg.color, 32 | radius: path[0][2] - path[1][2] > path[2][1] - path[1][1] ? (path[2][1] - path[1][1]) / 2 : 0, 33 | opacity: 0.3, 34 | }; 35 | return setting; 36 | }; 37 | 38 | export default baseLine; 39 | -------------------------------------------------------------------------------- /src/models/tab.js: -------------------------------------------------------------------------------- 1 | const panes = JSON.parse(sessionStorage.getItem('panes')); 2 | 3 | export default { 4 | namespace: 'tab', 5 | 6 | state: { 7 | panes: panes ? panes.panes : [ 8 | { 9 | title: '今日动态', 10 | key: '/dashboard/report', 11 | content: '' 12 | } 13 | ], 14 | activeKey: panes ? panes.activeKey : '/dashboard/report' 15 | }, 16 | 17 | reducers: { 18 | update(state, { payload }) { 19 | const findIndex = state.panes.find(item => item.title === payload.title) 20 | const panes = findIndex === undefined ? [...state.panes, payload] : state.panes 21 | sessionStorage.setItem('panes', JSON.stringify({ 22 | panes, 23 | activeKey: payload.key, 24 | })); 25 | return { 26 | panes, 27 | activeKey: payload.key, 28 | } 29 | }, 30 | check(state, { activeKey }) { 31 | sessionStorage.setItem('panes', JSON.stringify({ 32 | ...state, 33 | activeKey 34 | })); 35 | return { 36 | ...state, 37 | activeKey 38 | } 39 | }, 40 | delete(state, { payload }) { 41 | const { findIndex, lastKey } = payload; 42 | let panes = state.panes.concat(); 43 | panes.splice(findIndex, 1); 44 | sessionStorage.setItem('panes', JSON.stringify({ 45 | panes, 46 | activeKey: lastKey, 47 | })); 48 | return { 49 | panes, 50 | activeKey: lastKey 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/components/Login/map.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input, Icon } from 'antd'; 3 | import styles from './index.less'; 4 | 5 | const map = { 6 | UserName: { 7 | component: Input, 8 | props: { 9 | size: 'large', 10 | prefix: , 11 | placeholder: 'admin', 12 | }, 13 | rules: [{ 14 | required: true, message: 'Please enter username!', 15 | }], 16 | }, 17 | Password: { 18 | component: Input, 19 | props: { 20 | size: 'large', 21 | prefix: , 22 | type: 'password', 23 | placeholder: '888888', 24 | }, 25 | rules: [{ 26 | required: true, message: 'Please enter password!', 27 | }], 28 | }, 29 | Mobile: { 30 | component: Input, 31 | props: { 32 | size: 'large', 33 | prefix: , 34 | placeholder: 'mobile number', 35 | }, 36 | rules: [{ 37 | required: true, message: 'Please enter mobile number!', 38 | }, { 39 | pattern: /^1\d{10}$/, message: 'Wrong mobile number format!', 40 | }], 41 | }, 42 | Captcha: { 43 | component: Input, 44 | props: { 45 | size: 'large', 46 | prefix: , 47 | placeholder: 'captcha', 48 | }, 49 | rules: [{ 50 | required: true, message: 'Please enter Captcha!', 51 | }], 52 | }, 53 | }; 54 | 55 | export default map; 56 | -------------------------------------------------------------------------------- /src/components/Charts/autoHeight.js: -------------------------------------------------------------------------------- 1 | /* eslint eqeqeq: 0 */ 2 | import React from 'react'; 3 | 4 | function computeHeight(node) { 5 | const totalHeight = parseInt(getComputedStyle(node).height, 10); 6 | const padding = 7 | parseInt(getComputedStyle(node).paddingTop, 10) + 8 | parseInt(getComputedStyle(node).paddingBottom, 10); 9 | return totalHeight - padding; 10 | } 11 | 12 | function getAutoHeight(n) { 13 | if (!n) { 14 | return 0; 15 | } 16 | 17 | let node = n; 18 | 19 | let height = computeHeight(node); 20 | 21 | while (!height) { 22 | node = node.parentNode; 23 | if (node) { 24 | height = computeHeight(node); 25 | } else { 26 | break; 27 | } 28 | } 29 | 30 | return height; 31 | } 32 | 33 | const autoHeight = () => (WrappedComponent) => { 34 | return class extends React.Component { 35 | state = { 36 | computedHeight: 0, 37 | }; 38 | 39 | componentDidMount() { 40 | const { height } = this.props; 41 | if (!height) { 42 | const h = getAutoHeight(this.root); 43 | // eslint-disable-next-line 44 | this.setState({ computedHeight: h }); 45 | } 46 | } 47 | 48 | handleRoot = (node) => { 49 | this.root = node; 50 | }; 51 | 52 | render() { 53 | const { height } = this.props; 54 | const { computedHeight } = this.state; 55 | const h = height || computedHeight; 56 | return ( 57 |

{h > 0 && }
58 | ); 59 | } 60 | }; 61 | }; 62 | 63 | export default autoHeight; 64 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: PageHeader 4 | zh-CN: PageHeader 5 | subtitle: 页头 6 | cols: 1 7 | order: 11 8 | --- 9 | 10 | 页头用来声明页面的主题,包含了用户所关注的最重要的信息,使用户可以快速理解当前页面是什么以及它的功能。 11 | 12 | ## API 13 | 14 | | 参数 | 说明 | 类型 | 默认值 | 15 | |----------|------------------------------------------|-------------|-------| 16 | | title | title 区域 | ReactNode | - | 17 | | logo | logo区域 | ReactNode | - | 18 | | action | 操作区,位于 title 行的行尾 | ReactNode | - | 19 | | content | 内容区 | ReactNode | - | 20 | | extraContent | 额外内容区,位于content的右侧 | ReactNode | - | 21 | | breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - | 22 | | routes | 面包屑相关属性,router 的路由栈信息 | object[] | - | 23 | | params | 面包屑相关属性,路由的参数 | object | - | 24 | | location | 面包屑相关属性,当前的路由信息 | object | - | 25 | | breadcrumbNameMap | 面包屑相关属性,路由的地址-名称映射表 | object | - | 26 | | tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - | 27 | | tabActiveKey | 当前高亮的 tab 项 | string | - | 28 | | tabDefaultActiveKey | 默认高亮的 tab 项 | string | 第一项 | 29 | | onTabChange | 切换面板的回调 | (key) => void | - | 30 | | linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - | 31 | 32 | > 面包屑的配置方式有三种,一是直接配置 `breadcrumbList`,二是结合 `react-router@2` `react-router@3`,配置 `routes` 及 `params` 实现,类似 [面包屑 Demo](https://ant.design/components/breadcrumb-cn/#components-breadcrumb-demo-router),三是结合 `react-router@4`,配置 `location` `breadcrumbNameMap`,优先级依次递减,脚手架中使用最后一种。 对于后两种用法,你也可以将 `routes` `params` 及 `location` `breadcrumbNameMap` 放到 context 中,组件会自动获取。 33 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.test.js: -------------------------------------------------------------------------------- 1 | import { getBreadcrumb } from './index'; 2 | import { urlToList } from '../utils/pathTools'; 3 | 4 | const routerData = { 5 | '/dashboard/analysis': { 6 | name: '分析页', 7 | }, 8 | '/userinfo': { 9 | name: '用户列表', 10 | }, 11 | '/userinfo/:id': { 12 | name: '用户信息', 13 | }, 14 | '/userinfo/:id/addr': { 15 | name: '收货订单', 16 | }, 17 | }; 18 | describe('test getBreadcrumb', () => { 19 | it('Simple url', () => { 20 | expect(getBreadcrumb(routerData, '/dashboard/analysis').name).toEqual( 21 | '分析页', 22 | ); 23 | }); 24 | it('Parameters url', () => { 25 | expect(getBreadcrumb(routerData, '/userinfo/2144').name).toEqual( 26 | '用户信息', 27 | ); 28 | }); 29 | it('The middle parameter url', () => { 30 | expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual( 31 | '收货订单', 32 | ); 33 | }); 34 | it('Loop through the parameters', () => { 35 | const urlNameList = urlToList('/userinfo/2144/addr').map((url) => { 36 | return getBreadcrumb(routerData, url).name; 37 | }); 38 | expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']); 39 | }); 40 | 41 | it('a path', () => { 42 | const urlNameList = urlToList('/userinfo').map((url) => { 43 | return getBreadcrumb(routerData, url).name; 44 | }); 45 | expect(urlNameList).toEqual(['用户列表']); 46 | }); 47 | it('Secondary path', () => { 48 | const urlNameList = urlToList('/userinfo/2144').map((url) => { 49 | return getBreadcrumb(routerData, url).name; 50 | }); 51 | expect(urlNameList).toEqual(['用户列表', '用户信息']); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/NumberInfo/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .numberInfo { 4 | .suffix { 5 | color: @text-color; 6 | font-size: 16px; 7 | font-style: normal; 8 | margin-left: 4px; 9 | } 10 | .numberInfoTitle { 11 | color: @text-color; 12 | font-size: @font-size-lg; 13 | margin-bottom: 16px; 14 | transition: all .3s; 15 | } 16 | .numberInfoSubTitle { 17 | color: @text-color-secondary; 18 | font-size: @font-size-base; 19 | height: 22px; 20 | line-height: 22px; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | word-break: break-all; 24 | white-space: nowrap; 25 | } 26 | .numberInfoValue { 27 | margin-top: 4px; 28 | font-size: 0; 29 | overflow: hidden; 30 | text-overflow: ellipsis; 31 | word-break: break-all; 32 | white-space: nowrap; 33 | & > span { 34 | color: @heading-color; 35 | display: inline-block; 36 | line-height: 32px; 37 | height: 32px; 38 | font-size: 24px; 39 | margin-right: 32px; 40 | } 41 | .subTotal { 42 | color: @text-color-secondary; 43 | font-size: @font-size-lg; 44 | vertical-align: top; 45 | margin-right: 0; 46 | i { 47 | font-size: 12px; 48 | transform: scale(0.82); 49 | margin-left: 4px; 50 | } 51 | :global { 52 | .anticon-caret-up { 53 | color: @red-6; 54 | } 55 | .anticon-caret-down { 56 | color: @green-6; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | .numberInfolight { 63 | .numberInfoValue { 64 | & > span { 65 | color: @text-color; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/NoticeList.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .list { 4 | max-height: 400px; 5 | overflow: auto; 6 | .item { 7 | transition: all .3s; 8 | overflow: hidden; 9 | cursor: pointer; 10 | padding-left: 24px; 11 | padding-right: 24px; 12 | 13 | .meta { 14 | width: 100%; 15 | } 16 | 17 | .avatar { 18 | background: #fff; 19 | margin-top: 4px; 20 | } 21 | 22 | &.read { 23 | opacity: .4; 24 | } 25 | &:last-child { 26 | border-bottom: 0; 27 | } 28 | &:hover { 29 | background: @primary-1; 30 | } 31 | .title { 32 | font-weight: normal; 33 | margin-bottom: 8px; 34 | } 35 | .description { 36 | font-size: 12px; 37 | line-height: @line-height-base; 38 | } 39 | .datetime { 40 | font-size: 12px; 41 | margin-top: 4px; 42 | line-height: @line-height-base; 43 | } 44 | .extra { 45 | float: right; 46 | color: @text-color-secondary; 47 | font-weight: normal; 48 | margin-right: 0; 49 | margin-top: -1.5px; 50 | } 51 | } 52 | } 53 | 54 | .notFound { 55 | text-align: center; 56 | padding: 73px 0 88px 0; 57 | color: @text-color-secondary; 58 | img { 59 | display: inline-block; 60 | margin-bottom: 16px; 61 | height: 76px; 62 | } 63 | } 64 | 65 | .clear { 66 | height: 46px; 67 | line-height: 46px; 68 | text-align: center; 69 | color: @text-color; 70 | border-radius: 0 0 @border-radius-base @border-radius-base; 71 | border-top: 1px solid @border-color-split; 72 | transition: all .3s; 73 | cursor: pointer; 74 | 75 | &:hover { 76 | color: @heading-color; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/Authorized/PromiseRender.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Spin } from 'antd'; 3 | 4 | export default class PromiseRender extends React.PureComponent { 5 | state = { 6 | component: null, 7 | }; 8 | componentDidMount() { 9 | this.setRenderComponent(this.props); 10 | } 11 | componentWillReceiveProps(nextProps) { 12 | // new Props enter 13 | this.setRenderComponent(nextProps); 14 | } 15 | // set render Component : ok or error 16 | setRenderComponent(props) { 17 | const ok = this.checkIsInstantiation(props.ok); 18 | const error = this.checkIsInstantiation(props.error); 19 | props.promise 20 | .then(() => { 21 | this.setState({ 22 | component: ok, 23 | }); 24 | }) 25 | .catch(() => { 26 | this.setState({ 27 | component: error, 28 | }); 29 | }); 30 | } 31 | // Determine whether the incoming component has been instantiated 32 | // AuthorizedRoute is already instantiated 33 | // Authorized render is already instantiated, children is no instantiated 34 | // Secured is not instantiated 35 | checkIsInstantiation = (target) => { 36 | if (!React.isValidElement(target)) { 37 | return target; 38 | } 39 | return () => target; 40 | }; 41 | render() { 42 | const Component = this.state.component; 43 | return Component ? ( 44 | 45 | ) : ( 46 |
55 | 56 |
57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Login/index.en-US.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Login 3 | cols: 1 4 | order: 15 5 | --- 6 | 7 | Support multiple common ways of login with built-in controls. You can choose your own combinations and use with your custom controls. 8 | 9 | ## API 10 | 11 | ### Login 12 | 13 | Property | Description | Type | Default 14 | ----|------|-----|------ 15 | defaultActiveKey | default key to activate the tab panel | String | - 16 | onTabChange | callback on changing tabs | (key) => void | - 17 | onSubmit | callback on submit | (err, values) => void | - 18 | 19 | ### Login.Tab 20 | 21 | Property | Description | Type | Default 22 | ----|------|-----|------ 23 | key | key of the tab | String | - 24 | tab | displayed text of the tab | ReactNode | - 25 | 26 | ### Login.UserName 27 | 28 | Property | Description | Type | Default 29 | ----|------|-----|------ 30 | name | name of the control, also the key of the submitted data | String | - 31 | rules | validation rules, same with [option.rules](getFieldDecorator(id, options)) in Form getFieldDecorator(id, options) | object[] | - 32 | 33 | Apart from the above properties, Login.Username also support all properties of antd.Input, together with the default values of basic settings, such as _placeholder_, _size_ and _prefix_. All of these default values can be over-written. 34 | 35 | ### Login.Password, Login.Mobile are the same as Login.UserName 36 | 37 | ### Login.Captcha 38 | 39 | Property | Description | Type | Default 40 | ----|------|-----|------ 41 | onGetCaptcha | callback on getting a new Captcha | () => void | - 42 | 43 | Apart from the above properties, _Login.Captcha_ support the same properties with _Login.UserName_. 44 | 45 | ### Login.Submit 46 | 47 | Support all properties of _antd.Button_. -------------------------------------------------------------------------------- /src/components/PageHeader/demo/structure.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: Structure 4 | --- 5 | 6 | 基本结构,具备响应式布局功能,主要断点为 768px 和 576px,拖动窗口改变大小试试看。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const breadcrumbList = [{ 12 | title: '面包屑', 13 | }]; 14 | 15 | const tabList = [{ 16 | key: '1', 17 | tab: '页签一', 18 | }, { 19 | key: '2', 20 | tab: '页签二', 21 | }, { 22 | key: '3', 23 | tab: '页签三', 24 | }]; 25 | 26 | ReactDOM.render( 27 |
28 | Title
} 31 | logo={
logo
} 32 | action={
action
} 33 | content={
content
} 34 | extraContent={
extraContent
} 35 | breadcrumbList={breadcrumbList} 36 | tabList={tabList} 37 | tabActiveKey="1" 38 | /> 39 |
40 | , mountNode); 41 | ```` 42 | 43 | 69 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Appointment.less: -------------------------------------------------------------------------------- 1 | .legendWrap { 2 | display: flex; 3 | flex-wrap: wrap; 4 | flex-direction: row; 5 | width: 200px; 6 | .legend { 7 | width: 100px; 8 | margin: 15px 0; 9 | .mark { 10 | display: inline-block; 11 | width: 10px; 12 | height: 10px; 13 | border-radius: 50%; 14 | margin-right: 5px; 15 | } 16 | .count { 17 | font-size: 20px; 18 | color: #333; 19 | padding-left: 15px; 20 | } 21 | } 22 | } 23 | .appointmentCountWrap { 24 | padding: 0 10px; 25 | width: 33.33%; 26 | height: 100px; 27 | margin-bottom: 20px; 28 | display: inline-block; 29 | vertical-align: top; 30 | .appointmentCount { 31 | border: 1px solid #E8E8E8; 32 | border-radius: 2px; 33 | width: 100%; 34 | height: 100%; 35 | display: flex; 36 | align-items: center; 37 | padding: 0 15px; 38 | .left { 39 | flex: 1; 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | color: #666; 44 | text-align: center; 45 | } 46 | .right { 47 | flex: 2.5; 48 | padding-left: 10px; 49 | .between { 50 | display: flex; 51 | justify-content: space-between; 52 | align-items: center; 53 | } 54 | .number { 55 | font-size: 18px; 56 | color: #15984B; 57 | } 58 | .title { 59 | color: #333; 60 | } 61 | } 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/routes/Exception/triggerException.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Button, Spin, Card } from 'antd'; 3 | import { connect } from 'dva'; 4 | import styles from './style.less'; 5 | 6 | @connect(state => ({ 7 | isloading: state.error.isloading, 8 | })) 9 | export default class TriggerException extends PureComponent { 10 | state={ 11 | isloading: false, 12 | } 13 | trigger401 = () => { 14 | this.setState({ 15 | isloading: true, 16 | }); 17 | this.props.dispatch({ 18 | type: 'error/query401', 19 | }); 20 | }; 21 | trigger403 = () => { 22 | this.setState({ 23 | isloading: true, 24 | }); 25 | this.props.dispatch({ 26 | type: 'error/query403', 27 | }); 28 | }; 29 | trigger500 = () => { 30 | this.setState({ 31 | isloading: true, 32 | }); 33 | this.props.dispatch({ 34 | type: 'error/query500', 35 | }); 36 | }; 37 | trigger404 = () => { 38 | this.setState({ 39 | isloading: true, 40 | }); 41 | this.props.dispatch({ 42 | type: 'error/query404', 43 | }); 44 | }; 45 | render() { 46 | return ( 47 | 48 | 49 | 52 | 55 | 58 | 61 | 62 | 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/models/date.js: -------------------------------------------------------------------------------- 1 | import { getDate, computeDays, getRangePickerValue } from '../utils/utils'; 2 | 3 | const date = JSON.parse(sessionStorage.getItem('date')); 4 | 5 | const reportBeginDate = date ? date.report.beginDate : getDate(new Date().getTime()-24*60*60*1000); 6 | const reportEndDate = date ? date.report.endDate : getDate(new Date().getTime()-24*60*60*1000); 7 | 8 | const indicatorBeginDate = date ? date.indicator.beginDate : getDate(new Date().getTime()-24*60*60*1000); 9 | const indicatorEndDate = date ? date.indicator.endDate : getDate(new Date().getTime()-24*60*60*1000); 10 | 11 | export default { 12 | namespace: 'date', 13 | 14 | state: { 15 | report: { 16 | beginDate: reportBeginDate, 17 | endDate: reportEndDate, 18 | rangePickerValue: getRangePickerValue(reportBeginDate, reportEndDate), 19 | rangeDateType: computeDays(reportBeginDate, reportEndDate) < 14 ? 'daily' : 'monthly', 20 | rangeDate: computeDays(reportBeginDate, reportEndDate) + 1, 21 | isOneDay: computeDays(reportBeginDate, reportEndDate) === 0 22 | }, 23 | indicator: { 24 | beginDate: indicatorBeginDate, 25 | endDate: indicatorEndDate, 26 | rangePickerValue: getRangePickerValue(indicatorBeginDate, indicatorEndDate), 27 | rangeDateType: computeDays(indicatorBeginDate, indicatorEndDate) < 14 ? 'daily' : 'monthly', 28 | rangeDate: computeDays(indicatorBeginDate, indicatorEndDate) + 1, 29 | isOneDay: computeDays(indicatorBeginDate, indicatorEndDate) === 0 30 | } 31 | }, 32 | 33 | reducers: { 34 | //第二个参数为dispatch的对象 35 | update(state, { payload }) { 36 | sessionStorage.setItem('date', JSON.stringify({ 37 | ...state, 38 | ...payload, 39 | })); 40 | return { 41 | ...state, 42 | ...payload 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/models/global.js: -------------------------------------------------------------------------------- 1 | import { queryNotices } from '../services/api'; 2 | 3 | export default { 4 | namespace: 'global', 5 | 6 | state: { 7 | collapsed: false, 8 | notices: [], 9 | }, 10 | 11 | effects: { 12 | *fetchNotices(_, { call, put }) { 13 | const data = yield call(queryNotices); 14 | yield put({ 15 | type: 'saveNotices', 16 | payload: data, 17 | }); 18 | yield put({ 19 | type: 'user/changeNotifyCount', 20 | payload: data.length, 21 | }); 22 | }, 23 | *clearNotices({ payload }, { put, select }) { 24 | yield put({ 25 | type: 'saveClearedNotices', 26 | payload, 27 | }); 28 | const count = yield select(state => state.global.notices.length); 29 | yield put({ 30 | type: 'user/changeNotifyCount', 31 | payload: count, 32 | }); 33 | }, 34 | }, 35 | 36 | reducers: { 37 | changeLayoutCollapsed(state, { payload }) { 38 | return { 39 | ...state, 40 | collapsed: payload, 41 | }; 42 | }, 43 | saveNotices(state, { payload }) { 44 | return { 45 | ...state, 46 | notices: payload, 47 | }; 48 | }, 49 | saveClearedNotices(state, { payload }) { 50 | return { 51 | ...state, 52 | notices: state.notices.filter(item => item.type !== payload), 53 | }; 54 | }, 55 | }, 56 | 57 | subscriptions: { 58 | setup({ history }) { 59 | // Subscribe history(url) change, trigger `load` action if pathname is `/` 60 | return history.listen(({ pathname, search }) => { 61 | if (typeof window.ga !== 'undefined') { 62 | window.ga('send', 'pageview', pathname + search); 63 | } 64 | }); 65 | }, 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/Dynamic.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .wrapCard { 4 | display: inline-block; 5 | padding: 0 10px; 6 | margin-bottom: 24px; 7 | } 8 | .wrapPrice { 9 | border-bottom: 1px solid #e8e8e8; 10 | padding-bottom: 20px; 11 | margin-bottom: 32px; 12 | .price { 13 | width: 100%; 14 | text-align: center; 15 | font-size: 48px; 16 | color: #15984B; 17 | } 18 | .compare { 19 | text-align: center; 20 | } 21 | } 22 | .vertical { 23 | position: absolute; 24 | top: 46%; 25 | transform: translateY(-50%); 26 | width: 1px; 27 | height: 50px; 28 | background: #e8e8e8; 29 | } 30 | .monthlyInpatientCardTitle { 31 | height: 40px; 32 | color: #fff; 33 | padding: 10px 20px; 34 | font-weight: 500; 35 | background: #FEA101; 36 | } 37 | .monthlyInpatientCardContent { 38 | position: relative; 39 | display: flex; 40 | .part { 41 | flex: 1; 42 | text-align: center; 43 | .partOne { 44 | color: #999; 45 | padding: 10px 0; 46 | } 47 | .partTwo { 48 | color: #FEA101; 49 | font-size: 36px; 50 | margin-bottom: 5px; 51 | } 52 | .partThree { 53 | display: inline-block; 54 | padding-left: 14px; 55 | } 56 | } 57 | } 58 | .burdenCard { 59 | background-color: #FAFFF9; 60 | border-color: #B4D9C4; 61 | border-radius: 8px; 62 | .item { 63 | font-size: 15px; 64 | text-align: center; 65 | color: #666; 66 | height: 40px; 67 | display: flex; 68 | justify-content: center; 69 | align-items: center; 70 | } 71 | .count { 72 | text-align: center; 73 | color: #333; 74 | font-size: 24px; 75 | } 76 | .compare { 77 | border-top: 1px solid #B4D9C4; 78 | text-align: center; 79 | padding-top: 10px; 80 | margin-top: 10px; 81 | } 82 | } -------------------------------------------------------------------------------- /src/components/Exception/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .exception { 4 | display: flex; 5 | align-items: center; 6 | height: 100%; 7 | 8 | .imgBlock { 9 | flex: 0 0 62.5%; 10 | width: 62.5%; 11 | padding-right: 152px; 12 | zoom: 1; 13 | &:before, 14 | &:after { 15 | content: " "; 16 | display: table; 17 | } 18 | &:after { 19 | clear: both; 20 | visibility: hidden; 21 | font-size: 0; 22 | height: 0; 23 | } 24 | } 25 | 26 | .imgEle { 27 | height: 360px; 28 | width: 100%; 29 | max-width: 430px; 30 | float: right; 31 | background-repeat: no-repeat; 32 | background-position: 50% 50%; 33 | background-size: contain; 34 | } 35 | 36 | .content { 37 | flex: auto; 38 | 39 | h1 { 40 | color: #434e59; 41 | font-size: 72px; 42 | font-weight: 600; 43 | line-height: 72px; 44 | margin-bottom: 24px; 45 | } 46 | 47 | .desc { 48 | color: @text-color-secondary; 49 | font-size: 20px; 50 | line-height: 28px; 51 | margin-bottom: 16px; 52 | } 53 | 54 | .actions { 55 | button:not(:last-child) { 56 | margin-right: 8px; 57 | } 58 | } 59 | } 60 | } 61 | 62 | @media screen and (max-width: @screen-xl) { 63 | .exception { 64 | .imgBlock { 65 | padding-right: 88px; 66 | } 67 | } 68 | } 69 | 70 | @media screen and (max-width: @screen-sm) { 71 | .exception { 72 | display: block; 73 | text-align: center; 74 | .imgBlock { 75 | padding-right: 0; 76 | margin: 0 auto 24px; 77 | } 78 | } 79 | } 80 | 81 | @media screen and (max-width: @screen-xs) { 82 | .exception { 83 | .imgBlock { 84 | margin-bottom: -24px; 85 | overflow: hidden; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.test.js: -------------------------------------------------------------------------------- 1 | import { checkPermissions } from './CheckPermissions.js'; 2 | 3 | const target = 'ok'; 4 | const error = 'error'; 5 | 6 | describe('test CheckPermissions', () => { 7 | it('Correct string permission authentication', () => { 8 | expect(checkPermissions('user', 'user', target, error)).toEqual('ok'); 9 | }); 10 | it('Correct string permission authentication', () => { 11 | expect(checkPermissions('user', 'NULL', target, error)).toEqual('error'); 12 | }); 13 | it('authority is undefined , return ok', () => { 14 | expect(checkPermissions(null, 'NULL', target, error)).toEqual('ok'); 15 | }); 16 | it('currentAuthority is undefined , return error', () => { 17 | expect(checkPermissions('admin', null, target, error)).toEqual('error'); 18 | }); 19 | it('Wrong string permission authentication', () => { 20 | expect(checkPermissions('admin', 'user', target, error)).toEqual('error'); 21 | }); 22 | it('Correct Array permission authentication', () => { 23 | expect(checkPermissions(['user', 'admin'], 'user', target, error)).toEqual( 24 | 'ok' 25 | ); 26 | }); 27 | it('Wrong Array permission authentication,currentAuthority error', () => { 28 | expect( 29 | checkPermissions(['user', 'admin'], 'user,admin', target, error) 30 | ).toEqual('error'); 31 | }); 32 | it('Wrong Array permission authentication', () => { 33 | expect(checkPermissions(['user', 'admin'], 'guest', target, error)).toEqual( 34 | 'error' 35 | ); 36 | }); 37 | it('Wrong Function permission authentication', () => { 38 | expect(checkPermissions(() => false, 'guest', target, error)).toEqual( 39 | 'error' 40 | ); 41 | }); 42 | it('Correct Function permission authentication', () => { 43 | expect(checkPermissions(() => true, 'guest', target, error)).toEqual('ok'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/image.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | title: With Image 4 | --- 5 | 6 | 带图片的页头。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | 11 | const content = ( 12 |
13 |

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

14 | 25 |
26 | ); 27 | 28 | const extra = ( 29 |
30 | 31 |
32 | ); 33 | 34 | const breadcrumbList = [{ 35 | title: '一级菜单', 36 | href: '/', 37 | }, { 38 | title: '二级菜单', 39 | href: '/', 40 | }, { 41 | title: '三级菜单', 42 | }]; 43 | 44 | ReactDOM.render( 45 |
46 | 52 |
53 | , mountNode); 54 | ```` 55 | 56 | 76 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/NoticeList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar, List } from 'antd'; 3 | import classNames from 'classnames'; 4 | import styles from './NoticeList.less'; 5 | 6 | export default function NoticeList({ 7 | data = [], onClick, onClear, title, locale, emptyText, emptyImage, 8 | }) { 9 | if (data.length === 0) { 10 | return ( 11 |
12 | {emptyImage ? ( 13 | not found 14 | ) : null} 15 |
{emptyText || locale.emptyText}
16 |
17 | ); 18 | } 19 | return ( 20 |
21 | 22 | {data.map((item, i) => { 23 | const itemCls = classNames(styles.item, { 24 | [styles.read]: item.read, 25 | }); 26 | return ( 27 | onClick(item)}> 28 | : null} 31 | title={ 32 |
33 | {item.title} 34 |
{item.extra}
35 |
36 | } 37 | description={ 38 |
39 |
40 | {item.description} 41 |
42 |
{item.datetime}
43 |
44 | } 45 | /> 46 |
47 | ); 48 | })} 49 |
50 |
51 | {locale.clear}{title} 52 |
53 |
54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Authorized/CheckPermissions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PromiseRender from './PromiseRender'; 3 | import { CURRENT } from './index'; 4 | 5 | function isPromise(obj) { 6 | return ( 7 | !!obj && 8 | (typeof obj === 'object' || typeof obj === 'function') && 9 | typeof obj.then === 'function' 10 | ); 11 | } 12 | 13 | /** 14 | * 通用权限检查方法 15 | * Common check permissions method 16 | * @param { 权限判定 Permission judgment type string |array | Promise | Function } authority 17 | * @param { 你的权限 Your permission description type:string} currentAuthority 18 | * @param { 通过的组件 Passing components } target 19 | * @param { 未通过的组件 no pass components } Exception 20 | */ 21 | const checkPermissions = (authority, currentAuthority, target, Exception) => { 22 | // 没有判定权限.默认查看所有 23 | // Retirement authority, return target; 24 | if (!authority) { 25 | return target; 26 | } 27 | // 数组处理 28 | if (Array.isArray(authority)) { 29 | if (authority.indexOf(currentAuthority) >= 0) { 30 | return target; 31 | } 32 | return Exception; 33 | } 34 | 35 | // string 处理 36 | if (typeof authority === 'string') { 37 | if (authority === currentAuthority) { 38 | return target; 39 | } 40 | return Exception; 41 | } 42 | 43 | // Promise 处理 44 | if (isPromise(authority)) { 45 | return ; 46 | } 47 | 48 | // Function 处理 49 | if (typeof authority === 'function') { 50 | try { 51 | const bool = authority(currentAuthority); 52 | if (bool) { 53 | return target; 54 | } 55 | return Exception; 56 | } catch (error) { 57 | throw error; 58 | } 59 | } 60 | throw new Error('unsupported parameters'); 61 | }; 62 | 63 | export { checkPermissions }; 64 | 65 | const check = (authority, target, Exception) => { 66 | return checkPermissions(authority, CURRENT, target, Exception); 67 | }; 68 | 69 | export default check; 70 | -------------------------------------------------------------------------------- /src/components/Authorized/Secured.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Exception from '../Exception/index'; 3 | import CheckPermissions from './CheckPermissions'; 4 | /** 5 | * 默认不能访问任何页面 6 | * default is "NULL" 7 | */ 8 | const Exception403 = () => ( 9 | 10 | ); 11 | 12 | // Determine whether the incoming component has been instantiated 13 | // AuthorizedRoute is already instantiated 14 | // Authorized render is already instantiated, children is no instantiated 15 | // Secured is not instantiated 16 | const checkIsInstantiation = (target) => { 17 | if (!React.isValidElement(target)) { 18 | return target; 19 | } 20 | return () => target; 21 | }; 22 | 23 | /** 24 | * 用于判断是否拥有权限访问此view权限 25 | * authority 支持传入 string ,funtion:()=>boolean|Promise 26 | * e.g. 'user' 只有user用户能访问 27 | * e.g. 'user,admin' user和 admin 都能访问 28 | * e.g. ()=>boolean 返回true能访问,返回false不能访问 29 | * e.g. Promise then 能访问 catch不能访问 30 | * e.g. authority support incoming string, funtion: () => boolean | Promise 31 | * e.g. 'user' only user user can access 32 | * e.g. 'user, admin' user and admin can access 33 | * e.g. () => boolean true to be able to visit, return false can not be accessed 34 | * e.g. Promise then can not access the visit to catch 35 | * @param {string | function | Promise} authority 36 | * @param {ReactNode} error 非必需参数 37 | */ 38 | const authorize = (authority, error) => { 39 | /** 40 | * conversion into a class 41 | * 防止传入字符串时找不到staticContext造成报错 42 | * String parameters can cause staticContext not found error 43 | */ 44 | let classError = false; 45 | if (error) { 46 | classError = () => error; 47 | } 48 | if (!authority) { 49 | throw new Error('authority is required'); 50 | } 51 | return function decideAuthority(targer) { 52 | const component = CheckPermissions(authority, targer, classError || Exception403); 53 | return checkIsInstantiation(component); 54 | }; 55 | }; 56 | 57 | export default authorize; 58 | -------------------------------------------------------------------------------- /.roadhogrc.mock.js: -------------------------------------------------------------------------------- 1 | import { delay } from 'roadhog-api-doc'; 2 | import mock_dynamic from './mock/Report/dynamic'; 3 | import mock_clinic from './mock/Report/clinic'; 4 | import mock_prescription from './mock/Report/prescription'; 5 | import mock_inpatient from './mock/Report/inpatient'; 6 | import mock_nonDrug from './mock/Report/nonDrug'; 7 | import mock_income from './mock/Report/income'; 8 | 9 | import mock_appointment from './mock/Indicator/appointment'; 10 | import { getMockBedSpace } from './mock/Indicator/bedSpace'; 11 | import mock_consumable from './mock/Indicator/consumable'; 12 | import { getMockEmphasis } from './mock/Indicator/emphasis'; 13 | import { getMockQuality } from './mock/Indicator/quality'; 14 | import { getMockSurgery } from './mock/Indicator/surgery'; 15 | // 是否禁用mock 16 | const noProxy = process.env.NO_PROXY === 'true'; 17 | console.log('proxy:' + noProxy); 18 | // 代码中会兼容本地 service mock 以及部署站点的静态数据 19 | const proxy = { 20 | 'GET /api/currentUser': { 21 | $desc: "获取当前用户接口", 22 | $params: { 23 | pageSize: { 24 | desc: '分页', 25 | exp: 2, 26 | }, 27 | }, 28 | $body: { 29 | name: '沈浩', 30 | avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', 31 | userid: '00000001', 32 | notifyCount: 12, 33 | }, 34 | }, 35 | 'GET /api/today_dynamic_info': mock_dynamic, 36 | 'GET /api/total_outemer_info': mock_clinic, 37 | 'GET /api/tcm_prescription_info': mock_prescription, 38 | 'GET /api/non_drug_info': mock_nonDrug, 39 | 'GET /api/in_out_hospital': mock_inpatient, 40 | 'GET /api/income': mock_income, 41 | 42 | 'GET /api/consumable_info': mock_consumable, 43 | 'GET /api/reservation_info': mock_appointment, 44 | 'GET /api/surgery_info': getMockSurgery, 45 | 'GET /api/bed_info': getMockBedSpace, 46 | 'GET /api/medical_treat_quality': getMockQuality, 47 | 'GET /api/data_operation_info': getMockEmphasis, 48 | }; 49 | 50 | export default noProxy ? { 51 | 'GET /api/(.*)': 'http://msodc.xinhua.com/', 52 | } : delay(proxy, 500); 53 | -------------------------------------------------------------------------------- /src/components/Charts/Pie/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .pie { 4 | position: relative; 5 | display: flex; 6 | justify-content: space-around; 7 | align-items: center; 8 | .chart { 9 | position: relative; 10 | } 11 | &.hasLegend .chart { 12 | // width: ~"calc(65% - 100px)"; 13 | width: 55%; 14 | } 15 | .legend { 16 | position: relative; 17 | // position: absolute; 18 | // right: 0; 19 | // top: 50%; 20 | // transform: translateY(-50%); 21 | margin: 0; 22 | list-style: none; 23 | padding: 0; 24 | li { 25 | cursor: pointer; 26 | margin-bottom: 16px; 27 | height: 22px; 28 | line-height: 22px; 29 | &:last-child { 30 | margin-bottom: 0; 31 | } 32 | } 33 | } 34 | .dot { 35 | border-radius: 8px; 36 | display: inline-block; 37 | margin-right: 8px; 38 | position: relative; 39 | top: -1px; 40 | height: 8px; 41 | width: 8px; 42 | } 43 | .line { 44 | background-color: @border-color-split; 45 | display: inline-block; 46 | margin-right: 8px; 47 | width: 1px; 48 | height: 16px; 49 | } 50 | .legendTitle { 51 | color: #333; 52 | } 53 | .percent { 54 | color: @text-color-secondary; 55 | } 56 | .value { 57 | position: absolute; 58 | right: 0; 59 | } 60 | .title { 61 | margin-bottom: 8px; 62 | } 63 | .total { 64 | position: absolute; 65 | left: 50%; 66 | top: 50%; 67 | text-align: center; 68 | height: 62px; 69 | transform: translate(-50%, -50%); 70 | & > h4 { 71 | color: @text-color-secondary; 72 | font-size: 14px; 73 | 74 | margin-top: 5px; 75 | font-weight: normal; 76 | } 77 | & > p { 78 | color: @heading-color; 79 | display: block; 80 | 81 | white-space: nowrap; 82 | } 83 | } 84 | } 85 | 86 | .legendBlock { 87 | &.hasLegend .chart { 88 | width: 100%; 89 | margin: 0 0 32px 0; 90 | } 91 | .legend { 92 | position: relative; 93 | transform: none; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "plugins": ["compat"], 5 | "env": { 6 | "browser": true, 7 | "node": true, 8 | "es6": true, 9 | "mocha": true, 10 | "jest": true, 11 | "jasmine": true 12 | }, 13 | "rules": { 14 | "generator-star-spacing": [0], 15 | "consistent-return": [0], 16 | "react/forbid-prop-types": [0], 17 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }], 18 | "global-require": [1], 19 | "import/prefer-default-export": [0], 20 | "react/jsx-no-bind": [0], 21 | "react/prop-types": [0], 22 | "react/prefer-stateless-function": [0], 23 | "react/jsx-wrap-multilines": ["error", { 24 | "declaration": "parens-new-line", 25 | "assignment": "parens-new-line", 26 | "return": "parens-new-line", 27 | "arrow": "parens-new-line", 28 | "condition": "parens-new-line", 29 | "logical": "parens-new-line", 30 | "prop": "ignore" 31 | }], 32 | "no-else-return": [0], 33 | "no-restricted-syntax": [0], 34 | "import/no-extraneous-dependencies": [0], 35 | "no-use-before-define": [0], 36 | "jsx-a11y/no-static-element-interactions": [0], 37 | "jsx-a11y/no-noninteractive-element-interactions": [0], 38 | "jsx-a11y/click-events-have-key-events": [0], 39 | "jsx-a11y/anchor-is-valid": [0], 40 | "no-nested-ternary": [0], 41 | "arrow-body-style": [0], 42 | "import/extensions": [0], 43 | "no-bitwise": [0], 44 | "no-cond-assign": [0], 45 | "import/no-unresolved": [0], 46 | "comma-dangle": ["error", { 47 | "arrays": "always-multiline", 48 | "objects": "always-multiline", 49 | "imports": "always-multiline", 50 | "exports": "always-multiline", 51 | "functions": "ignore" 52 | }], 53 | "object-curly-newline": [0], 54 | "function-paren-newline": [0], 55 | "no-restricted-globals": [0], 56 | "require-yield": [1], 57 | "compat/compat": "error" 58 | }, 59 | "parserOptions": { 60 | "ecmaFeatures": { 61 | "experimentalObjectRestSpread": true 62 | } 63 | }, 64 | "settings": { 65 | "polyfills": ["fetch", "promises"] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/TabNav/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Tabs, Button } from 'antd'; 3 | import { routerRedux } from 'dva/router'; 4 | import { Link } from 'dva/router'; 5 | import { connect } from 'dva'; 6 | import styles from './index.less'; 7 | const TabPane = Tabs.TabPane; 8 | 9 | @connect(({ tab }) => ({ 10 | tab, 11 | })) 12 | 13 | class TabNav extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | } 17 | 18 | onChange = (activeKey) => { 19 | const { search } = this.props; 20 | let path = { 21 | pathname: activeKey, 22 | search 23 | }; 24 | this.props.dispatch({ 25 | type: 'tab/check', 26 | activeKey 27 | }) 28 | this.props.dispatch(routerRedux.push(path)); 29 | } 30 | //其实操控dispatch无异于操控state,区别在于将state的公有化,供其他模块使用 31 | onEdit = (targetKey, action) => { 32 | this[action](targetKey); 33 | } 34 | remove = (targetKey) => { 35 | const { tab } = this.props; 36 | const { panes, activeKey } = tab; 37 | const panesLength = panes.length; 38 | let lastIndex = 0; 39 | const findIndex = panes.findIndex(item => item.key === targetKey); 40 | findIndex === panesLength - 1 ? lastIndex = panesLength - 2 : lastIndex = findIndex + 1; 41 | const lastKey = lastIndex >= 0 ? panes[lastIndex].key : 'empty'; 42 | let path = { 43 | pathname: lastKey, 44 | }; 45 | this.props.dispatch(routerRedux.push(path)); 46 | this.props.dispatch({ 47 | type: 'tab/delete', 48 | payload: { 49 | findIndex, 50 | lastKey 51 | } 52 | }) 53 | } 54 | render() { 55 | const { tab } = this.props; 56 | const { panes, activeKey } = tab; 57 | return ( 58 |
59 | 69 | {panes.map(pane => )} 70 | 71 |
72 | ); 73 | } 74 | } 75 | 76 | export default TabNav; -------------------------------------------------------------------------------- /src/components/Authorized/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 3 | en-US: Authorized 4 | zh-CN: Authorized 5 | subtitle: 权限 6 | cols: 1 7 | order: 15 8 | --- 9 | 10 | 权限组件,通过比对现有权限与准入权限,决定相关元素的展示。 11 | 12 | ## API 13 | 14 | ### RenderAuthorized 15 | 16 | `RenderAuthorized: (currentAuthority: string | () => string) => Authorized` 17 | 18 | 权限组件默认 export RenderAuthorized 函数,它接收当前权限作为参数,返回一个权限对象,该对象提供以下几种使用方式。 19 | 20 | 21 | ### Authorized 22 | 23 | 最基础的权限控制。 24 | 25 | | 参数 | 说明 | 类型 | 默认值 | 26 | |----------|------------------------------------------|-------------|-------| 27 | | children | 正常渲染的元素,权限判断通过时展示 | ReactNode | - | 28 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean` | - | 29 | | noMatch | 权限异常渲染元素,权限判断不通过时展示 | ReactNode | - | 30 | 31 | ### Authorized.AuthorizedRoute 32 | 33 | | 参数 | 说明 | 类型 | 默认值 | 34 | |----------|------------------------------------------|-------------|-------| 35 | | authority | 准入权限/权限判断 | `string | array | Promise | (currentAuthority) => boolean` | - | 36 | | redirectPath | 权限异常时重定向的页面路由 | string | - | 37 | 38 | 其余参数与 `Route` 相同。 39 | 40 | ### Authorized.Secured 41 | 42 | 注解方式,`@Authorized.Secured(authority, error)` 43 | 44 | | 参数 | 说明 | 类型 | 默认值 | 45 | |----------|------------------------------------------|-------------|-------| 46 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean` | - | 47 | | error | 权限异常时渲染元素 | ReactNode | | 48 | 49 | ### Authorized.check 50 | 51 | 函数形式的 Authorized,用于某些不能被 HOC 包裹的组件。 `Authorized.check(authority, target, Exception)` 52 | 注意:传入一个 Promise 时,无论正确还是错误返回的都是一个 ReactClass。 53 | 54 | | 参数 | 说明 | 类型 | 默认值 | 55 | |----------|------------------------------------------|-------------|-------| 56 | | authority | 准入权限/权限判断 | `string | Promise | (currentAuthority) => boolean` | - | 57 | | target | 权限判断通过时渲染的元素 | ReactNode | - | 58 | | Exception | 权限异常时渲染元素 | ReactNode | - | 59 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Report/NonDrug.less: -------------------------------------------------------------------------------- 1 | .columnRank { 2 | display: flex; 3 | flex-wrap: wrap; 4 | flex-direction: column; 5 | height: 764px; 6 | } 7 | .wrapCard { 8 | display: inline-block; 9 | width: 50%; 10 | padding: 0 10px; 11 | margin-bottom: 20px; 12 | .countCard { 13 | background: #FFF8F6; 14 | border: 1px solid #FAE0DA; 15 | border-radius: 2px; 16 | :global { 17 | .ant-card-body:before, .ant-card-body:after { 18 | content: none; 19 | } 20 | } 21 | } 22 | 23 | } 24 | .dailyCard { 25 | .part1 { 26 | flex: 2; 27 | color: #333; 28 | padding-right: 20px; 29 | } 30 | .part2 { 31 | flex: 1; 32 | display: flex; 33 | // justify-content: center; 34 | align-items: center; 35 | padding-left: 20px; 36 | border-left: 1px solid #ccc; 37 | height: 22px; 38 | text-align: center; 39 | font-size: 24px; 40 | color: #333; 41 | } 42 | } 43 | .monthlyCard { 44 | .part1 { 45 | padding-left: 38px; 46 | color: #666; 47 | height: 42px; 48 | display: flex; 49 | align-items: center; 50 | // justify-content: flex-end; 51 | margin-bottom: 4px; 52 | } 53 | .part2 { 54 | display: flex; 55 | align-items: center; 56 | .left { 57 | flex: 1; 58 | font-size: 24px; 59 | text-align: right; 60 | padding-right: 7%; 61 | color: #333; 62 | } 63 | .right { 64 | width: 85px; 65 | text-align: right; 66 | padding-left: 5%; 67 | color: #4A4A4A; 68 | } 69 | } 70 | } 71 | .one { 72 | border-top: 30px solid #FECD01; 73 | border-left: 30px solid #FECD01; 74 | } 75 | .two { 76 | border-top: 30px solid #D8D8D8; 77 | border-left: 30px solid #D8D8D8; 78 | } 79 | .three { 80 | border-top: 30px solid #F0D49B; 81 | border-left: 30px solid #F0D49B; 82 | } 83 | .arrow { 84 | position: absolute; 85 | top: 0; 86 | left: 0; 87 | border-bottom: 30px solid transparent; 88 | border-right: 30px solid transparent; 89 | .num { 90 | position: absolute; 91 | top: -29px; 92 | left: -18px; 93 | color: #fff; 94 | font-size: 20px; 95 | } 96 | } 97 | .square { 98 | width: 46px; 99 | height: 30px; 100 | background: #FFC4B5; 101 | border-radius: 2px; 102 | position: absolute; 103 | top: 0; 104 | left: 0; 105 | line-height: 30px; 106 | text-align: center; 107 | color: #fff; 108 | font-size: 20px; 109 | } -------------------------------------------------------------------------------- /src/components/Result/demo/classic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Classic 4 | --- 5 | 6 | 典型结果页面。 7 | 8 | ````jsx 9 | import Result from 'ant-design-pro/lib/Result'; 10 | import { Button, Row, Col, Icon, Steps } from 'antd'; 11 | 12 | const { Step } = Steps; 13 | 14 | const desc1 = ( 15 |
16 |
17 | 曲丽丽 18 | 19 |
20 |
2016-12-12 12:32
21 |
22 | ); 23 | 24 | const desc2 = ( 25 |
26 |
27 | 周毛毛 28 | 29 |
30 | 31 |
32 | ); 33 | 34 | const extra = ( 35 |
36 |
37 | 项目名称 38 |
39 | 40 | 41 | 项目 ID: 42 | 23421 43 | 44 | 45 | 负责人: 46 | 曲丽丽 47 | 48 | 49 | 生效时间: 50 | 2016-12-12 ~ 2017-12-12 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 | ); 61 | 62 | const actions = ( 63 |
64 | 65 | 66 | 67 |
68 | ); 69 | 70 | ReactDOM.render( 71 | 79 | , mountNode); 80 | ```` 81 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .header { 4 | height: 64px; 5 | padding: 0 12px 0 0; 6 | background: #fff; 7 | box-shadow: 0 1px 4px rgba(0, 21, 41, .08); 8 | position: relative; 9 | } 10 | 11 | :global { 12 | .ant-layout { 13 | min-height: 100vh; 14 | overflow-x: hidden; 15 | } 16 | } 17 | 18 | .logo { 19 | height: 64px; 20 | line-height: 58px; 21 | vertical-align: top; 22 | display: inline-block; 23 | padding: 0 0 0 24px; 24 | cursor: pointer; 25 | font-size: 20px; 26 | img { 27 | display: inline-block; 28 | vertical-align: middle; 29 | } 30 | } 31 | 32 | .menu { 33 | :global(.anticon) { 34 | margin-right: 8px; 35 | } 36 | :global(.ant-dropdown-menu-item) { 37 | width: 160px; 38 | } 39 | } 40 | 41 | i.trigger { 42 | font-size: 20px; 43 | line-height: 64px; 44 | cursor: pointer; 45 | transition: all .3s, padding 0s; 46 | padding: 0 24px; 47 | &:hover { 48 | background: @primary-1; 49 | } 50 | } 51 | 52 | .right { 53 | float: right; 54 | height: 100%; 55 | .action { 56 | cursor: pointer; 57 | padding: 0 12px; 58 | display: inline-block; 59 | transition: all .3s; 60 | height: 100%; 61 | > i { 62 | font-size: 16px; 63 | vertical-align: middle; 64 | color: @text-color; 65 | } 66 | &:hover, 67 | &:global(.ant-popover-open) { 68 | background: @primary-1; 69 | } 70 | } 71 | .search { 72 | padding: 0; 73 | margin: 0 12px; 74 | &:hover { 75 | background: transparent; 76 | } 77 | } 78 | .account { 79 | .avatar { 80 | margin: 20px 8px 20px 0; 81 | color: @primary-color; 82 | background: rgba(255, 255, 255, .85); 83 | vertical-align: middle; 84 | } 85 | } 86 | } 87 | 88 | @media only screen and (max-width: @screen-md) { 89 | .header { 90 | :global(.ant-divider-vertical) { 91 | vertical-align: unset; 92 | } 93 | .name { 94 | display: none; 95 | } 96 | i.trigger { 97 | padding: 0 12px; 98 | } 99 | .logo { 100 | padding-right: 12px; 101 | position: relative; 102 | } 103 | .right { 104 | position: absolute; 105 | right: 12px; 106 | top: 0; 107 | background: #fff; 108 | .account { 109 | .avatar { 110 | margin-right: 0; 111 | } 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/HeaderSearch/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Input, Icon, AutoComplete } from 'antd'; 4 | import classNames from 'classnames'; 5 | import styles from './index.less'; 6 | 7 | export default class HeaderSearch extends PureComponent { 8 | static defaultProps = { 9 | defaultActiveFirstOption: false, 10 | onPressEnter: () => {}, 11 | onSearch: () => {}, 12 | className: '', 13 | placeholder: '', 14 | dataSource: [], 15 | }; 16 | static propTypes = { 17 | className: PropTypes.string, 18 | placeholder: PropTypes.string, 19 | onSearch: PropTypes.func, 20 | onPressEnter: PropTypes.func, 21 | defaultActiveFirstOption: PropTypes.bool, 22 | dataSource: PropTypes.array, 23 | }; 24 | state = { 25 | searchMode: false, 26 | value: '', 27 | }; 28 | componentWillUnmount() { 29 | clearTimeout(this.timeout); 30 | } 31 | onKeyDown = (e) => { 32 | if (e.key === 'Enter') { 33 | this.timeout = setTimeout(() => { 34 | this.props.onPressEnter(this.state.value); // Fix duplicate onPressEnter 35 | }, 0); 36 | } 37 | } 38 | onChange = (value) => { 39 | this.setState({ value }); 40 | if (this.props.onChange) { 41 | this.props.onChange(); 42 | } 43 | } 44 | enterSearchMode = () => { 45 | this.setState({ searchMode: true }, () => { 46 | if (this.state.searchMode) { 47 | this.input.focus(); 48 | } 49 | }); 50 | } 51 | leaveSearchMode = () => { 52 | this.setState({ 53 | searchMode: false, 54 | value: '', 55 | }); 56 | } 57 | render() { 58 | const { className, placeholder, ...restProps } = this.props; 59 | const inputClass = classNames(styles.input, { 60 | [styles.show]: this.state.searchMode, 61 | }); 62 | return ( 63 | 67 | 68 | 75 | { this.input = node; }} 78 | onKeyDown={this.onKeyDown} 79 | onBlur={this.leaveSearchMode} 80 | /> 81 | 82 | 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/components/PageHeader/demo/standard.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | title: Standard 4 | --- 5 | 6 | 标准页头。 7 | 8 | ````jsx 9 | import PageHeader from 'ant-design-pro/lib/PageHeader'; 10 | import DescriptionList from 'ant-design-pro/lib/DescriptionList'; 11 | import { Button, Menu, Dropdown, Icon, Row, Col } from 'antd'; 12 | 13 | const { Description } = DescriptionList; 14 | const ButtonGroup = Button.Group; 15 | 16 | const description = ( 17 | 18 | 曲丽丽 19 | XX 服务 20 | 2017-07-07 21 | 12421 22 | 23 | ); 24 | 25 | const menu = ( 26 | 27 | 选项一 28 | 选项二 29 | 选项三 30 | 31 | ); 32 | 33 | const action = ( 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | 46 | const extra = ( 47 | 48 | 49 |
状态
50 |
待审批
51 | 52 | 53 |
订单金额
54 |
¥ 568.08
55 | 56 |
57 | ); 58 | 59 | const breadcrumbList = [{ 60 | title: '一级菜单', 61 | href: '/', 62 | }, { 63 | title: '二级菜单', 64 | href: '/', 65 | }, { 66 | title: '三级菜单', 67 | }]; 68 | 69 | const tabList = [{ 70 | key: 'detail', 71 | tab: '详情', 72 | }, { 73 | key: 'rule', 74 | tab: '规则', 75 | }]; 76 | 77 | function onTabChange(key) { 78 | console.log(key); 79 | } 80 | 81 | ReactDOM.render( 82 |
83 | } 86 | action={action} 87 | content={description} 88 | extraContent={extra} 89 | breadcrumbList={breadcrumbList} 90 | tabList={tabList} 91 | tabActiveKey="detail" 92 | onTabChange={onTabChange} 93 | /> 94 |
95 | , mountNode); 96 | ```` 97 | 98 | 103 | -------------------------------------------------------------------------------- /src/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Menu, Icon, Spin, Tag, Dropdown, Avatar, Divider, Tooltip } from 'antd'; 3 | import moment from 'moment'; 4 | import groupBy from 'lodash/groupBy'; 5 | import Debounce from 'lodash-decorators/debounce'; 6 | import { Link } from 'dva/router'; 7 | import NoticeIcon from '../NoticeIcon'; 8 | import HeaderSearch from '../HeaderSearch'; 9 | import styles from './index.less'; 10 | 11 | export default class GlobalHeader extends PureComponent { 12 | state = { 13 | token: window._csrf ? window._csrf.token : 'token' 14 | } 15 | componentWillUnmount() { 16 | this.triggerResizeEvent.cancel(); 17 | } 18 | toggle = () => { 19 | const { collapsed, onCollapse } = this.props; 20 | onCollapse(!collapsed); 21 | this.triggerResizeEvent(); 22 | } 23 | @Debounce(600) 24 | triggerResizeEvent() { // eslint-disable-line 25 | const event = document.createEvent('HTMLEvents'); 26 | event.initEvent('resize', true, false); 27 | window.dispatchEvent(event); 28 | } 29 | logoOut = () => { 30 | document.querySelector('#logoutForm').submit(); 31 | } 32 | render() { 33 | const { 34 | currentUser, collapsed, fetchingNotices, isMobile, logo, 35 | onNoticeVisibleChange, onMenuClick, onNoticeClear, 36 | } = this.props; 37 | const { token } = this.state 38 | const menu = ( 39 | 40 | 退出登录 41 | 42 | ); 43 | return ( 44 |
45 | {isMobile && ( 46 | [ 47 | ( 48 | 49 | logo 50 | 51 | ), 52 | , 53 | ] 54 | )} 55 | 60 |
61 | {currentUser ? ( 62 | 63 | 64 | {currentUser.name} 65 | 66 | 67 | ) : } 68 | 69 | logo 70 | 71 | 运营数据中心 75 | 76 |
77 |
78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/PageHeader/index.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .pageHeader { 4 | background: @component-background; 5 | padding: 16px 32px 0 32px; 6 | border-bottom: @border-width-base @border-style-base @border-color-split; 7 | 8 | .detail { 9 | display: flex; 10 | } 11 | 12 | .row { 13 | display: flex; 14 | } 15 | 16 | .breadcrumb { 17 | margin-bottom: 16px; 18 | } 19 | 20 | .tabs { 21 | margin: 0 0 -17px -8px; 22 | 23 | :global { 24 | .ant-tabs-bar { 25 | border-bottom: @border-width-base @border-style-base @border-color-split; 26 | } 27 | } 28 | } 29 | 30 | .logo { 31 | flex: 0 1 auto; 32 | margin-right: 16px; 33 | padding-top: 1px; 34 | > img { 35 | width: 28px; 36 | height: 28px; 37 | border-radius: @border-radius-base; 38 | display: block; 39 | } 40 | } 41 | 42 | .title { 43 | font-size: 20px; 44 | font-weight: 500; 45 | color: @heading-color; 46 | } 47 | 48 | .action { 49 | margin-left: 56px; 50 | min-width: 266px; 51 | 52 | :global { 53 | .ant-btn-group:not(:last-child), 54 | .ant-btn:not(:last-child) { 55 | margin-right: 8px; 56 | } 57 | 58 | .ant-btn-group > .ant-btn { 59 | margin-right: 0; 60 | } 61 | } 62 | } 63 | 64 | .title, .action, .content, .extraContent, .main { 65 | flex: auto; 66 | } 67 | 68 | .title, .action { 69 | margin-bottom: 16px; 70 | } 71 | 72 | .logo, .content, .extraContent { 73 | margin-bottom: 16px; 74 | } 75 | 76 | .action, .extraContent { 77 | text-align: right; 78 | } 79 | 80 | .extraContent { 81 | margin-left: 88px; 82 | min-width: 242px; 83 | } 84 | } 85 | 86 | @media screen and (max-width: @screen-xl) { 87 | .pageHeader { 88 | .extraContent { 89 | margin-left: 44px; 90 | } 91 | } 92 | } 93 | 94 | @media screen and (max-width: @screen-lg) { 95 | .pageHeader { 96 | .extraContent { 97 | margin-left: 20px; 98 | } 99 | } 100 | } 101 | 102 | @media screen and (max-width: @screen-md) { 103 | .pageHeader { 104 | .row { 105 | display: block; 106 | } 107 | 108 | .action, .extraContent { 109 | margin-left: 0; 110 | text-align: left; 111 | } 112 | } 113 | } 114 | 115 | @media screen and (max-width: @screen-sm) { 116 | .pageHeader { 117 | .detail { 118 | display: block; 119 | } 120 | } 121 | } 122 | 123 | @media screen and (max-width: @screen-xs) { 124 | .pageHeader { 125 | .action { 126 | :global { 127 | .ant-btn-group, .ant-btn { 128 | display: block; 129 | margin-bottom: 8px; 130 | } 131 | .ant-btn-group > .ant-btn { 132 | display: inline-block; 133 | margin-bottom: 0; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/components/Progress/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { formatPercent } from '../../utils/utils'; 3 | 4 | import styles from './index.less'; 5 | 6 | const Progress = ({ 7 | width = 0, 8 | vertical= false, 9 | percent = 0, 10 | radius = 0, 11 | color = '#3AC9A8', 12 | background = false, 13 | textSize = 14 14 | }) => { 15 | var progressStyle = { 16 | height: 0, 17 | width: 0 18 | } 19 | //有背景且水平 20 | if(background && !vertical) { 21 | var progressStyle = { 22 | width: '95%', 23 | alignItems: 'center' 24 | } 25 | var progressOuter = { 26 | background, 27 | borderRadius: radius, 28 | width: '100%', 29 | height: width, 30 | marginRight: 10, 31 | flex: 1 32 | } 33 | var progressInner = { 34 | height: width, 35 | width: formatPercent(percent), 36 | borderRadius: radius, 37 | background: color, 38 | } 39 | } 40 | //没有背景且水平 41 | if(!background && !vertical) { 42 | var progressStyle = { 43 | width: '100%' 44 | } 45 | var progressOuter = { 46 | width: formatPercent(percent), 47 | marginRight: 10, 48 | } 49 | var progressInner = { 50 | width: '100%', 51 | height: width, 52 | borderRadius: radius, 53 | background: color, 54 | } 55 | } 56 | //有背景且垂直 57 | if(background && vertical) { 58 | var progressStyle = { 59 | height: '100%', 60 | flexDirection: 'column-reverse' 61 | } 62 | var progressOuter = { 63 | background, 64 | borderRadius: radius, 65 | height: '100%', 66 | width, 67 | marginTop: 5, 68 | display: 'flex', 69 | flexDirection: 'column-reverse', 70 | } 71 | var progressInner = { 72 | width, 73 | height: formatPercent(percent), 74 | display: 'inline-block', 75 | borderRadius: radius, 76 | background: color, 77 | } 78 | } 79 | //无背景且垂直 80 | if(!background && vertical) { 81 | var progressStyle = { 82 | height: '100%', 83 | flexDirection: 'column-reverse', 84 | alignItems: 'center' 85 | } 86 | var progressOuter = { 87 | borderRadius: radius, 88 | height: formatPercent(percent), 89 | width, 90 | marginTop: 5, 91 | display: 'flex', 92 | flexDirection: 'column-reverse', 93 | } 94 | var progressInner = { 95 | width, 96 | height: '100%', 97 | display: 'inline-block', 98 | borderRadius: radius, 99 | background: color, 100 | } 101 | } 102 | return ( 103 |
104 |
105 |
106 |
107 |
108 | {formatPercent(percent)} 109 |
110 |
111 | ) 112 | } 113 | 114 | export default Progress; -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'dva/fetch'; 2 | import { notification } from 'antd'; 3 | import { routerRedux } from 'dva/router'; 4 | import store from '../index'; 5 | 6 | const codeMessage = { 7 | 200: '服务器成功返回请求的数据。', 8 | 201: '新建或修改数据成功。', 9 | 202: '一个请求已经进入后台排队(异步任务)。', 10 | 204: '删除数据成功。', 11 | 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。', 12 | 401: '用户没有权限(令牌、用户名、密码错误)。', 13 | 403: '用户得到授权,但是访问是被禁止的。', 14 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。', 15 | 406: '请求的格式不可得。', 16 | 410: '请求的资源被永久删除,且不会再得到的。', 17 | 422: '当创建一个对象时,发生一个验证错误。', 18 | 500: '服务器发生错误,请检查服务器。', 19 | 502: '网关错误。', 20 | 503: '服务不可用,服务器暂时过载或维护。', 21 | 504: '网关超时。', 22 | }; 23 | function checkStatus(response) { 24 | if (response.status >= 200 && response.status < 300) { 25 | return response; 26 | } 27 | const errortext = codeMessage[response.status] || response.statusText; 28 | notification.error({ 29 | message: `请求错误 ${response.status}: ${response.url}`, 30 | description: errortext, 31 | }); 32 | const error = new Error(errortext); 33 | error.name = response.status; 34 | error.response = response; 35 | throw error; 36 | } 37 | 38 | /** 39 | * Requests a URL, returning a promise. 40 | * 41 | * @param {string} url The URL we want to request 42 | * @param {object} [options] The options we want to pass to "fetch" 43 | * @return {object} An object containing either "data" or "err" 44 | */ 45 | export default function request(url, options) { 46 | const defaultOptions = { 47 | credentials: 'include', 48 | }; 49 | const newOptions = { ...defaultOptions, ...options }; 50 | if (newOptions.method === 'POST' || newOptions.method === 'PUT') { 51 | if (!(newOptions.body instanceof FormData)) { 52 | newOptions.headers = { 53 | Accept: 'application/json', 54 | 'Content-Type': 'application/json; charset=utf-8', 55 | ...newOptions.headers, 56 | }; 57 | newOptions.body = JSON.stringify(newOptions.body); 58 | } else { 59 | // newOptions.body is FormData 60 | newOptions.headers = { 61 | Accept: 'application/json', 62 | 'Content-Type': 'multipart/form-data', 63 | ...newOptions.headers, 64 | }; 65 | } 66 | } 67 | 68 | return fetch(url, newOptions) 69 | .then(checkStatus) 70 | .then((response) => { 71 | if (newOptions.method === 'DELETE' || response.status === 204) { 72 | return response.text(); 73 | } 74 | return response.json(); 75 | }) 76 | .catch((e) => { 77 | const { dispatch } = store; 78 | const status = e.name; 79 | if (status === 401) { 80 | dispatch({ 81 | type: 'login/logout', 82 | }); 83 | return; 84 | } 85 | if (status === 403) { 86 | dispatch(routerRedux.push('/exception/403')); 87 | return; 88 | } 89 | if (status <= 504 && status >= 500) { 90 | dispatch(routerRedux.push('/exception/500')); 91 | return; 92 | } 93 | if (status >= 404 && status < 422) { 94 | dispatch(routerRedux.push('/exception/404')); 95 | } 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "operation-data-center", 3 | "version": "1.1.0", 4 | "description": "An out-of-box UI solution for enterprise applications", 5 | "private": true, 6 | "scripts": { 7 | "mock": "cross-env DISABLE_ESLINT=true roadhog dev", 8 | "start": "cross-env NO_PROXY=true DISABLE_ESLINT=true roadhog dev", 9 | "build": "cross-env DISABLE_ESLINT=true roadhog build", 10 | "site": "roadhog-api-doc static && gh-pages -d dist", 11 | "analyze": "cross-env ANALYZE=true roadhog build", 12 | "lint:style": "stylelint \"src/**/*.less\" --syntax less", 13 | "lint": "eslint --ext .js src mock tests && npm run lint:style", 14 | "lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style", 15 | "lint-staged": "lint-staged", 16 | "lint-staged:js": "eslint --ext .js", 17 | "test": "roadhog test", 18 | "test:component": "roadhog test ./src/components", 19 | "test:all": "node ./tests/run-tests.js" 20 | }, 21 | "dependencies": { 22 | "@antv/data-set": "^0.8.0", 23 | "@babel/polyfill": "^7.0.0-beta.36", 24 | "antd": "^3.1.0", 25 | "babel-runtime": "^6.9.2", 26 | "bizcharts": "^3.1.3-beta.1", 27 | "bizcharts-plugin-slider": "^2.0.1", 28 | "classnames": "^2.2.5", 29 | "dva": "^2.1.0", 30 | "dva-loading": "^1.0.4", 31 | "enquire-js": "^0.1.1", 32 | "lodash": "^4.17.4", 33 | "lodash-decorators": "^4.4.1", 34 | "moment": "^2.19.1", 35 | "numeral": "^2.0.6", 36 | "omit.js": "^1.0.0", 37 | "path-to-regexp": "^2.1.0", 38 | "prop-types": "^15.5.10", 39 | "qs": "^6.5.0", 40 | "rc-drawer-menu": "^0.5.0", 41 | "react": "^16.2.0", 42 | "react-container-query": "^0.9.1", 43 | "react-document-title": "^2.0.3", 44 | "react-dom": "^16.2.0", 45 | "react-fittext": "^1.0.0", 46 | "rollbar": "^2.3.4", 47 | "url-polyfill": "^1.0.10" 48 | }, 49 | "devDependencies": { 50 | "babel-eslint": "^8.1.2", 51 | "babel-plugin-dva-hmr": "^0.4.1", 52 | "babel-plugin-import": "^1.6.3", 53 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 54 | "cross-env": "^5.1.1", 55 | "cross-port-killer": "^1.0.1", 56 | "enzyme": "^3.1.0", 57 | "eslint": "^4.14.0", 58 | "eslint-config-airbnb": "^16.0.0", 59 | "eslint-plugin-babel": "^4.0.0", 60 | "eslint-plugin-compat": "^2.1.0", 61 | "eslint-plugin-import": "^2.8.0", 62 | "eslint-plugin-jsx-a11y": "^6.0.3", 63 | "eslint-plugin-markdown": "^1.0.0-beta.6", 64 | "eslint-plugin-react": "^7.0.1", 65 | "gh-pages": "^1.0.0", 66 | "husky": "^0.14.3", 67 | "lint-staged": "^6.0.0", 68 | "mockjs": "^1.0.1-beta3", 69 | "pro-download": "^1.0.1", 70 | "redbox-react": "^1.5.0", 71 | "regenerator-runtime": "^0.11.1", 72 | "roadhog": "^2.1.0", 73 | "roadhog-api-doc": "^0.3.4", 74 | "stylelint": "^8.4.0", 75 | "stylelint-config-standard": "^18.0.0" 76 | }, 77 | "optionalDependencies": { 78 | "puppeteer": "^1.1.1" 79 | }, 80 | "lint-staged": { 81 | "**/*.{js,jsx}": "lint-staged:js", 82 | "**/*.less": "stylelint --syntax less" 83 | }, 84 | "browserslist": [ 85 | "> 1%", 86 | "last 2 versions", 87 | "not ie <= 10" 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /src/components/NoticeIcon/index.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import { Popover, Icon, Tabs, Badge, Spin } from 'antd'; 3 | import classNames from 'classnames'; 4 | import List from './NoticeList'; 5 | import styles from './index.less'; 6 | 7 | const { TabPane } = Tabs; 8 | 9 | export default class NoticeIcon extends PureComponent { 10 | static defaultProps = { 11 | onItemClick: () => {}, 12 | onPopupVisibleChange: () => {}, 13 | onTabChange: () => {}, 14 | onClear: () => {}, 15 | loading: false, 16 | locale: { 17 | emptyText: '暂无数据', 18 | clear: '清空', 19 | }, 20 | emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg', 21 | }; 22 | static Tab = TabPane; 23 | constructor(props) { 24 | super(props); 25 | this.state = {}; 26 | if (props.children && props.children[0]) { 27 | this.state.tabType = props.children[0].props.title; 28 | } 29 | } 30 | onItemClick = (item, tabProps) => { 31 | const { onItemClick } = this.props; 32 | onItemClick(item, tabProps); 33 | } 34 | onTabChange = (tabType) => { 35 | this.setState({ tabType }); 36 | this.props.onTabChange(tabType); 37 | } 38 | getNotificationBox() { 39 | const { children, loading, locale } = this.props; 40 | if (!children) { 41 | return null; 42 | } 43 | const panes = React.Children.map(children, (child) => { 44 | const title = child.props.list && child.props.list.length > 0 45 | ? `${child.props.title} (${child.props.list.length})` : child.props.title; 46 | return ( 47 | 48 | this.onItemClick(item, child.props)} 52 | onClear={() => this.props.onClear(child.props.title)} 53 | title={child.props.title} 54 | locale={locale} 55 | /> 56 | 57 | ); 58 | }); 59 | return ( 60 | 61 | 62 | {panes} 63 | 64 | 65 | ); 66 | } 67 | render() { 68 | const { className, count, popupAlign, onPopupVisibleChange } = this.props; 69 | const noticeButtonClass = classNames(className, styles.noticeButton); 70 | const notificationBox = this.getNotificationBox(); 71 | const trigger = ( 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | if (!notificationBox) { 79 | return trigger; 80 | } 81 | const popoverProps = {}; 82 | if ('popupVisible' in this.props) { 83 | popoverProps.visible = this.props.popupVisible; 84 | } 85 | return ( 86 | 96 | {trigger} 97 | 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /mock/Report/dynamic.js: -------------------------------------------------------------------------------- 1 | const mock_dynamic = { 2 | "result": { 3 | //总门急诊人次 4 | "totalEmergencyVisitsModule": { 5 | "totalEmergencyVisitsMom": 0.1653, 6 | "totalEmergencyVisits": 52553, 7 | "totalEmergencyVisitsYoy": -0.1404 8 | }, 9 | //中医处方数量 10 | "chinaPrescriptionsModule": { 11 | "chinaPrescriptionsMom": 0.338, 12 | "chinaPrescriptions": 19897, 13 | "chinaPrescriptionsYoy": 0.069 14 | }, 15 | //非药物中治率(门诊) 16 | "nondrugTreatmentRateModule": { 17 | "nondrugTreatmentRate": 0.0321, 18 | "nondrugTreatmentRateYoy": 0.0073, 19 | "nondrugTreatmentRateMom": 0.006 20 | }, 21 | //住院 22 | "inHospitalModule": [ 23 | { 24 | "recover": 1968, 25 | "recoverYoy": 0.105, 26 | "recoverMom": 0.2495 27 | }, 28 | { 29 | "admissionsYoy": 0.1275, 30 | "admissions": 1972, 31 | "admissionsMom": 0.3406 32 | }, 33 | { 34 | "inHospitalMom": 0.2862, 35 | "inHospital": 25772, 36 | "inHospitalYoy": -0.0103 37 | } 38 | ], 39 | //总收入 40 | "totalIncomeModule": [ 41 | { 42 | "inHospitalIncome": 37621717.1, 43 | "inHospitalIncomeMom": 0.3073, 44 | "inHospitalIncomeYoy": 0.1069 45 | }, 46 | { 47 | "drugIncomeMom": 0.2381, 48 | "drugIncomeYoy": 0.0855, 49 | "drugIncome": 23913973.43 50 | }, 51 | { 52 | "outpatientEmergencyIncomeYoy": 0.0312, 53 | "outpatientEmergencyIncomeMom": 0.3109, 54 | "outpatientEmergencyIncome": 16997740.74 55 | }, 56 | { 57 | "totalIncome": 54619457.84, 58 | "totalIncomeMom": 0.3084, 59 | "totalIncomeYoy": 0.0822 60 | }, 61 | { 62 | "nonDrugIncomeYoy": 0.0796, 63 | "nonDrugIncomeMom": 0.369, 64 | "nonDrugIncome": 30705484.41 65 | } 66 | ], 67 | //患者负担 68 | "patientBurdenModule": [ 69 | { 70 | "perOutpatientTreatFees": 73.76, 71 | "perOutpatientTreatFeesYoy": 0.074, 72 | "perOutpatientTreatFeesMom": 0.0186 73 | }, 74 | { 75 | "perMedicalExaminationFeesMom": -0.6146, 76 | "perMedicalExaminationFees": 52.91, 77 | "perMedicalExaminationFeesYoy": -0.5761 78 | }, 79 | { 80 | "perPrescriptionFeesMom": -0.0065, 81 | "perPrescriptionFees": 202.14, 82 | "perPrescriptionFeesYoy": -0.0044 83 | }, 84 | { 85 | "perDaysOfRecoverYoy": -0.104, 86 | "perDaysOfRecover": 13.1, 87 | "perDaysOfRecoverMom": 0.0299 88 | }, 89 | { 90 | "perRecoverDrugFeesYoy": -0.0816, 91 | "perRecoverDrugFees": 17930.96, 92 | "perRecoverDrugFeesMom": -0.0794 93 | }, 94 | { 95 | "perOutpatientEmergencyFeesYoy": 0.1996, 96 | "perOutpatientEmergencyFees": 323.44, 97 | "perOutpatientEmergencyFeesMom": 0.125 98 | }, 99 | { 100 | "perOutpatientDrugFeesYoy": 0.0538, 101 | "perOutpatientDrugFees": 195.81, 102 | "perOutpatientDrugFeesMom": -0.0884 103 | }, 104 | { 105 | "perInHospitalFeesMom": -0.1094, 106 | "perInHospitalFeesYoy": -0.0672, 107 | "perInHospitalFees": 6424.63 108 | } 109 | ] 110 | } 111 | } 112 | 113 | export default mock_dynamic; -------------------------------------------------------------------------------- /src/components/Login/LoginItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Form, Button, Row, Col } from 'antd'; 4 | import omit from 'omit.js'; 5 | import styles from './index.less'; 6 | import map from './map'; 7 | 8 | const FormItem = Form.Item; 9 | 10 | function generator({ defaultProps, defaultRules, type }) { 11 | return (WrappedComponent) => { 12 | return class BasicComponent extends Component { 13 | static contextTypes = { 14 | form: PropTypes.object, 15 | updateActive: PropTypes.func, 16 | }; 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | count: 0, 21 | }; 22 | } 23 | componentDidMount() { 24 | if (this.context.updateActive) { 25 | this.context.updateActive(this.props.name); 26 | } 27 | } 28 | componentWillUnmount() { 29 | clearInterval(this.interval); 30 | } 31 | onGetCaptcha = () => { 32 | let count = 59; 33 | this.setState({ count }); 34 | if (this.props.onGetCaptcha) { 35 | this.props.onGetCaptcha(); 36 | } 37 | this.interval = setInterval(() => { 38 | count -= 1; 39 | this.setState({ count }); 40 | if (count === 0) { 41 | clearInterval(this.interval); 42 | } 43 | }, 1000); 44 | } 45 | render() { 46 | const { getFieldDecorator } = this.context.form; 47 | const options = {}; 48 | let otherProps = {}; 49 | const { onChange, defaultValue, rules, name, ...restProps } = this.props; 50 | const { count } = this.state; 51 | options.rules = rules || defaultRules; 52 | if (onChange) { 53 | options.onChange = onChange; 54 | } 55 | if (defaultValue) { 56 | options.initialValue = defaultValue; 57 | } 58 | otherProps = restProps || otherProps; 59 | if (type === 'Captcha') { 60 | const inputProps = omit(otherProps, ['onGetCaptcha']); 61 | return ( 62 | 63 | 64 | 65 | {getFieldDecorator(name, options)( 66 | 67 | )} 68 | 69 | 70 | 78 | 79 | 80 | 81 | ); 82 | } 83 | return ( 84 | 85 | {getFieldDecorator(name, options)( 86 | 87 | )} 88 | 89 | ); 90 | } 91 | }; 92 | }; 93 | } 94 | 95 | const LoginItem = {}; 96 | Object.keys(map).forEach((item) => { 97 | LoginItem[item] = generator({ 98 | defaultProps: map[item].props, 99 | defaultRules: map[item].rules, 100 | type: item, 101 | })(map[item].component); 102 | }); 103 | 104 | export default LoginItem; 105 | -------------------------------------------------------------------------------- /src/components/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Form, Tabs } from 'antd'; 4 | import classNames from 'classnames'; 5 | import LoginItem from './LoginItem'; 6 | import LoginTab from './LoginTab'; 7 | import LoginSubmit from './LoginSubmit'; 8 | import styles from './index.less'; 9 | 10 | @Form.create() 11 | class Login extends Component { 12 | static defaultProps = { 13 | className: '', 14 | defaultActiveKey: '', 15 | onTabChange: () => {}, 16 | onSubmit: () => {}, 17 | }; 18 | static propTypes = { 19 | className: PropTypes.string, 20 | defaultActiveKey: PropTypes.string, 21 | onTabChange: PropTypes.func, 22 | onSubmit: PropTypes.func, 23 | }; 24 | static childContextTypes = { 25 | tabUtil: PropTypes.object, 26 | form: PropTypes.object, 27 | updateActive: PropTypes.func, 28 | }; 29 | state = { 30 | type: this.props.defaultActiveKey, 31 | tabs: [], 32 | active: {}, 33 | }; 34 | getChildContext() { 35 | return { 36 | tabUtil: { 37 | addTab: (id) => { 38 | this.setState({ 39 | tabs: [...this.state.tabs, id], 40 | }); 41 | }, 42 | removeTab: (id) => { 43 | this.setState({ 44 | tabs: this.state.tabs.filter(currentId => currentId !== id), 45 | }); 46 | }, 47 | }, 48 | form: this.props.form, 49 | updateActive: (activeItem) => { 50 | const { type, active } = this.state; 51 | if (active[type]) { 52 | active[type].push(activeItem); 53 | } else { 54 | active[type] = [activeItem]; 55 | } 56 | this.setState({ 57 | active, 58 | }); 59 | }, 60 | }; 61 | } 62 | onSwitch = (type) => { 63 | this.setState({ 64 | type, 65 | }); 66 | this.props.onTabChange(type); 67 | } 68 | handleSubmit = (e) => { 69 | e.preventDefault(); 70 | const { active, type } = this.state; 71 | const activeFileds = active[type]; 72 | this.props.form.validateFields(activeFileds, { force: true }, 73 | (err, values) => { 74 | this.props.onSubmit(err, values); 75 | } 76 | ); 77 | } 78 | render() { 79 | const { className, children } = this.props; 80 | const { type, tabs } = this.state; 81 | const TabChildren = []; 82 | const otherChildren = []; 83 | React.Children.forEach(children, (item) => { 84 | if (!item) { 85 | return; 86 | } 87 | // eslint-disable-next-line 88 | if (item.type.__ANT_PRO_LOGIN_TAB) { 89 | TabChildren.push(item); 90 | } else { 91 | otherChildren.push(item); 92 | } 93 | }); 94 | return ( 95 |
96 |
97 | { 98 | tabs.length ? ( 99 |
100 | 106 | {TabChildren} 107 | 108 | {otherChildren} 109 |
110 | ) : [...children] 111 | } 112 |
113 |
114 | ); 115 | } 116 | } 117 | 118 | Login.Tab = LoginTab; 119 | Login.Submit = LoginSubmit; 120 | Object.keys(LoginItem).forEach((item) => { 121 | Login[item] = LoginItem[item]; 122 | }); 123 | 124 | export default Login; 125 | -------------------------------------------------------------------------------- /src/components/Login/demo/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 0 3 | title: 4 | zh-CN: 标准登录 5 | en-US: Standard Login 6 | --- 7 | 8 | Support login with account and mobile number. 9 | 10 | ````jsx 11 | import Login from 'ant-design-pro/lib/Login'; 12 | import { Alert, Checkbox } from 'antd'; 13 | 14 | const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login; 15 | 16 | class LoginDemo extends React.Component { 17 | state = { 18 | notice: '', 19 | type: 'tab2', 20 | autoLogin: true, 21 | } 22 | onSubmit = (err, values) => { 23 | console.log('value collected ->', { ...values, autoLogin: this.state.autoLogin }); 24 | if (this.state.type === 'tab1') { 25 | this.setState({ 26 | notice: '', 27 | }, () => { 28 | if (!err && (values.username !== 'admin' || values.password !== '888888')) { 29 | setTimeout(() => { 30 | this.setState({ 31 | notice: 'The combination of username and password is incorrect!', 32 | }); 33 | }, 500); 34 | } 35 | }); 36 | } 37 | } 38 | onTabChange = (key) => { 39 | this.setState({ 40 | type: key, 41 | }); 42 | } 43 | changeAutoLogin = (e) => { 44 | this.setState({ 45 | autoLogin: e.target.checked, 46 | }); 47 | } 48 | render() { 49 | return ( 50 | 55 | 56 | { 57 | this.state.notice && 58 | 59 | } 60 | 61 | 62 | 63 | 64 | 65 | console.log('Get captcha!')} name="captcha" /> 66 | 67 |
68 | Keep me logged in 69 | Forgot password 70 |
71 | Login 72 |
73 | Other login methods 74 | 75 | 76 | 77 | Register 78 |
79 |
80 | ); 81 | } 82 | } 83 | 84 | ReactDOM.render(, mountNode); 85 | ```` 86 | 87 | 116 | -------------------------------------------------------------------------------- /src/components/Charts/LineOrArea/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Chart, Tooltip, Geom, Legend, Axis } from 'bizcharts'; 3 | import DataSet from '@antv/data-set'; 4 | import autoHeight from '../autoHeight'; 5 | import styles from './index.less'; 6 | 7 | @autoHeight() 8 | export default class LineOrArea extends React.Component { 9 | render() { 10 | const { 11 | title, 12 | area = false, 13 | point = false, 14 | line = false, 15 | bar = false, 16 | barWidth = 10, 17 | height = 400, 18 | titleMap = {}, 19 | borderWidth = 1, 20 | data = [], 21 | padding, 22 | lineColor, 23 | areaColor, 24 | lineWidth = 1, 25 | pointSize = 4, 26 | opacity = [1], 27 | legend = true, 28 | type = 'line', 29 | shape, 30 | xAxisRotate, 31 | scale, 32 | GeomConfig, 33 | LegendSetting 34 | } = this.props; 35 | 36 | const position = `${titleMap.x}*value` 37 | const ds = new DataSet(); 38 | const dv = ds.createView(); 39 | dv.source(data) 40 | //重命名 41 | .transform({ 42 | type: 'rename', 43 | map: titleMap.filedsMap, 44 | }) 45 | //将fileds中对应字段的内容转换成key:value的形式 46 | .transform({ 47 | type: 'fold', 48 | fields: Object.values(titleMap.filedsMap), // 展开字段集 49 | key: 'key', // key字段 50 | value: 'value', // value字段 51 | }); 52 | const xlabel = { 53 | offset: 15, // 数值,设置坐标轴文本 label 距离坐标轴线的距离 54 | // 设置文本的显示样式,还可以是个回调函数,回调函数的参数为该坐标轴对应字段的数值 55 | textStyle: { 56 | textAlign: 'start', // 文本对齐方向,可取值为: start center end 57 | fill: '#333', // 文本的颜色 58 | fontSize: '12', // 文本大小 59 | //fontWeight: 'bold', // 文本粗细 60 | rotate: xAxisRotate, 61 | //textBaseline: 'middle' // 文本基准线,可取 top middle bottom,默认为middle 62 | }, 63 | autoRotate: xAxisRotate ? false : true, // 文本是否需要自动旋转,默认为 true 64 | } 65 | //设置刻度线样式 66 | const axisLine = { 67 | stroke: '#e8e8e8', 68 | lineWidth: 1 69 | }; 70 | 71 | return ( 72 |
73 |
74 | 75 | 81 | 86 | 87 | {legend && ()} 88 | {area && ( 89 | 98 | )} 99 | {line && ( 100 | { 107 | return { 108 | name: key, 109 | value: value 110 | } 111 | }]} 112 | {...GeomConfig?GeomConfig.line:{}} 113 | /> 114 | )} 115 | {point && ( 116 | 125 | )} 126 | 127 |
128 |
129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Emphasis.less: -------------------------------------------------------------------------------- 1 | @import "~antd/lib/style/themes/default.less"; 2 | 3 | .dailyConsumable { 4 | display: flex; 5 | position: relative; 6 | height: 84px; 7 | margin-top: 10px; 8 | .part { 9 | flex: 1; 10 | text-align: center; 11 | height: 100%; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: space-between; 15 | .item { 16 | font-size: 16px; 17 | color: #666; 18 | } 19 | .ratio { 20 | font-size: 30px; 21 | color: #15984B; 22 | } 23 | } 24 | } 25 | .monthlyConsumable { 26 | display: flex; 27 | height: 180px; 28 | .part { 29 | height: 100%; 30 | text-align: center; 31 | } 32 | .item { 33 | font-size: 16px; 34 | color: #666; 35 | text-align: center; 36 | margin-top: 7px; 37 | } 38 | } 39 | .horizontalProgress { 40 | margin-top: 36px; 41 | width: 100%; 42 | display: flex; 43 | align-items: center; 44 | .item { 45 | line-height: 30px; 46 | color: #333; 47 | margin-left: 20px; 48 | } 49 | } 50 | .monthlyQuality { 51 | margin-top: 20px; 52 | .item { 53 | color: #333; 54 | margin-top: 5px; 55 | } 56 | } 57 | .extra { 58 | display: inline-block; 59 | width: 10px; 60 | height: 10px; 61 | margin-right: 10px; 62 | } 63 | .vertical { 64 | position: absolute; 65 | top: 50%; 66 | transform: translate(-50%, -50%); 67 | left: 50%; 68 | width: 1px; 69 | height: 80px; 70 | background: #e8e8e8; 71 | } 72 | .surgeryCategory { 73 | height: 40px; 74 | border: 1px solid #E8E8E8; 75 | border-radius: 2px; 76 | display: flex; 77 | align-items: center; 78 | justify-content: space-between; 79 | margin-bottom: 20px; 80 | padding: 0 8px; 81 | .left { 82 | min-width: 120px; 83 | } 84 | .item { 85 | text-align: center; 86 | } 87 | } 88 | .badSpaceLegend { 89 | width: 148px; 90 | margin-bottom: 10px; 91 | .point { 92 | display: inline-block; 93 | width: 10px; 94 | height: 10px; 95 | margin-right: 10px; 96 | } 97 | .item { 98 | color: #333; 99 | } 100 | } 101 | .rowCardContent { 102 | position: relative; 103 | display: flex; 104 | align-items: center; 105 | width: 100%; 106 | margin-bottom: 45px; 107 | .part { 108 | flex: 1; 109 | text-align: center; 110 | .partOne { 111 | color: #333; 112 | padding: 10px 0; 113 | } 114 | .partTwo { 115 | font-size: 36px; 116 | padding-bottom: 10px; 117 | } 118 | .partThree { 119 | display: inline-block; 120 | } 121 | } 122 | .bedVertical { 123 | position: absolute; 124 | top: 50%; 125 | transform: translateY(-50%); 126 | width: 1px; 127 | height: 142px; 128 | background: #e8e8e8; 129 | } 130 | } 131 | .wrapCard { 132 | display: inline-block; 133 | width: 16.66%; 134 | padding: 0 10px; 135 | margin-bottom: 24px; 136 | .technologyCard { 137 | background-color: #FAFFF9; 138 | border-color: #B4D9C4; 139 | border-radius: 8px; 140 | .item { 141 | font-size: 14px; 142 | text-align: center; 143 | color: #666; 144 | height: 40px; 145 | display: flex; 146 | justify-content: center; 147 | align-items: center; 148 | } 149 | .count { 150 | text-align: center; 151 | color: #333; 152 | font-size: 18px; 153 | } 154 | .compare { 155 | border-top: 1px solid #B4D9C4; 156 | text-align: center; 157 | padding-top: 6px; 158 | margin-top: 8px; 159 | } 160 | } 161 | } 162 | .surgeryCard { 163 | height: 90px; 164 | border: 1px solid #E8E8E8; 165 | border-radius: 2px; 166 | margin-bottom: 20px; 167 | .item { 168 | height: 45px; 169 | line-height: 45px; 170 | width: 100%; 171 | border-bottom: 1px solid #E8E8E8; 172 | color: #4a4a4a; 173 | text-align: center; 174 | } 175 | .count { 176 | display: flex; 177 | height: 45px; 178 | line-height: 45px; 179 | width: 100%; 180 | .left { 181 | flex: 1; 182 | color: #15984b; 183 | font-size: 18px; 184 | text-align: center; 185 | } 186 | .right { 187 | flex: 1; 188 | color: #8e8181; 189 | text-align: center; 190 | border-left: 1px solid #e8e8e8; 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /src/routes/Dashboard/Indicator/Consumable.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react' 2 | import { connect } from 'dva'; 3 | import Compare from '../../../components/Compare'; 4 | import { yuan, formatPercent } from '../../../utils/utils'; 5 | import { 6 | Table 7 | } from 'antd'; 8 | import styles from './Consumable.less'; 9 | 10 | @connect(({ consumable, date, loading }) => ({ 11 | consumable, 12 | date, 13 | loading: loading.effects['consumable/fetch'], 14 | })) 15 | 16 | export default class Consumable extends Component { 17 | 18 | state = { 19 | rangeDateType: this.props.date.indicator.rangeDateType, 20 | isOneDay: this.props.date.indicator.isOneDay 21 | }; 22 | 23 | componentDidMount() { 24 | const { date } = this.props; 25 | this.props.dispatch({ 26 | type: 'consumable/fetch', 27 | payload: { 28 | beginDate: date.indicator.beginDate, 29 | endDate: date.indicator.endDate 30 | } 31 | }) 32 | } 33 | 34 | componentWillUnmount() { 35 | const { dispatch } = this.props; 36 | dispatch({ 37 | type: 'consumable/clear', 38 | }); 39 | } 40 | 41 | render() { 42 | const { rangeDateType, isOneDay } = this.state; 43 | const { consumable, loading, date } = this.props; 44 | const { 45 | diffDepartSupplyIncomeModule 46 | } = consumable; 47 | let columns = [ 48 | { 49 | title: '科室名称', 50 | dataIndex: 'departName', 51 | key: 'departName', 52 | width: 200 53 | }, 54 | { 55 | title: '门诊材料费', 56 | dataIndex: 'outpatientMaterialCostCount', 57 | key: 'outpatientMaterialCostCount', 58 | sorter: (a, b) => (a.outpatientMaterialCostCount || 0) - (b.outpatientMaterialCostCount || 0), 59 | render: text => { 60 | return yuan(text) 61 | }, 62 | width: 200 63 | }, 64 | { 65 | title: '住院材料费', 66 | dataIndex: 'inHospitalMaterialCostCount', 67 | key: 'inHospitalMaterialCostCount', 68 | sorter: (a, b) => (a.inHospitalMaterialCostCount || 0) - (b.inHospitalMaterialCostCount || 0), 69 | render: text => { 70 | return yuan(text) 71 | }, 72 | width: 200 73 | }, 74 | { 75 | title: '卫生总费用', 76 | dataIndex: 'totalHealthCostCount', 77 | key: 'totalHealthCostCount', 78 | sorter: (a, b) => (a.totalHealthCostCount || 0) - (b.totalHealthCostCount || 0), 79 | render: text => { 80 | return yuan(text) 81 | }, 82 | width: 200 83 | }, 84 | { 85 | title: '耗材收入占比', 86 | dataIndex: 'materialCostRate', 87 | key: 'materialCostRate', 88 | sorter: (a, b) => (a.materialCostRate || 0) - (b.materialCostRate || 0), 89 | render: text => { 90 | return formatPercent(text); 91 | }, 92 | width: 200 93 | } 94 | ]; 95 | if(rangeDateType === 'monthly') { 96 | const columnObject = { 97 | title: '耗材收入占比环比', 98 | dataIndex: 'materialCostRateMom', 99 | key: 'materialCostRateMom', 100 | sorter: (a, b) => (a.materialCostRateMom || 0) - (b.materialCostRateMom || 0), 101 | render: text => { 102 | return ( 103 | 104 | ) 105 | }, 106 | width: 200 107 | } 108 | columns.push(columnObject); 109 | } 110 | return ( 111 | 112 |
117 | {isOneDay ? date.indicator.beginDate : `${date.indicator.beginDate} --- ${date.indicator.endDate}`} 118 |
119 |
120 |
121 |
不同科室的耗材收入占比
122 |
123 | 130 | index % 2 === 0 ? 'stripe' : '' 131 | } 132 | /> 133 | 134 | 135 | 136 | 137 | ) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /dist/iconfonts/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "iconfont"; 3 | src: url('iconfont.eot?t=1522201475376'); /* IE9*/ 4 | src: url('iconfont.eot?t=1522201475376#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAosAAsAAAAADzwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW70gAY21hcAAAAYAAAACIAAAB/vr40tRnbHlmAAACCAAABdYAAAg4ea6TXWhlYWQAAAfgAAAALwAAADYQ9G0GaGhlYQAACBAAAAAeAAAAJAfyA3pobXR4AAAIMAAAABYAAAAoJ+oAAGxvY2EAAAhIAAAAFgAAABYKGAfSbWF4cAAACGAAAAAdAAAAIAEdAIFuYW1lAAAIgAAAAUUAAAJtPlT+fXBvc3QAAAnIAAAAZAAAAH9eySQBeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sc4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDzjYW7438AQw9zC0AgUZgTJAQAmZwyDeJzFkdERxCAIRBejRnOWkjJSUL4yKeEqpY3cAv5cBYF5ijs4OiyAAmAhO8mAXBBYnFTF9QWb6xkHz42ZuN+adDzPrIpXFuIdzavEu5lZULFS6xQrXgt57+n/GL5+54nTxT3hFzUFNmUtgTmkNTCXdA28boE5qT2A9W4BrPcTmC86AvQf2q8dkXicjVVNjNtEFJ7nsceJ1/l1bOfHTtbOJt5ussk2TpxNdptk2+22W9GWbkthUYVo+RMHEEhFXBAECgIhhEr3gKiEihBqEYdeEBKHVnBCAoGQ2gPqCqlC/PSA4MqFGp43pawWCbA879nzxt/M+943YyIQ8sd39BJNE4VMku1kkdxJCLAK2FHOBMtp1rgKqJag6qkodYqOJRbtGt0Bus1SWsNrlnUmshhEIQ+u1fCcGudAq9nj5qChmQCZXPZwsmQk6WmQ0k7+JX8f9y6ohaIR6037y9V+qjGuhJ6Wk8lMMvlaiAlCiOP4WBQe07WwEJaY/54Qy6qXCtu4AsgZJ3vHamQ8lzz+SvNxs6SHAYZDUHLj0Qv9RDaB9zNZTUlmxHgklM5GihMpePqHsbQim+XvCV6AuZ6hdTokKiGC1mgrXtkpJppooqAm0OjwbWxb/FdzZtfktP2qPT25ayZ/yDTh87mDADW7ClC1/StwcG4Da4hYjwdYULZFyjTdTaTQ9KCVQONwE1uxTIpgfhvB/CsjMKgFYHiFAzye4NoKpE5myQLZRw6TY+Qh8ijiu3nQE1EQrRo4iR603VYP+dVVa+QFDCVwiIWhBA5B1KbX0FKM/vXwv3os1aKDgbcMsOzdJCM/gAlvAnnecDcHOXyeyFEy8sE79k/kRg5K//b6+490+PuQkgB8OALnAh9g5AYbdhhgDkfYXOD9jXnx9j+59QD9/+7h+gPC3+ZzdkPTq8giMtNDY0dB07HWTPNabhkJzIO4PQ8Nb0RmIOsWaiIYGuU0ndEycTzS1ojOCNjIVi+oR4rpdnlEHFeLTBkTRnb36u5srjTTBEhpQO2F5QWbwlhyjOvc3QU5IdcmlXB6plNVYqqxsO5fFwSw1tfBEgT/uv9RZiqD92Sz+b1mmhUUnWBUI9lDdzUadx3K6ve05OiYXN7pODvL4bGxcLXbrW74paTqtQxqtVtK3L9/E+I6zgA30pXtlQw0DzSnzKphVM1Aa+JtbsaITiqoN5e0CcpQ2JQp5t9GwlBZeaBlr+01dOSpiDsGxY7sKa5a1LE5MagDeo5syWj94g1BuHFxw560uitHPjuy0rXa9tTS3g/2Lk3ZP7vuWdftHtmlDV13uGXhx//6Ei3Uj1/uz8/3Lx9fOT/rebPnV8B1v3I//E3du+gG+VDM5zleoS/iXqySnVhpKw8F0BiusxhsmabXhx72FFVcKDa35baKQWsXRaeNI+qAGWLCJQ/rv3GcwTScO/hcoTN+5zFlm35z/YGXaUjoZJ/9aHWwf/9+F1tndbC05xf/bLoac1OpcjgahquTPZETBqwOP8HR5bX73pvA8+yFE4qjrC0ffenCeHl8aQnNhbW1LyGdOlzcE+d5fj8/5Uhp6R1zS22C84oSgSTIgJwgpNQW+uBQRkQsDxapTBRm10HUUYCBv2XKTRxV9gKD4ixA+5bXmMA0dI1/RP72HHnK/7T7hWFe868zPA6uXQOLMf/6URYX91ROsUhMrFzVulqpE4pGWKekcsrVCovE2alKh8lx9o4AfinMiQ9K6XC4y+QE65hmEDeNIC52Om+sP3WxM29wBxD12qZZ/KOM7a28yOIyq3ytqqVu8BVOMKfjBPGIeKrSDWLnxOTzUkZ6WJJCc2IswroIj1HD6Aau0zlDNu17iWhkhpxEGr0y1reIZdU300cDReQhBkHlXaz7eMtrByLXBdSNqKaYWKzBDgjUUQM8G9paCgNqSitwgbzUlO56OwBP+j6HRz5ujBqKDL9XNcY9z/FMYYzSY53tW9h8X5pWkqosLe6LU4kp4PdAkcXwYBbunZXrBVP7WDPzVXleCvFvP6IoTzwJAt+XSroifSypGUea43kOaq1QmI+F+otciLOpRGUK/Dkebmzl9RuFhSV5MnWSSYIISrOpgCxI1HrdnGQRzQAwtAgrGadlNdSYZfjnYPUkR6nwFsa4Bv5gww3I6W/yvMBJ+QgXjUs0PpMXlD8BJ2d7FgAAeJxjYGRgYADiSRc/Toznt/nKwM3CAALXHoo0I+j/9SzCzC1ALgcDE0gUAFWYC1gAeJxjYGRgYG7438AQwwJk/X/HIswApFEAFwByAgR0AAB4nGNhYGBgfsnAwMKABTMi2AAjWgESAAAAAAAAAHYAnADEAWwB+gJiAsgDaAQcAAB4nGNgZGBg4GIoZeBkAAEmMI8LSP4H8xkAFZQBnwAAAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nG3BWw6CMBAF0LkFedTEnbCophZ6ic6QkgZh9X746zni5MfLfx4ODVrc0KHHgBEed8Gnfdqhrm4jo+lU+OLjnXTllZOWpJF+ZdgzY9Cl27PVUoc58Qx21P7KpstJkS/Fuho7') format('woff'), 6 | url('iconfont.ttf?t=1522201475376') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont.svg?t=1522201475376#iconfont') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | i[class^='icon'] { 11 | font-family:"iconfont" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-down:before { content: "\e602"; color: #F36969; font-size: 14px; margin-right: 5px;} 19 | 20 | .icon-up:before { content: "\e605"; color: #3AC9A8; font-size: 14px; margin-right: 5px;} 21 | 22 | .icon-icon-rili:before { content: "\e606"; } 23 | 24 | .icon-icon-rili1:before { content: "\e607"; } 25 | 26 | .icon-menjizhenrenci:before { content: "\e608"; } 27 | 28 | .icon-jiashicang:before { content: "\e609"; } 29 | 30 | .icon-shouru:before { content: "\e60a"; } 31 | 32 | .icon-feiyaowu:before { content: "\e60b"; } 33 | 34 | .icon-zhongyi:before { content: "\e60c"; } 35 | 36 | --------------------------------------------------------------------------------