diff --git a/.temp/ant-design-pro.less b/.temp/ant-design-pro.less
new file mode 100644
index 00000000..497f7e97
--- /dev/null
+++ b/.temp/ant-design-pro.less
@@ -0,0 +1,3449 @@
+@import "/Users/xiaohuoni/umi-antd-pro/node_modules/antd/lib/style/themes/default.less";
+.antd-pro-components-active-chart-index-activeChart {
+ position: relative;
+}
+.antd-pro-components-active-chart-index-activeChartGrid {
+ p {
+ position: absolute;
+ top: 80px;
+ }
+ p:last-child {
+ top: 115px;
+ }
+}
+.antd-pro-components-active-chart-index-activeChartLegend {
+ position: relative;
+ font-size: 0;
+ margin-top: 8px;
+ height: 20px;
+ line-height: 20px;
+ span {
+ display: inline-block;
+ font-size: 12px;
+ text-align: center;
+ width: 33.33%;
+ }
+ span:first-child {
+ text-align: left;
+ }
+ span:last-child {
+ text-align: right;
+ }
+}
+.antd-pro-components-active-chart-index-dashedLine {
+ position: relative;
+ height: 1px;
+ top: -70px;
+ left: -3px;
+
+ .antd-pro-components-active-chart-index-line {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%);
+ background-size: 6px;
+ }
+}
+
+.antd-pro-components-active-chart-index-dashedLine:last-child {
+ top: -36px;
+}
+
+
+
+.antd-pro-components-article-list-content-index-listContent {
+ .antd-pro-components-article-list-content-index-description {
+ line-height: 22px;
+ max-width: 720px;
+ }
+ .antd-pro-components-article-list-content-index-extra {
+ color: @text-color-secondary;
+ margin-top: 16px;
+ line-height: 22px;
+ .ant-avatar {
+ vertical-align: top;
+ margin-right: 8px;
+ width: 20px;
+ height: 20px;
+ position: relative;
+ top: 1px;
+ }
+ & > em {
+ color: @disabled-color;
+ font-style: normal;
+ margin-left: 16px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-components-article-list-content-index-listContent {
+ .antd-pro-components-article-list-content-index-extra {
+ & > em {
+ display: block;
+ margin-left: 0;
+ margin-top: 8px;
+ }
+ }
+ }
+}
+
+
+
+.antd-pro-components-avatar-list-index-avatarList {
+ display: inline-block;
+ ul {
+ display: inline-block;
+ margin-left: 8px;
+ font-size: 0;
+ }
+}
+
+.antd-pro-components-avatar-list-index-avatarItem {
+ display: inline-block;
+ font-size: @font-size-base;
+ margin-left: -8px;
+ width: @avatar-size-base;
+ height: @avatar-size-base;
+ .ant-avatar {
+ border: 1px solid #fff;
+ }
+}
+
+.antd-pro-components-avatar-list-index-avatarItemLarge {
+ width: @avatar-size-lg;
+ height: @avatar-size-lg;
+}
+
+.antd-pro-components-avatar-list-index-avatarItemSmall {
+ width: @avatar-size-sm;
+ height: @avatar-size-sm;
+}
+
+.antd-pro-components-avatar-list-index-avatarItemMini {
+ width: 20px;
+ height: 20px;
+ .ant-avatar {
+ width: 20px;
+ height: 20px;
+ line-height: 20px;
+ }
+}
+
+
+
+.antd-pro-components-charts-chart-card-index-chartCard {
+ position: relative;
+ .antd-pro-components-charts-chart-card-index-chartTop {
+ position: relative;
+ overflow: hidden;
+ width: 100%;
+ }
+ .antd-pro-components-charts-chart-card-index-chartTopMargin {
+ margin-bottom: 12px;
+ }
+ .antd-pro-components-charts-chart-card-index-chartTopHasMargin {
+ margin-bottom: 20px;
+ }
+ .antd-pro-components-charts-chart-card-index-metaWrap {
+ float: left;
+ }
+ .antd-pro-components-charts-chart-card-index-avatar {
+ position: relative;
+ top: 4px;
+ float: left;
+ margin-right: 20px;
+ img {
+ border-radius: 100%;
+ }
+ }
+ .antd-pro-components-charts-chart-card-index-meta {
+ color: @text-color-secondary;
+ font-size: @font-size-base;
+ line-height: 22px;
+ height: 22px;
+ }
+ .antd-pro-components-charts-chart-card-index-action {
+ cursor: pointer;
+ position: absolute;
+ top: 0;
+ right: 0;
+ }
+ .antd-pro-components-charts-chart-card-index-total {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ white-space: nowrap;
+ color: @heading-color;
+ margin-top: 4px;
+ margin-bottom: 0;
+ font-size: 30px;
+ line-height: 38px;
+ height: 38px;
+ }
+ .antd-pro-components-charts-chart-card-index-content {
+ margin-bottom: 12px;
+ position: relative;
+ width: 100%;
+ }
+ .antd-pro-components-charts-chart-card-index-contentFixed {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ }
+ .antd-pro-components-charts-chart-card-index-footer {
+ border-top: 1px solid @border-color-split;
+ padding-top: 9px;
+ margin-top: 8px;
+ & > * {
+ position: relative;
+ }
+ }
+ .antd-pro-components-charts-chart-card-index-footerMargin {
+ margin-top: 20px;
+ }
+}
+
+
+
+.antd-pro-components-charts-field-index-field {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin: 0;
+ .antd-pro-components-charts-field-index-label,
+ .antd-pro-components-charts-field-index-number {
+ font-size: @font-size-base;
+ line-height: 22px;
+ }
+ .antd-pro-components-charts-field-index-number {
+ color: @heading-color;
+ margin-left: 8px;
+ }
+}
+
+.antd-pro-components-charts-index-miniChart {
+ position: relative;
+ width: 100%;
+ .antd-pro-components-charts-index-chartContent {
+ position: absolute;
+ bottom: -28px;
+ width: 100%;
+ > div {
+ margin: 0 -5px;
+ overflow: hidden;
+ }
+ }
+ .antd-pro-components-charts-index-chartLoading {
+ position: absolute;
+ top: 16px;
+ left: 50%;
+ margin-left: -7px;
+ }
+}
+
+
+
+.antd-pro-components-charts-mini-progress-index-miniProgress {
+ padding: 5px 0;
+ position: relative;
+ width: 100%;
+ .antd-pro-components-charts-mini-progress-index-progressWrap {
+ background-color: @background-color-base;
+ position: relative;
+ }
+ .antd-pro-components-charts-mini-progress-index-progress {
+ transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s;
+ border-radius: 1px 0 0 1px;
+ background-color: @primary-color;
+ width: 0;
+ height: 100%;
+ }
+ .antd-pro-components-charts-mini-progress-index-target {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ span {
+ border-radius: 100px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 4px;
+ width: 2px;
+ }
+ span:last-child {
+ top: auto;
+ bottom: 0;
+ }
+ }
+}
+
+
+
+.antd-pro-components-charts-pie-index-pie {
+ position: relative;
+ .antd-pro-components-charts-pie-index-chart {
+ position: relative;
+ }
+ &.antd-pro-components-charts-pie-index-hasLegend .antd-pro-components-charts-pie-index-chart {
+ width: ~'calc(100% - 240px)';
+ }
+ .antd-pro-components-charts-pie-index-legend {
+ position: absolute;
+ right: 0;
+ min-width: 200px;
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0 20px;
+ list-style: none;
+ padding: 0;
+ li {
+ cursor: pointer;
+ margin-bottom: 16px;
+ height: 22px;
+ line-height: 22px;
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+ .antd-pro-components-charts-pie-index-dot {
+ border-radius: 8px;
+ display: inline-block;
+ margin-right: 8px;
+ position: relative;
+ top: -1px;
+ height: 8px;
+ width: 8px;
+ }
+ .antd-pro-components-charts-pie-index-line {
+ background-color: @border-color-split;
+ display: inline-block;
+ margin-right: 8px;
+ width: 1px;
+ height: 16px;
+ }
+ .antd-pro-components-charts-pie-index-legendTitle {
+ color: @text-color;
+ }
+ .antd-pro-components-charts-pie-index-percent {
+ color: @text-color-secondary;
+ }
+ .antd-pro-components-charts-pie-index-value {
+ position: absolute;
+ right: 0;
+ }
+ .antd-pro-components-charts-pie-index-title {
+ margin-bottom: 8px;
+ }
+ .antd-pro-components-charts-pie-index-total {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ text-align: center;
+ max-height: 62px;
+ transform: translate(-50%, -50%);
+ & > h4 {
+ color: @text-color-secondary;
+ font-size: 14px;
+ line-height: 22px;
+ height: 22px;
+ margin-bottom: 8px;
+ font-weight: normal;
+ }
+ & > p {
+ color: @heading-color;
+ display: block;
+ font-size: 1.2em;
+ height: 32px;
+ line-height: 32px;
+ white-space: nowrap;
+ }
+ }
+}
+
+.antd-pro-components-charts-pie-index-legendBlock {
+ &.antd-pro-components-charts-pie-index-hasLegend .antd-pro-components-charts-pie-index-chart {
+ width: 100%;
+ margin: 0 0 32px 0;
+ }
+ .antd-pro-components-charts-pie-index-legend {
+ position: relative;
+ transform: none;
+ }
+}
+
+
+
+.antd-pro-components-charts-radar-index-radar {
+ .antd-pro-components-charts-radar-index-legend {
+ margin-top: 16px;
+ .antd-pro-components-charts-radar-index-legendItem {
+ position: relative;
+ text-align: center;
+ cursor: pointer;
+ color: @text-color-secondary;
+ line-height: 22px;
+ p {
+ margin: 0;
+ }
+ h6 {
+ color: @heading-color;
+ padding-left: 16px;
+ font-size: 24px;
+ line-height: 32px;
+ margin-top: 4px;
+ margin-bottom: 0;
+ }
+ &:after {
+ background-color: @border-color-split;
+ position: absolute;
+ top: 8px;
+ right: 0;
+ height: 40px;
+ width: 1px;
+ content: '';
+ }
+ }
+ > :last-child .antd-pro-components-charts-radar-index-legendItem:after {
+ display: none;
+ }
+ .antd-pro-components-charts-radar-index-dot {
+ border-radius: 6px;
+ display: inline-block;
+ margin-right: 6px;
+ position: relative;
+ top: -1px;
+ height: 6px;
+ width: 6px;
+ }
+ }
+}
+
+.antd-pro-components-charts-tag-cloud-index-tagCloud {
+ overflow: hidden;
+ canvas {
+ transform-origin: 0 0;
+ }
+}
+
+.antd-pro-components-charts-timeline-chart-index-timelineChart {
+ background: #fff;
+}
+
+
+
+.antd-pro-components-charts-water-wave-index-waterWave {
+ display: inline-block;
+ position: relative;
+ transform-origin: left;
+ .antd-pro-components-charts-water-wave-index-text {
+ position: absolute;
+ left: 0;
+ top: 32px;
+ text-align: center;
+ width: 100%;
+ span {
+ color: @text-color-secondary;
+ font-size: 14px;
+ line-height: 22px;
+ }
+ h4 {
+ color: @heading-color;
+ line-height: 32px;
+ font-size: 24px;
+ }
+ }
+ .antd-pro-components-charts-water-wave-index-waterWaveCanvasWrapper {
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ }
+}
+
+
+
+.antd-pro-components-description-list-index-descriptionList {
+ // offset the padding-bottom of last row
+ .ant-row {
+ margin-bottom: -16px;
+ overflow: hidden;
+ }
+
+ .antd-pro-components-description-list-index-title {
+ font-size: 14px;
+ color: @heading-color;
+ font-weight: 500;
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-description-list-index-term {
+ // Line-height is 22px IE dom height will calculate error
+ line-height: 20px;
+ padding-bottom: 16px;
+ margin-right: 8px;
+ color: @heading-color;
+ white-space: nowrap;
+ display: table-cell;
+
+ &:after {
+ content: ':';
+ margin: 0 8px 0 2px;
+ position: relative;
+ top: -0.5px;
+ }
+ }
+
+ .antd-pro-components-description-list-index-detail {
+ line-height: 20px;
+ width: 100%;
+ padding-bottom: 16px;
+ color: @text-color;
+ display: table-cell;
+ }
+
+ &.antd-pro-components-description-list-index-small {
+ // offset the padding-bottom of last row
+ .ant-row {
+ margin-bottom: -8px;
+ }
+ .antd-pro-components-description-list-index-title {
+ margin-bottom: 12px;
+ color: @text-color;
+ }
+ .antd-pro-components-description-list-index-term,
+ .antd-pro-components-description-list-index-detail {
+ padding-bottom: 8px;
+ }
+ }
+
+ &.antd-pro-components-description-list-index-large {
+ .antd-pro-components-description-list-index-title {
+ font-size: 16px;
+ }
+ }
+
+ &.antd-pro-components-description-list-index-vertical {
+ .antd-pro-components-description-list-index-term {
+ padding-bottom: 8px;
+ display: block;
+ }
+
+ .antd-pro-components-description-list-index-detail {
+ display: block;
+ }
+ }
+}
+
+
+
+.antd-pro-components-editable-item-index-editableItem {
+ line-height: @input-height-base;
+ display: table;
+ width: 100%;
+ margin-top: (@font-size-base * @line-height-base - @input-height-base) / 2;
+
+ .antd-pro-components-editable-item-index-wrapper {
+ display: table-row;
+
+ & > * {
+ display: table-cell;
+ }
+
+ & > *:first-child {
+ width: 85%;
+ }
+
+ .antd-pro-components-editable-item-index-icon {
+ cursor: pointer;
+ text-align: right;
+ }
+ }
+}
+
+
+
+.antd-pro-components-editable-link-group-index-linkGroup {
+ padding: 20px 0 8px 24px;
+ font-size: 0;
+ & > a {
+ color: @text-color;
+ display: inline-block;
+ font-size: @font-size-base;
+ margin-bottom: 13px;
+ width: 25%;
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
+
+.antd-pro-components-ellipsis-index-ellipsis {
+ overflow: hidden;
+ display: inline-block;
+ word-break: break-all;
+ width: 100%;
+}
+
+.antd-pro-components-ellipsis-index-lines {
+ position: relative;
+ .antd-pro-components-ellipsis-index-shadow {
+ display: block;
+ position: absolute;
+ color: transparent;
+ opacity: 0;
+ z-index: -999;
+ }
+}
+
+.antd-pro-components-ellipsis-index-lineClamp {
+ position: relative;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+}
+
+
+
+.antd-pro-components-exception-index-exception {
+ display: flex;
+ align-items: center;
+ height: 80%;
+ min-height: 500px;
+
+ .antd-pro-components-exception-index-imgBlock {
+ flex: 0 0 62.5%;
+ width: 62.5%;
+ padding-right: 152px;
+ zoom: 1;
+ &:before,
+ &:after {
+ content: ' ';
+ display: table;
+ }
+ &:after {
+ clear: both;
+ visibility: hidden;
+ font-size: 0;
+ height: 0;
+ }
+ }
+
+ .antd-pro-components-exception-index-imgEle {
+ height: 360px;
+ width: 100%;
+ max-width: 430px;
+ float: right;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ background-size: contain;
+ }
+
+ .antd-pro-components-exception-index-content {
+ flex: auto;
+
+ h1 {
+ color: #434e59;
+ font-size: 72px;
+ font-weight: 600;
+ line-height: 72px;
+ margin-bottom: 24px;
+ }
+
+ .antd-pro-components-exception-index-desc {
+ color: @text-color-secondary;
+ font-size: 20px;
+ line-height: 28px;
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-exception-index-actions {
+ button:not(:last-child) {
+ margin-right: 8px;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xl) {
+ .antd-pro-components-exception-index-exception {
+ .antd-pro-components-exception-index-imgBlock {
+ padding-right: 88px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-components-exception-index-exception {
+ display: block;
+ text-align: center;
+ .antd-pro-components-exception-index-imgBlock {
+ padding-right: 0;
+ margin: 0 auto 24px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-components-exception-index-exception {
+ .antd-pro-components-exception-index-imgBlock {
+ margin-bottom: -24px;
+ overflow: hidden;
+ }
+ }
+}
+
+
+
+.antd-pro-components-footer-toolbar-index-toolbar {
+ position: fixed;
+ width: 100%;
+ bottom: 0;
+ right: 0;
+ height: 56px;
+ line-height: 56px;
+ box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
+ background: #fff;
+ border-top: 1px solid @border-color-split;
+ padding: 0 24px;
+ z-index: 9;
+
+ &:after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
+ .antd-pro-components-footer-toolbar-index-left {
+ float: left;
+ }
+
+ .antd-pro-components-footer-toolbar-index-right {
+ float: right;
+ }
+
+ button + button {
+ margin-left: 8px;
+ }
+}
+
+
+
+.antd-pro-components-global-footer-index-globalFooter {
+ padding: 0 16px;
+ margin: 48px 0 24px 0;
+ text-align: center;
+
+ .antd-pro-components-global-footer-index-links {
+ margin-bottom: 8px;
+
+ a {
+ color: @text-color-secondary;
+ transition: all 0.3s;
+
+ &:not(:last-child) {
+ margin-right: 40px;
+ }
+
+ &:hover {
+ color: @text-color;
+ }
+ }
+ }
+
+ .antd-pro-components-global-footer-index-copyright {
+ color: @text-color-secondary;
+ font-size: @font-size-base;
+ }
+}
+
+
+
+@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
+
+.antd-pro-components-global-header-index-header {
+ height: 64px;
+ padding: 0;
+ background: #fff;
+ box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+ position: relative;
+}
+
+.antd-pro-components-global-header-index-logo {
+ height: 64px;
+ line-height: 58px;
+ vertical-align: top;
+ display: inline-block;
+ padding: 0 0 0 24px;
+ cursor: pointer;
+ font-size: 20px;
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ }
+}
+
+.antd-pro-components-global-header-index-menu {
+ .anticon {
+ margin-right: 8px;
+ }
+ .ant-dropdown-menu-item {
+ width: 160px;
+ }
+}
+
+i.antd-pro-components-global-header-index-trigger {
+ font-size: 20px;
+ height: 64px;
+ cursor: pointer;
+ transition: all 0.3s, padding 0s;
+ padding: 22px 24px;
+ &:hover {
+ background: @pro-header-hover-bg;
+ }
+}
+
+.antd-pro-components-global-header-index-right {
+ float: right;
+ height: 100%;
+ overflow: hidden;
+ .antd-pro-components-global-header-index-action {
+ cursor: pointer;
+ padding: 0 12px;
+ display: inline-block;
+ transition: all 0.3s;
+ height: 100%;
+ > i {
+ vertical-align: middle;
+ color: @text-color;
+ }
+ &:hover {
+ background: @pro-header-hover-bg;
+ }
+ &.ant-popover-open {
+ background: @pro-header-hover-bg;
+ }
+ }
+ .antd-pro-components-global-header-index-search {
+ padding: 0 12px;
+ &:hover {
+ background: transparent;
+ }
+ }
+ .antd-pro-components-global-header-index-account {
+ .antd-pro-components-global-header-index-avatar {
+ margin: 20px 8px 20px 0;
+ color: @primary-color;
+ background: rgba(255, 255, 255, 0.85);
+ vertical-align: top;
+ }
+ }
+}
+
+.antd-pro-components-global-header-index-dark {
+ height: 64px;
+ .antd-pro-components-global-header-index-action {
+ color: rgba(255, 255, 255, 0.85);
+ > i {
+ color: rgba(255, 255, 255, 0.85);
+ }
+ .ant-popover-open {
+ background: @primary-color;
+ }
+ .ant-badge {
+ color: rgba(255, 255, 255, 0.85);
+ }
+ }
+}
+
+@media only screen and (max-width: @screen-md) {
+ .antd-pro-components-global-header-index-header {
+ .ant-divider-vertical {
+ vertical-align: unset;
+ }
+ .antd-pro-components-global-header-index-name {
+ display: none;
+ }
+ i.antd-pro-components-global-header-index-trigger {
+ padding: 22px 12px;
+ }
+ .antd-pro-components-global-header-index-logo {
+ padding-left: 12px;
+ padding-right: 12px;
+ position: relative;
+ }
+ .antd-pro-components-global-header-index-right {
+ position: absolute;
+ right: 12px;
+ top: 0;
+ background: #fff;
+ .antd-pro-components-global-header-index-account {
+ .antd-pro-components-global-header-index-avatar {
+ margin-right: 0;
+ }
+ }
+ }
+ }
+}
+
+
+
+.antd-pro-components-header-search-index-headerSearch {
+ .anticon-search {
+ cursor: pointer;
+ font-size: 16px;
+ }
+ .antd-pro-components-header-search-index-input {
+ transition: width 0.3s, margin-left 0.3s;
+ width: 0;
+ background: transparent;
+ border-radius: 0;
+ .ant-select-selection {
+ background: transparent;
+ }
+ input {
+ border: 0;
+ padding-left: 0;
+ padding-right: 0;
+ box-shadow: none !important;
+ }
+ &,
+ &:hover,
+ &:focus {
+ border-bottom: 1px solid @border-color-base;
+ }
+ &.antd-pro-components-header-search-index-show {
+ width: 210px;
+ margin-left: 8px;
+ }
+ }
+}
+
+
+
+.antd-pro-components-login-index-login {
+ .ant-tabs .ant-tabs-bar {
+ border-bottom: 0;
+ margin-bottom: 24px;
+ text-align: center;
+ }
+
+ .antd-pro-components-login-index-ant-form-item {
+ margin: 0 2px 24px;
+ }
+
+ .antd-pro-components-login-index-getCaptcha {
+ display: block;
+ width: 100%;
+ }
+
+ .antd-pro-components-login-index-icon {
+ font-size: 24px;
+ color: rgba(0, 0, 0, 0.2);
+ margin-left: 16px;
+ vertical-align: middle;
+ cursor: pointer;
+ transition: color 0.3s;
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+
+ .antd-pro-components-login-index-other {
+ text-align: left;
+ margin-top: 24px;
+ line-height: 22px;
+
+ .antd-pro-components-login-index-register {
+ float: right;
+ }
+ }
+
+ .antd-pro-components-login-index-prefixIcon {
+ font-size: @font-size-base;
+ color: @disabled-color;
+ }
+
+ .submit {
+ width: 100%;
+ margin-top: 24px;
+ }
+}
+
+
+
+.antd-pro-components-notice-icon-index-popover {
+ width: 336px;
+ .ant-popover-inner-content {
+ padding: 0;
+ }
+}
+
+.antd-pro-components-notice-icon-index-noticeButton {
+ cursor: pointer;
+ display: inline-block;
+ transition: all 0.3s;
+}
+
+.antd-pro-components-notice-icon-index-icon {
+ padding: 4px;
+}
+
+.antd-pro-components-notice-icon-index-tabs {
+ .ant-tabs-nav-scroll {
+ text-align: center;
+ }
+ .ant-tabs-bar {
+ margin-bottom: 4px;
+ }
+}
+
+
+
+.antd-pro-components-notice-icon-notice-list-list {
+ max-height: 400px;
+ overflow: auto;
+ .antd-pro-components-notice-icon-notice-list-item {
+ transition: all 0.3s;
+ overflow: hidden;
+ cursor: pointer;
+ padding-left: 24px;
+ padding-right: 24px;
+
+ .antd-pro-components-notice-icon-notice-list-meta {
+ width: 100%;
+ }
+
+ .antd-pro-components-notice-icon-notice-list-avatar {
+ background: #fff;
+ margin-top: 4px;
+ }
+ .antd-pro-components-notice-icon-notice-list-iconElement {
+ font-size: 32px;
+ }
+
+ &.antd-pro-components-notice-icon-notice-list-read {
+ opacity: 0.4;
+ }
+ &:last-child {
+ border-bottom: 0;
+ }
+ &:hover {
+ background: @primary-1;
+ }
+ .antd-pro-components-notice-icon-notice-list-title {
+ font-weight: normal;
+ margin-bottom: 8px;
+ }
+ .antd-pro-components-notice-icon-notice-list-description {
+ font-size: 12px;
+ line-height: @line-height-base;
+ }
+ .antd-pro-components-notice-icon-notice-list-datetime {
+ font-size: 12px;
+ margin-top: 4px;
+ line-height: @line-height-base;
+ }
+ .antd-pro-components-notice-icon-notice-list-extra {
+ float: right;
+ color: @text-color-secondary;
+ font-weight: normal;
+ margin-right: 0;
+ margin-top: -1.5px;
+ }
+ }
+}
+
+.antd-pro-components-notice-icon-notice-list-notFound {
+ text-align: center;
+ padding: 73px 0 88px 0;
+ color: @text-color-secondary;
+ img {
+ display: inline-block;
+ margin-bottom: 16px;
+ height: 76px;
+ }
+}
+
+.antd-pro-components-notice-icon-notice-list-clear {
+ height: 46px;
+ line-height: 46px;
+ text-align: center;
+ color: @text-color;
+ border-radius: 0 0 @border-radius-base @border-radius-base;
+ border-top: 1px solid @border-color-split;
+ transition: all 0.3s;
+ cursor: pointer;
+
+ &:hover {
+ color: @heading-color;
+ }
+}
+
+
+
+.antd-pro-components-number-info-index-numberInfo {
+ .antd-pro-components-number-info-index-suffix {
+ color: @text-color;
+ font-size: 16px;
+ font-style: normal;
+ margin-left: 4px;
+ }
+ .antd-pro-components-number-info-index-numberInfoTitle {
+ color: @text-color;
+ font-size: @font-size-lg;
+ margin-bottom: 16px;
+ transition: all 0.3s;
+ }
+ .antd-pro-components-number-info-index-numberInfoSubTitle {
+ color: @text-color-secondary;
+ font-size: @font-size-base;
+ height: 22px;
+ line-height: 22px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ white-space: nowrap;
+ }
+ .antd-pro-components-number-info-index-numberInfoValue {
+ margin-top: 4px;
+ font-size: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ white-space: nowrap;
+ & > span {
+ color: @heading-color;
+ display: inline-block;
+ line-height: 32px;
+ height: 32px;
+ font-size: 24px;
+ margin-right: 32px;
+ }
+ .antd-pro-components-number-info-index-subTotal {
+ color: @text-color-secondary;
+ font-size: @font-size-lg;
+ vertical-align: top;
+ margin-right: 0;
+ i {
+ font-size: 12px;
+ transform: scale(0.82);
+ margin-left: 4px;
+ }
+ .anticon-caret-up {
+ color: @red-6;
+ }
+ .anticon-caret-down {
+ color: @green-6;
+ }
+ }
+ }
+}
+.antd-pro-components-number-info-index-numberInfolight {
+ .antd-pro-components-number-info-index-numberInfoValue {
+ & > span {
+ color: @text-color;
+ }
+ }
+}
+
+
+
+.antd-pro-components-page-header-index-pageHeader {
+ background: @component-background;
+ padding: 16px 32px 0 32px;
+ border-bottom: @border-width-base @border-style-base @border-color-split;
+ .antd-pro-components-page-header-index-wide {
+ max-width: 1200px;
+ margin: auto;
+ }
+ .antd-pro-components-page-header-index-detail {
+ display: flex;
+ }
+
+ .antd-pro-components-page-header-index-row {
+ display: flex;
+ width: 100%;
+ }
+
+ .antd-pro-components-page-header-index-breadcrumb {
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-page-header-index-tabs {
+ margin: 0 0 0 -8px;
+ // 1px 可以让选中效果显示完成
+ .ant-tabs-bar {
+ border-bottom: none;
+ margin-bottom: 1px;
+ }
+ }
+
+ .antd-pro-components-page-header-index-logo {
+ flex: 0 1 auto;
+ margin-right: 16px;
+ padding-top: 1px;
+ > img {
+ width: 28px;
+ height: 28px;
+ border-radius: @border-radius-base;
+ display: block;
+ }
+ }
+
+ .antd-pro-components-page-header-index-title {
+ font-size: 20px;
+ font-weight: 500;
+ color: @heading-color;
+ }
+
+ .antd-pro-components-page-header-index-action {
+ margin-left: 56px;
+ min-width: 266px;
+ .ant-btn-group:not(:last-child),
+ .ant-btn:not(:last-child) {
+ margin-right: 8px;
+ }
+
+ .ant-btn-group > .ant-btn {
+ margin-right: 0;
+ }
+ }
+
+ .antd-pro-components-page-header-index-title,
+ .antd-pro-components-page-header-index-content {
+ flex: auto;
+ }
+
+ .antd-pro-components-page-header-index-action,
+ .antd-pro-components-page-header-index-extraContent,
+ .antd-pro-components-page-header-index-main {
+ flex: 0 1 auto;
+ }
+
+ .antd-pro-components-page-header-index-main {
+ width: 100%;
+ }
+
+ .antd-pro-components-page-header-index-title,
+ .antd-pro-components-page-header-index-action {
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-page-header-index-logo,
+ .antd-pro-components-page-header-index-content,
+ .antd-pro-components-page-header-index-extraContent {
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-page-header-index-action,
+ .antd-pro-components-page-header-index-extraContent {
+ text-align: right;
+ }
+
+ .antd-pro-components-page-header-index-extraContent {
+ margin-left: 88px;
+ min-width: 242px;
+ }
+}
+
+@media screen and (max-width: @screen-xl) {
+ .antd-pro-components-page-header-index-pageHeader {
+ .antd-pro-components-page-header-index-extraContent {
+ margin-left: 44px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-components-page-header-index-pageHeader {
+ .antd-pro-components-page-header-index-extraContent {
+ margin-left: 20px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .antd-pro-components-page-header-index-pageHeader {
+ .antd-pro-components-page-header-index-row {
+ display: block;
+ }
+
+ .antd-pro-components-page-header-index-action,
+ .antd-pro-components-page-header-index-extraContent {
+ margin-left: 0;
+ text-align: left;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-components-page-header-index-pageHeader {
+ .antd-pro-components-page-header-index-detail {
+ display: block;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-components-page-header-index-pageHeader {
+ .antd-pro-components-page-header-index-action {
+ .ant-btn-group,
+ .ant-btn {
+ display: block;
+ margin-bottom: 8px;
+ }
+ .ant-btn-group > .ant-btn {
+ display: inline-block;
+ margin-bottom: 0;
+ }
+ }
+ }
+}
+
+.antd-pro-components-page-header-wrapper-grid-content-main {
+ width: 100%;
+ height: 100%;
+ min-height: 100%;
+ transition: 0.3s;
+ &.antd-pro-components-page-header-wrapper-grid-content-wide {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+}
+
+
+
+.antd-pro-components-page-header-wrapper-index-content {
+ margin: 24px 24px 0;
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-components-page-header-wrapper-index-content {
+ margin: 24px 0 0;
+ }
+}
+
+
+
+.antd-pro-components-result-index-result {
+ text-align: center;
+ width: 72%;
+ margin: 0 auto;
+ @media screen and (max-width: @screen-xs) {
+ width: 100%;
+ }
+
+ .antd-pro-components-result-index-icon {
+ font-size: 72px;
+ line-height: 72px;
+ margin-bottom: 24px;
+
+ & > .antd-pro-components-result-index-success {
+ color: @success-color;
+ }
+
+ & > .antd-pro-components-result-index-error {
+ color: @error-color;
+ }
+ }
+
+ .antd-pro-components-result-index-title {
+ font-size: 24px;
+ color: @heading-color;
+ font-weight: 500;
+ line-height: 32px;
+ margin-bottom: 16px;
+ }
+
+ .antd-pro-components-result-index-description {
+ font-size: 14px;
+ line-height: 22px;
+ color: @text-color-secondary;
+ margin-bottom: 24px;
+ }
+
+ .antd-pro-components-result-index-extra {
+ background: #fafafa;
+ padding: 24px 40px;
+ border-radius: @border-radius-sm;
+ text-align: left;
+
+ @media screen and (max-width: @screen-xs) {
+ padding: 18px 20px;
+ }
+ }
+
+ .antd-pro-components-result-index-actions {
+ margin-top: 32px;
+
+ button:not(:last-child) {
+ margin-right: 8px;
+ }
+ }
+}
+
+
+
+.antd-pro-components-select-lang-index-menu {
+ .anticon {
+ margin-right: 8px;
+ }
+ .ant-dropdown-menu-item {
+ width: 160px;
+ }
+}
+
+.antd-pro-components-select-lang-index-dropDown {
+ cursor: pointer;
+ font-size: 14px;
+ vertical-align: top;
+ line-height: 64px;
+ > svg {
+ position: relative;
+ top: 2px;
+ }
+}
+
+
+
+.antd-pro-components-setting-drawer-index-content {
+ min-height: 100%;
+ background: #fff;
+ position: relative;
+}
+
+.antd-pro-components-setting-drawer-index-blockChecbox {
+ display: flex;
+ .antd-pro-components-setting-drawer-index-item {
+ margin-right: 16px;
+ position: relative;
+ // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
+ border-radius: @border-radius-base;
+ cursor: pointer;
+ img {
+ width: 48px;
+ }
+ }
+ .antd-pro-components-setting-drawer-index-selectIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ padding-top: 15px;
+ padding-left: 24px;
+ height: 100%;
+ color: @primary-color;
+ font-size: 14px;
+ font-weight: bold;
+ }
+}
+
+.antd-pro-components-setting-drawer-index-color_block {
+ width: 38px;
+ height: 22px;
+ margin: 4px;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-right: 12px;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.antd-pro-components-setting-drawer-index-title {
+ font-size: 14px;
+ color: @heading-color;
+ line-height: 22px;
+ margin-bottom: 12px;
+}
+
+.antd-pro-components-setting-drawer-index-handle {
+ position: absolute;
+ top: 240px;
+ background: @primary-color;
+ width: 48px;
+ height: 48px;
+ right: 300px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ pointer-events: auto;
+ z-index: 0;
+ text-align: center;
+ font-size: 16px;
+ border-radius: 4px 0 0 4px;
+}
+
+.antd-pro-components-setting-drawer-index-productionHint {
+ font-size: 12px;
+ margin-top: 16px;
+}
+
+.antd-pro-components-setting-drawer-theme-color-themeColor {
+ overflow: hidden;
+ margin-top: 24px;
+ .antd-pro-components-setting-drawer-theme-color-title {
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.65);
+ line-height: 22px;
+ margin-bottom: 12px;
+ }
+ .antd-pro-components-setting-drawer-theme-color-colorBlock {
+ width: 20px;
+ height: 20px;
+ border-radius: 2px;
+ float: left;
+ cursor: pointer;
+ margin-right: 8px;
+ text-align: center;
+ color: #fff;
+ font-weight: bold;
+ }
+}
+
+
+
+@nav-header-height: 64px;
+
+.antd-pro-components-sider-menu-index-logo {
+ height: @nav-header-height;
+ position: relative;
+ line-height: @nav-header-height;
+ padding-left: (@menu-collapsed-width - 32px) / 2;
+ transition: all 0.3s;
+ background: #002140;
+ overflow: hidden;
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ height: 32px;
+ }
+ h1 {
+ color: white;
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 20px;
+ margin: 0 0 0 12px;
+ font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-weight: 600;
+ }
+}
+
+.antd-pro-components-sider-menu-index-sider {
+ min-height: 100vh;
+ box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
+ position: relative;
+ z-index: 10;
+ &.antd-pro-components-sider-menu-index-fixSiderbar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ .ant-menu-root {
+ overflow-y: auto;
+ height: ~'calc(100vh - @{nav-header-height})';
+ }
+ }
+ &.antd-pro-components-sider-menu-index-light {
+ box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
+ background-color: white;
+ .antd-pro-components-sider-menu-index-logo {
+ background: white;
+ box-shadow: 1px 1px 0 0 @border-color-split;
+ h1 {
+ color: @primary-color;
+ }
+ }
+ .ant-menu-light {
+ border-right-color: transparent;
+ }
+ }
+}
+
+.antd-pro-components-sider-menu-index-icon {
+ width: 14px;
+ margin-right: 10px;
+}
+ .top-nav-menu li.ant-menu-item {
+ height: @nav-header-height;
+ line-height: @nav-header-height;
+ }
+ .drawer .drawer-content {
+ background: #001529;
+ }
+ .ant-menu-inline-collapsed {
+ & > .ant-menu-item .sider-menu-item-img + span,
+ &
+ > .ant-menu-item-group
+ > .ant-menu-item-group-list
+ > .ant-menu-item
+ .sider-menu-item-img
+ + span,
+ & > .ant-menu-submenu > .ant-menu-submenu-title .sider-menu-item-img + span {
+ max-width: 0;
+ display: inline-block;
+ opacity: 0;
+ }
+ }
+ .ant-menu-item .sider-menu-item-img + span,
+ .ant-menu-submenu-title .sider-menu-item-img + span {
+ transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
+ opacity: 1;
+ }
+
+
+
+.antd-pro-components-standard-form-row-index-standardFormRow {
+ border-bottom: 1px dashed @border-color-split;
+ padding-bottom: 16px;
+ margin-bottom: 16px;
+ display: flex;
+ .ant-form-item {
+ margin-right: 24px;
+ }
+ .antd-pro-components-standard-form-row-index-ant-form-item-label label {
+ color: @text-color;
+ margin-right: 0;
+ }
+ .antd-pro-components-standard-form-row-index-ant-form-item-label,
+ .antd-pro-components-standard-form-row-index-ant-form-item-control {
+ padding: 0;
+ line-height: 32px;
+ }
+ .label {
+ color: @heading-color;
+ font-size: @font-size-base;
+ margin-right: 24px;
+ flex: 0 0 auto;
+ text-align: right;
+ & > span {
+ display: inline-block;
+ height: 32px;
+ line-height: 32px;
+ &:after {
+ content: ':';
+ }
+ }
+ }
+ .content {
+ flex: 1 1 0;
+ :global {
+ .ant-form-item:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+}
+
+.antd-pro-components-standard-form-row-index-standardFormRowLast {
+ border: none;
+ padding-bottom: 0;
+ margin-bottom: 0;
+}
+
+.antd-pro-components-standard-form-row-index-standardFormRowBlock {
+ .ant-form-item,
+ div.ant-form-item-control-wrapper {
+ display: block;
+ }
+}
+
+.antd-pro-components-standard-form-row-index-standardFormRowGrid {
+ .ant-form-item,
+ div.ant-form-item-control-wrapper {
+ display: block;
+ }
+ .ant-form-item-label {
+ float: left;
+ }
+}
+
+
+
+.antd-pro-components-standard-table-index-standardTable {
+ .ant-table-pagination {
+ margin-top: 24px;
+ }
+
+ .antd-pro-components-standard-table-index-tableAlert {
+ margin-bottom: 16px;
+ }
+}
+
+
+
+.antd-pro-components-tag-select-index-tagSelect {
+ user-select: none;
+ margin-left: -8px;
+ position: relative;
+ overflow: hidden;
+ max-height: 32px;
+ line-height: 32px;
+ transition: all 0.3s;
+ .ant-tag {
+ padding: 0 8px;
+ margin-right: 24px;
+ font-size: @font-size-base;
+ }
+ &.antd-pro-components-tag-select-index-expanded {
+ transition: all 0.3s;
+ max-height: 200px;
+ }
+ .antd-pro-components-tag-select-index-trigger {
+ position: absolute;
+ top: 0;
+ right: 0;
+ i {
+ font-size: 12px;
+ }
+ }
+ &.antd-pro-components-tag-select-index-hasExpandTag {
+ padding-right: 50px;
+ }
+}
+
+.antd-pro-components-top-nav-header-index-head {
+ width: 100%;
+ transition: background 0.3s, width 0.2s;
+ height: 64px;
+ box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+ position: relative;
+ .ant-menu-submenu.ant-menu-submenu-horizontal {
+ line-height: 64px;
+ height: 100%;
+ .ant-menu-submenu-title {
+ height: 100%;
+ }
+ }
+ &.antd-pro-components-top-nav-header-index-light {
+ background-color: #fff;
+ }
+ .antd-pro-components-top-nav-header-index-main {
+ display: flex;
+ height: 64px;
+ padding-left: 24px;
+ &.antd-pro-components-top-nav-header-index-wide {
+ max-width: 1200px;
+ margin: auto;
+ padding-left: 0;
+ }
+ .antd-pro-components-top-nav-header-index-left {
+ flex: 1;
+ display: flex;
+ }
+ .antd-pro-components-top-nav-header-index-right {
+ width: 324px;
+ }
+ }
+}
+
+.antd-pro-components-top-nav-header-index-logo {
+ width: 165px;
+ height: 64px;
+ position: relative;
+ line-height: 64px;
+ transition: all 0.3s;
+ overflow: hidden;
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ height: 32px;
+ }
+ h1 {
+ color: #fff;
+ display: inline-block;
+ vertical-align: top;
+ font-size: 16px;
+ margin: 0 0 0 12px;
+ font-weight: 400;
+ }
+}
+
+.antd-pro-components-top-nav-header-index-light {
+ h1 {
+ color: #002140;
+ }
+}
+
+
+
+.antd-pro-components-trend-index-trendItem {
+ display: inline-block;
+ font-size: @font-size-base;
+ line-height: 22px;
+
+ .antd-pro-components-trend-index-up,
+ .antd-pro-components-trend-index-down {
+ margin-left: 4px;
+ position: relative;
+ top: 1px;
+ i {
+ font-size: 12px;
+ transform: scale(0.83);
+ }
+ }
+ .antd-pro-components-trend-index-up {
+ color: @red-6;
+ }
+ .antd-pro-components-trend-index-down {
+ color: @green-6;
+ top: -1px;
+ }
+
+ &.antd-pro-components-trend-index-trendItemGrey .antd-pro-components-trend-index-up,
+ &.antd-pro-components-trend-index-trendItemGrey .antd-pro-components-trend-index-down {
+ color: @text-color;
+ }
+
+ &.antd-pro-components-trend-index-reverseColor .antd-pro-components-trend-index-up {
+ color: @green-6;
+ }
+ &.antd-pro-components-trend-index-reverseColor .antd-pro-components-trend-index-down {
+ color: @red-6;
+ }
+}
+
+.antd-pro-layouts-header-fixedHeader {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 100%;
+ z-index: 9;
+ transition: width 0.2s;
+}
+
+
+
+.antd-pro-layouts-user-layout-container {
+ display: flex;
+ flex-direction: column;
+ height: 100vh;
+ overflow: auto;
+ background: @layout-body-background;
+}
+
+.antd-pro-layouts-user-layout-lang {
+ text-align: right;
+ width: 100%;
+ height: 40px;
+ line-height: 44px;
+ .ant-dropdown-trigger {
+ margin-right: 24px;
+ }
+}
+
+.antd-pro-layouts-user-layout-content {
+ padding: 32px 0;
+ flex: 1;
+}
+
+@media (min-width: @screen-md-min) {
+ .antd-pro-layouts-user-layout-container {
+ background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');
+ background-repeat: no-repeat;
+ background-position: center 110px;
+ background-size: 100%;
+ }
+
+ .antd-pro-layouts-user-layout-content {
+ padding: 32px 0 24px 0;
+ }
+}
+
+.antd-pro-layouts-user-layout-top {
+ text-align: center;
+}
+
+.antd-pro-layouts-user-layout-header {
+ height: 44px;
+ line-height: 44px;
+ a {
+ text-decoration: none;
+ }
+}
+
+.antd-pro-layouts-user-layout-logo {
+ height: 44px;
+ vertical-align: top;
+ margin-right: 16px;
+}
+
+.antd-pro-layouts-user-layout-title {
+ font-size: 33px;
+ color: @heading-color;
+ font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-weight: 600;
+ position: relative;
+ top: 2px;
+}
+
+.antd-pro-layouts-user-layout-desc {
+ font-size: @font-size-base;
+ color: @text-color-secondary;
+ margin-top: 12px;
+ margin-bottom: 40px;
+}
+
+
+
+.antd-pro-pages-account-center-articles-articleList {
+ .ant-list-item:first-child {
+ padding-top: 0;
+ }
+}
+a.antd-pro-pages-account-center-articles-listItemMetaTitle {
+ color: @heading-color;
+}
+
+
+
+
+.antd-pro-pages-account-center-center-avatarHolder {
+ text-align: center;
+ margin-bottom: 24px;
+
+ & > img {
+ width: 104px;
+ height: 104px;
+ margin-bottom: 20px;
+ }
+
+ .antd-pro-pages-account-center-center-name {
+ font-size: 20px;
+ line-height: 28px;
+ font-weight: 500;
+ color: @heading-color;
+ margin-bottom: 4px;
+ }
+}
+
+.antd-pro-pages-account-center-center-detail {
+ p {
+ margin-bottom: 8px;
+ padding-left: 26px;
+ position: relative;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ i {
+ position: absolute;
+ height: 14px;
+ width: 14px;
+ left: 0;
+ top: 4px;
+ background: url(https://gw.alipayobjects.com/zos/rmsportal/pBjWzVAHnOOtAUvZmZfy.svg);
+
+ &.antd-pro-pages-account-center-center-title {
+ background-position: 0 0;
+ }
+
+ &.antd-pro-pages-account-center-center-group {
+ background-position: 0 -22px;
+ }
+
+ &.antd-pro-pages-account-center-center-address {
+ background-position: 0 -44px;
+ }
+ }
+}
+
+.antd-pro-pages-account-center-center-tagsTitle,
+.antd-pro-pages-account-center-center-teamTitle {
+ font-weight: 500;
+ color: @heading-color;
+ margin-bottom: 12px;
+}
+
+.antd-pro-pages-account-center-center-tags {
+ .ant-tag {
+ margin-bottom: 8px;
+ }
+}
+
+.antd-pro-pages-account-center-center-team {
+ .ant-avatar {
+ margin-right: 12px;
+ }
+
+ a {
+ display: block;
+ margin-bottom: 24px;
+ color: @text-color;
+ transition: color 0.3s;
+ .textOverflow();
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
+
+.antd-pro-pages-account-center-center-tabsCard {
+ .ant-card-head {
+ padding: 0 16px;
+ }
+}
+
+
+
+.antd-pro-pages-account-settings-base-view-baseView {
+ display: flex;
+ padding-top: 12px;
+
+ .antd-pro-pages-account-settings-base-view-left {
+ max-width: 448px;
+ min-width: 224px;
+ }
+ .antd-pro-pages-account-settings-base-view-right {
+ flex: 1;
+ padding-left: 104px;
+ .antd-pro-pages-account-settings-base-view-avatar_title {
+ height: 22px;
+ font-size: @font-size-base;
+ color: @heading-color;
+ line-height: 22px;
+ margin-bottom: 8px;
+ }
+ .antd-pro-pages-account-settings-base-view-avatar {
+ width: 144px;
+ height: 144px;
+ margin-bottom: 12px;
+ overflow: hidden;
+ img {
+ width: 100%;
+ }
+ }
+ .antd-pro-pages-account-settings-base-view-button_view {
+ width: 144px;
+ text-align: center;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xl) {
+ .antd-pro-pages-account-settings-base-view-baseView {
+ flex-direction: column-reverse;
+
+ .antd-pro-pages-account-settings-base-view-right {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ max-width: 448px;
+ .antd-pro-pages-account-settings-base-view-avatar_title {
+ display: none;
+ }
+ }
+ }
+}
+
+
+
+.antd-pro-pages-account-settings-geographic-view-row {
+ .antd-pro-pages-account-settings-geographic-view-item {
+ max-width: 220px;
+ width: 50%;
+ }
+ .antd-pro-pages-account-settings-geographic-view-item:first-child {
+ margin-right: 8px;
+ width: ~'calc(50% - 8px)';
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-account-settings-geographic-view-item:first-child {
+ margin: 0;
+ margin-bottom: 8px;
+ }
+}
+
+
+
+.antd-pro-pages-account-settings-info-main {
+ width: 100%;
+ height: 100%;
+ background-color: @body-background;
+ display: flex;
+ padding-top: 16px;
+ padding-bottom: 16px;
+ overflow: auto;
+ .antd-pro-pages-account-settings-info-leftmenu {
+ width: 224px;
+ border-right: @border-width-base @border-style-base @border-color-split;
+ .ant-menu-inline {
+ border: none;
+ }
+ .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
+ font-weight: bold;
+ }
+ }
+ .antd-pro-pages-account-settings-info-right {
+ flex: 1;
+ padding-left: 40px;
+ padding-right: 40px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ .antd-pro-pages-account-settings-info-title {
+ font-size: 20px;
+ color: @heading-color;
+ line-height: 28px;
+ font-weight: 500;
+ margin-bottom: 12px;
+ }
+ }
+ .ant-list-split .ant-list-item:last-child {
+ border-bottom: 1px solid #e8e8e8;
+ }
+ .ant-list-item {
+ padding-top: 14px;
+ padding-bottom: 14px;
+ }
+}
+ .ant-list-item-meta {
+ // 账号绑定图标
+ .taobao {
+ color: #ff4000;
+ display: block;
+ font-size: 48px;
+ line-height: 48px;
+ border-radius: @border-radius-base;
+ }
+ .dingding {
+ background-color: #2eabff;
+ color: #fff;
+ font-size: 32px;
+ line-height: 32px;
+ padding: 6px;
+ margin: 2px;
+ border-radius: @border-radius-base;
+ }
+ .alipay {
+ color: #2eabff;
+ font-size: 48px;
+ line-height: 48px;
+ border-radius: @border-radius-base;
+ }
+ }
+
+ // 密码强度
+ font.strong {
+ color: @success-color;
+ }
+ font.medium {
+ color: @warning-color;
+ }
+ font.weak {
+ color: @error-color;
+ }
+
+@media screen and (max-width: @screen-md) {
+ .main {
+ flex-direction: column;
+ .leftmenu {
+ width: 100%;
+ border: none;
+ }
+ .right {
+ padding: 40px;
+ }
+ }
+}
+
+
+
+.antd-pro-pages-account-settings-phone-view-area_code {
+ max-width: 128px;
+ margin-right: 8px;
+ width: 30%;
+}
+.antd-pro-pages-account-settings-phone-view-phone_number {
+ max-width: 312px;
+ width: ~'calc(70% - 8px)';
+}
+
+
+
+
+.antd-pro-pages-dashboard-analysis-iconGroup {
+ i {
+ transition: color 0.32s;
+ color: @text-color-secondary;
+ cursor: pointer;
+ margin-left: 16px;
+ &:hover {
+ color: @text-color;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-analysis-rankingList {
+ margin: 25px 0 0;
+ padding: 0;
+ list-style: none;
+ li {
+ .clearfix();
+ margin-top: 16px;
+ display: flex;
+ align-items: center;
+ span {
+ color: @text-color;
+ font-size: 14px;
+ line-height: 22px;
+ }
+ .antd-pro-pages-dashboard-analysis-rankingItemNumber {
+ background-color: @background-color-base;
+ border-radius: 20px;
+ display: inline-block;
+ font-size: 12px;
+ font-weight: 600;
+ margin-right: 16px;
+ height: 20px;
+ line-height: 20px;
+ width: 20px;
+ text-align: center;
+ margin-top: 1.5px;
+ &.antd-pro-pages-dashboard-analysis-active {
+ background-color: #314659;
+ color: #fff;
+ }
+ }
+ .antd-pro-pages-dashboard-analysis-rankingItemTitle {
+ flex: 1;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 8px;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-analysis-salesExtra {
+ display: inline-block;
+ margin-right: 24px;
+ a {
+ color: @text-color;
+ margin-left: 24px;
+ &:hover {
+ color: @primary-color;
+ }
+ &.antd-pro-pages-dashboard-analysis-currentDate {
+ color: @primary-color;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-analysis-salesCard {
+ .antd-pro-pages-dashboard-analysis-salesBar {
+ padding: 0 0 32px 32px;
+ }
+ .antd-pro-pages-dashboard-analysis-salesRank {
+ padding: 0 32px 32px 72px;
+ }
+ .ant-tabs-bar {
+ padding-left: 16px;
+ .ant-tabs-nav .ant-tabs-tab {
+ padding-top: 16px;
+ padding-bottom: 14px;
+ line-height: 24px;
+ }
+ }
+ .ant-tabs-extra-content {
+ padding-right: 24px;
+ line-height: 55px;
+ }
+ .ant-card-head {
+ position: relative;
+ }
+ .ant-card-head-title {
+ align-items: normal;
+ }
+}
+
+.antd-pro-pages-dashboard-analysis-salesCardExtra {
+ height: inherit;
+}
+
+.antd-pro-pages-dashboard-analysis-salesTypeRadio {
+ position: absolute;
+ right: 54px;
+ bottom: 12px;
+}
+
+.antd-pro-pages-dashboard-analysis-offlineCard {
+ .ant-tabs-ink-bar {
+ bottom: auto;
+ }
+ .ant-tabs-bar {
+ border-bottom: none;
+ }
+ .ant-tabs-nav-container-scrolling {
+ padding-left: 40px;
+ padding-right: 40px;
+ }
+ .ant-tabs-tab-prev-icon:before {
+ position: relative;
+ left: 6px;
+ }
+ .ant-tabs-tab-next-icon:before {
+ position: relative;
+ right: 6px;
+ }
+ .ant-tabs-tab-active h4 {
+ color: @primary-color;
+ }
+}
+
+.antd-pro-pages-dashboard-analysis-trendText {
+ margin-left: 8px;
+ color: @heading-color;
+}
+
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-pages-dashboard-analysis-salesExtra {
+ display: none;
+ }
+
+ .antd-pro-pages-dashboard-analysis-rankingList {
+ li {
+ span:first-child {
+ margin-right: 8px;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-dashboard-analysis-rankingTitle {
+ margin-top: 16px;
+ }
+
+ .antd-pro-pages-dashboard-analysis-salesCard .antd-pro-pages-dashboard-analysis-salesBar {
+ padding: 16px;
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-dashboard-analysis-salesExtraWrap {
+ display: none;
+ }
+
+ .antd-pro-pages-dashboard-analysis-salesCard {
+ .ant-tabs-content {
+ padding-top: 30px;
+ }
+ }
+}
+
+
+
+
+.antd-pro-pages-dashboard-monitor-mapChart {
+ padding-top: 24px;
+ height: 452px;
+ text-align: center;
+ img {
+ display: inline-block;
+ max-width: 100%;
+ max-height: 437px;
+ }
+}
+
+.pie-stat {
+ font-size: 24px !important;
+}
+
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-pages-dashboard-monitor-mapChart {
+ height: auto;
+ }
+}
+
+
+
+
+.antd-pro-pages-dashboard-workplace-activitiesList {
+ padding: 0 24px 8px 24px;
+ .antd-pro-pages-dashboard-workplace-username {
+ color: @text-color;
+ }
+ .antd-pro-pages-dashboard-workplace-event {
+ font-weight: normal;
+ }
+}
+
+.antd-pro-pages-dashboard-workplace-pageHeaderContent {
+ display: flex;
+ .antd-pro-pages-dashboard-workplace-avatar {
+ flex: 0 1 72px;
+ margin-bottom: 8px;
+ & > span {
+ border-radius: 72px;
+ display: block;
+ width: 72px;
+ height: 72px;
+ }
+ }
+ .antd-pro-pages-dashboard-workplace-content {
+ position: relative;
+ top: 4px;
+ margin-left: 24px;
+ flex: 1 1 auto;
+ color: @text-color-secondary;
+ line-height: 22px;
+ .antd-pro-pages-dashboard-workplace-contentTitle {
+ font-size: 20px;
+ line-height: 28px;
+ font-weight: 500;
+ color: @heading-color;
+ margin-bottom: 12px;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-workplace-extraContent {
+ .clearfix();
+ float: right;
+ white-space: nowrap;
+ .antd-pro-pages-dashboard-workplace-statItem {
+ padding: 0 32px;
+ position: relative;
+ display: inline-block;
+ > p:first-child {
+ color: @text-color-secondary;
+ font-size: @font-size-base;
+ line-height: 22px;
+ margin-bottom: 4px;
+ }
+ > p {
+ color: @heading-color;
+ font-size: 30px;
+ line-height: 38px;
+ margin: 0;
+ > span {
+ color: @text-color-secondary;
+ font-size: 20px;
+ }
+ }
+ &:after {
+ background-color: @border-color-split;
+ position: absolute;
+ top: 8px;
+ right: 0;
+ width: 1px;
+ height: 40px;
+ content: '';
+ }
+ &:last-child {
+ padding-right: 0;
+ &:after {
+ display: none;
+ }
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-workplace-members {
+ a {
+ display: block;
+ margin: 12px 0;
+ height: 24px;
+ color: @text-color;
+ transition: all 0.3s;
+ .textOverflow();
+ .antd-pro-pages-dashboard-workplace-member {
+ font-size: @font-size-base;
+ line-height: 24px;
+ vertical-align: top;
+ margin-left: 12px;
+ }
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-workplace-projectList {
+ .ant-card-meta-description {
+ color: @text-color-secondary;
+ height: 44px;
+ line-height: 22px;
+ overflow: hidden;
+ }
+ .antd-pro-pages-dashboard-workplace-cardTitle {
+ font-size: 0;
+ a {
+ color: @heading-color;
+ margin-left: 12px;
+ line-height: 24px;
+ height: 24px;
+ display: inline-block;
+ vertical-align: top;
+ font-size: @font-size-base;
+ &:hover {
+ color: @primary-color;
+ }
+ }
+ }
+ .antd-pro-pages-dashboard-workplace-projectGrid {
+ width: 33.33%;
+ }
+ .antd-pro-pages-dashboard-workplace-projectItemContent {
+ display: flex;
+ margin-top: 8px;
+ overflow: hidden;
+ font-size: 12px;
+ height: 20px;
+ line-height: 20px;
+ .textOverflow();
+ a {
+ color: @text-color-secondary;
+ display: inline-block;
+ flex: 1 1 0;
+ .textOverflow();
+ &:hover {
+ color: @primary-color;
+ }
+ }
+ .antd-pro-pages-dashboard-workplace-datetime {
+ color: @disabled-color;
+ flex: 0 0 auto;
+ float: right;
+ }
+ }
+}
+
+.antd-pro-pages-dashboard-workplace-datetime {
+ color: @disabled-color;
+}
+
+@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) {
+ .antd-pro-pages-dashboard-workplace-activeCard {
+ margin-bottom: 24px;
+ }
+ .antd-pro-pages-dashboard-workplace-members {
+ margin-bottom: 0;
+ }
+ .antd-pro-pages-dashboard-workplace-extraContent {
+ margin-left: -44px;
+ .antd-pro-pages-dashboard-workplace-statItem {
+ padding: 0 16px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-pages-dashboard-workplace-activeCard {
+ margin-bottom: 24px;
+ }
+ .antd-pro-pages-dashboard-workplace-members {
+ margin-bottom: 0;
+ }
+ .antd-pro-pages-dashboard-workplace-extraContent {
+ float: none;
+ margin-right: 0;
+ .antd-pro-pages-dashboard-workplace-statItem {
+ padding: 0 16px;
+ text-align: left;
+ &:after {
+ display: none;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-dashboard-workplace-extraContent {
+ margin-left: -16px;
+ }
+ .antd-pro-pages-dashboard-workplace-projectList {
+ .antd-pro-pages-dashboard-workplace-projectGrid {
+ width: 50%;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-dashboard-workplace-pageHeaderContent {
+ display: block;
+ .antd-pro-pages-dashboard-workplace-content {
+ margin-left: 0;
+ }
+ }
+ .antd-pro-pages-dashboard-workplace-extraContent {
+ .antd-pro-pages-dashboard-workplace-statItem {
+ float: none;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-pages-dashboard-workplace-projectList {
+ .antd-pro-pages-dashboard-workplace-projectGrid {
+ width: 100%;
+ }
+ }
+}
+
+.antd-pro-pages-exception-style-trigger {
+ background: 'red';
+ .ant-btn {
+ margin-right: 8px;
+ margin-bottom: 12px;
+ }
+}
+
+
+
+.antd-pro-pages-form-step-form-style-stepForm {
+ margin: 40px auto 0;
+ max-width: 500px;
+}
+
+.antd-pro-pages-form-step-form-style-stepFormText {
+ margin-bottom: 24px;
+ .ant-form-item-label,
+ .ant-form-item-control {
+ line-height: 22px;
+ }
+}
+
+.antd-pro-pages-form-step-form-style-result {
+ margin: 0 auto;
+ max-width: 560px;
+ padding: 24px 0 8px;
+}
+
+.antd-pro-pages-form-step-form-style-desc {
+ padding: 0 56px;
+ color: @text-color-secondary;
+ h3 {
+ font-size: 16px;
+ margin: 0 0 12px 0;
+ color: @text-color-secondary;
+ line-height: 32px;
+ }
+ h4 {
+ margin: 0 0 4px 0;
+ color: @text-color-secondary;
+ font-size: 14px;
+ line-height: 22px;
+ }
+ p {
+ margin-top: 0;
+ margin-bottom: 12px;
+ line-height: 22px;
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-form-step-form-style-desc {
+ padding: 0;
+ }
+}
+
+.antd-pro-pages-form-step-form-style-information {
+ line-height: 22px;
+ .ant-row:not(:last-child) {
+ margin-bottom: 24px;
+ }
+ .antd-pro-pages-form-step-form-style-label {
+ color: @heading-color;
+ text-align: right;
+ padding-right: 8px;
+ @media screen and (max-width: @screen-sm) {
+ text-align: left;
+ }
+ }
+}
+
+.antd-pro-pages-form-step-form-style-money {
+ font-family: 'Helvetica Neue', sans-serif;
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 14px;
+}
+
+.antd-pro-pages-form-step-form-style-uppercase {
+ font-size: 12px;
+}
+
+
+
+.antd-pro-pages-form-style-card {
+ margin-bottom: 24px;
+}
+
+.antd-pro-pages-form-style-heading {
+ font-size: 14px;
+ line-height: 22px;
+ margin: 0 0 16px 0;
+}
+
+.ant-steps {
+ max-width: 750px;
+ margin: 16px auto;
+}
+
+.antd-pro-pages-form-style-errorIcon {
+ cursor: pointer;
+ color: @error-color;
+ margin-right: 24px;
+ i {
+ margin-right: 4px;
+ }
+}
+
+.antd-pro-pages-form-style-errorPopover {
+ .ant-popover-inner-content {
+ padding: 0;
+ max-height: 290px;
+ overflow: auto;
+ min-width: 256px;
+ }
+}
+
+.antd-pro-pages-form-style-errorListItem {
+ list-style: none;
+ border-bottom: 1px solid @border-color-split;
+ padding: 8px 16px;
+ cursor: pointer;
+ transition: all 0.3s;
+ &:hover {
+ background: @primary-1;
+ }
+ &:last-child {
+ border: 0;
+ }
+ .antd-pro-pages-form-style-errorIcon {
+ color: @error-color;
+ float: left;
+ margin-top: 4px;
+ margin-right: 12px;
+ padding-bottom: 22px;
+ }
+ .antd-pro-pages-form-style-errorField {
+ font-size: 12px;
+ color: @text-color-secondary;
+ margin-top: 2px;
+ }
+}
+
+.antd-pro-pages-form-style-editable {
+ td {
+ padding-top: 13px !important;
+ padding-bottom: 12.5px !important;
+ }
+}
+
+// custom footer for fixed footer toolbar
+.antd-pro-pages-form-style-advancedForm + div {
+ padding-bottom: 64px;
+}
+
+.antd-pro-pages-form-style-advancedForm {
+ .ant-form .ant-row:last-child .ant-form-item {
+ margin-bottom: 24px;
+ }
+ .ant-table td {
+ transition: none !important;
+ }
+}
+
+.antd-pro-pages-form-style-optional {
+ color: @text-color-secondary;
+ font-style: normal;
+}
+
+
+
+
+.antd-pro-pages-list-basic-list-standardList {
+ .ant-card-head {
+ border-bottom: none;
+ }
+ .antd-pro-pages-list-basic-list-ant-card-head-title {
+ line-height: 32px;
+ padding: 24px 0;
+ }
+ .antd-pro-pages-list-basic-list-ant-card-extra {
+ padding: 24px 0;
+ }
+ .antd-pro-pages-list-basic-list-ant-list-pagination {
+ text-align: right;
+ margin-top: 24px;
+ }
+ .ant-avatar-lg {
+ width: 48px;
+ height: 48px;
+ line-height: 48px;
+ }
+ .headerInfo {
+ position: relative;
+ text-align: center;
+ & > span {
+ color: @text-color-secondary;
+ display: inline-block;
+ font-size: @font-size-base;
+ line-height: 22px;
+ margin-bottom: 4px;
+ }
+ & > p {
+ color: @heading-color;
+ font-size: 24px;
+ line-height: 32px;
+ margin: 0;
+ }
+ & > em {
+ background-color: @border-color-split;
+ position: absolute;
+ height: 56px;
+ width: 1px;
+ top: 0;
+ right: 0;
+ }
+ }
+ .listContent {
+ font-size: 0;
+ .listContentItem {
+ color: @text-color-secondary;
+ display: inline-block;
+ vertical-align: middle;
+ font-size: @font-size-base;
+ margin-left: 40px;
+ > span {
+ line-height: 20px;
+ }
+ > p {
+ margin-top: 4px;
+ margin-bottom: 0;
+ line-height: 22px;
+ }
+ }
+ }
+ .extraContentSearch {
+ margin-left: 16px;
+ width: 272px;
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .ant-list-item-content {
+ display: block;
+ flex: none;
+ width: 100%;
+ }
+ .antd-pro-pages-list-basic-list-ant-list-item-action {
+ margin-left: 0;
+ }
+ .antd-pro-pages-list-basic-list-listContent {
+ margin-left: 0;
+ & > div {
+ margin-left: 0;
+ }
+ }
+ .listCard {
+ :global {
+ .ant-card-head-title {
+ overflow: visible;
+ }
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .antd-pro-pages-list-basic-list-extraContentSearch {
+ margin-left: 0;
+ width: 100%;
+ }
+ .antd-pro-pages-list-basic-list-headerInfo {
+ margin-bottom: 16px;
+ & > em {
+ display: none;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .antd-pro-pages-list-basic-list-listContent {
+ & > div {
+ display: block;
+ }
+ & > div:last-child {
+ top: 0;
+ width: 100%;
+ }
+ }
+ }
+ .antd-pro-pages-list-basic-list-listCard {
+ .ant-radio-group {
+ display: block;
+ margin-bottom: 8px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-lg) and (min-width: @screen-md) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .antd-pro-pages-list-basic-list-listContent {
+ & > div {
+ display: block;
+ }
+ & > div:last-child {
+ top: 0;
+ width: 100%;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xl) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .antd-pro-pages-list-basic-list-listContent {
+ & > div {
+ margin-left: 24px;
+ }
+ & > div:last-child {
+ top: 0;
+ }
+ }
+ }
+}
+
+@media screen and (max-width: 1400px) {
+ .antd-pro-pages-list-basic-list-standardList {
+ .antd-pro-pages-list-basic-list-listContent {
+ text-align: right;
+ & > div:last-child {
+ top: 0;
+ }
+ }
+ }
+}
+
+.antd-pro-pages-list-basic-list-standardListForm {
+ .ant-form-item {
+ margin-bottom: 12px;
+ &:last-child {
+ padding-top: 4px;
+ margin-bottom: 32px;
+ }
+ }
+}
+
+.antd-pro-pages-list-basic-list-formResult {
+ width: 100%;
+ [class^='title'] {
+ margin-bottom: 8px;
+ }
+}
+
+
+
+
+.antd-pro-pages-list-card-list-cardList {
+ margin-bottom: -24px;
+
+ .antd-pro-pages-list-card-list-card {
+ .ant-card-meta-title {
+ margin-bottom: 12px;
+ & > a {
+ color: @heading-color;
+ display: inline-block;
+ max-width: 100%;
+ }
+ }
+ .ant-card-actions {
+ background: #f7f9fa;
+ }
+ .ant-card-body:hover {
+ .ant-card-meta-title > a {
+ color: @primary-color;
+ }
+ }
+ }
+ .antd-pro-pages-list-card-list-item {
+ height: 64px;
+ }
+ .ant-list .ant-list-item-content-single {
+ max-width: 100%;
+ }
+}
+
+.antd-pro-pages-list-card-list-extraImg {
+ margin-top: -60px;
+ text-align: center;
+ width: 195px;
+ img {
+ width: 100%;
+ }
+}
+
+.antd-pro-pages-list-card-list-newButton {
+ background-color: #fff;
+ border-color: @border-color-base;
+ border-radius: @border-radius-sm;
+ color: @text-color-secondary;
+ width: 100%;
+ height: 188px;
+}
+
+.antd-pro-pages-list-card-list-cardAvatar {
+ width: 48px;
+ height: 48px;
+ border-radius: 48px;
+}
+
+.antd-pro-pages-list-card-list-cardDescription {
+ .textOverflowMulti();
+}
+
+.antd-pro-pages-list-card-list-pageHeaderContent {
+ position: relative;
+}
+
+.antd-pro-pages-list-card-list-contentLink {
+ margin-top: 16px;
+ a {
+ margin-right: 32px;
+ img {
+ width: 24px;
+ }
+ }
+ img {
+ vertical-align: middle;
+ margin-right: 8px;
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-pages-list-card-list-contentLink {
+ a {
+ margin-right: 16px;
+ }
+ }
+}
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-list-card-list-extraImg {
+ display: none;
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-list-card-list-pageHeaderContent {
+ padding-bottom: 30px;
+ }
+ .antd-pro-pages-list-card-list-contentLink {
+ position: absolute;
+ left: 0;
+ bottom: -4px;
+ width: 1000px;
+ a {
+ margin-right: 16px;
+ }
+ img {
+ margin-right: 4px;
+ }
+ }
+}
+
+
+
+
+.antd-pro-pages-list-search-applications-filterCardList {
+ margin-bottom: -24px;
+ .ant-card-meta-content {
+ margin-top: 0;
+ }
+ // disabled white space
+ .ant-card-meta-avatar {
+ font-size: 0;
+ }
+ .ant-card-actions {
+ background: #f7f9fa;
+ }
+ .ant-list .ant-list-item-content-single {
+ max-width: 100%;
+ }
+ .cardInfo {
+ .clearfix();
+ margin-top: 16px;
+ margin-left: 40px;
+ & > div {
+ position: relative;
+ text-align: left;
+ float: left;
+ width: 50%;
+ p {
+ line-height: 32px;
+ font-size: 24px;
+ margin: 0;
+ }
+ p:first-child {
+ color: @text-color-secondary;
+ font-size: 12px;
+ line-height: 20px;
+ margin-bottom: 4px;
+ }
+ }
+ }
+}
+
+
+
+a.antd-pro-pages-list-search-articles-listItemMetaTitle {
+ color: @heading-color;
+}
+.antd-pro-pages-list-search-articles-listItemExtra {
+ width: 272px;
+ height: 1px;
+}
+.antd-pro-pages-list-search-articles-selfTrigger {
+ margin-left: 12px;
+}
+
+@media screen and (max-width: @screen-xs) {
+ .antd-pro-pages-list-search-articles-selfTrigger {
+ display: block;
+ margin-left: 0;
+ }
+}
+@media screen and (max-width: @screen-md) {
+ .antd-pro-pages-list-search-articles-selfTrigger {
+ display: block;
+ margin-left: 0;
+ }
+}
+@media screen and (max-width: @screen-lg) {
+ .antd-pro-pages-list-search-articles-listItemExtra {
+ width: 0;
+ height: 1px;
+ }
+}
+
+
+
+
+.antd-pro-pages-list-search-projects-coverCardList {
+ margin-bottom: -24px;
+
+ .antd-pro-pages-list-search-projects-card {
+ .ant-card-meta-title {
+ margin-bottom: 4px;
+ & > a {
+ color: @heading-color;
+ display: inline-block;
+ max-width: 100%;
+ }
+ }
+ .antd-pro-pages-list-search-projects-ant-card-meta-description {
+ height: 44px;
+ line-height: 22px;
+ overflow: hidden;
+ }
+
+ &:hover {
+ :global {
+ .ant-card-meta-title > a {
+ color: @primary-color;
+ }
+ }
+ }
+ }
+
+ .antd-pro-pages-list-search-projects-cardItemContent {
+ display: flex;
+ margin-top: 16px;
+ margin-bottom: -4px;
+ line-height: 20px;
+ height: 20px;
+ & > span {
+ color: @text-color-secondary;
+ flex: 1;
+ font-size: 12px;
+ }
+ .antd-pro-pages-list-search-projects-avatarList {
+ flex: 0 1 auto;
+ }
+ }
+ .antd-pro-pages-list-search-projects-cardList {
+ margin-top: 24px;
+ }
+ .ant-list .ant-list-item-content-single {
+ max-width: 100%;
+ }
+}
+
+
+
+
+.antd-pro-pages-list-table-list-tableList {
+ .antd-pro-pages-list-table-list-tableListOperator {
+ margin-bottom: 16px;
+ button {
+ margin-right: 8px;
+ }
+ }
+}
+
+.antd-pro-pages-list-table-list-tableListForm {
+ .ant-form-item {
+ margin-bottom: 24px;
+ margin-right: 0;
+ display: flex;
+ > .ant-form-item-label {
+ width: auto;
+ line-height: 32px;
+ padding-right: 8px;
+ }
+ .ant-form-item-control {
+ line-height: 32px;
+ }
+ }
+ .antd-pro-pages-list-table-list-ant-form-item-control-wrapper {
+ flex: 1;
+ }
+ .submitButtons {
+ display: block;
+ white-space: nowrap;
+ margin-bottom: 24px;
+ }
+}
+
+@media screen and (max-width: @screen-lg) {
+ .ant-form-item {
+ margin-right: 24px;
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .ant-form-item {
+ margin-right: 8px;
+ }
+}
+
+
+
+.antd-pro-pages-profile-advanced-profile-headerList {
+ margin-bottom: 4px;
+}
+
+.antd-pro-pages-profile-advanced-profile-tabsCard {
+ .ant-card-head {
+ padding: 0 16px;
+ }
+}
+
+.antd-pro-pages-profile-advanced-profile-noData {
+ color: @disabled-color;
+ text-align: center;
+ line-height: 64px;
+ font-size: 16px;
+ i {
+ font-size: 24px;
+ margin-right: 16px;
+ position: relative;
+ top: 3px;
+ }
+}
+
+.antd-pro-pages-profile-advanced-profile-heading {
+ color: @heading-color;
+ font-size: 20px;
+}
+
+.antd-pro-pages-profile-advanced-profile-stepDescription {
+ font-size: 14px;
+ position: relative;
+ left: 38px;
+ padding-top: 8px;
+ text-align: left;
+
+ > div {
+ margin-top: 8px;
+ margin-bottom: 4px;
+ }
+}
+
+.antd-pro-pages-profile-advanced-profile-textSecondary {
+ color: @text-color-secondary;
+}
+
+@media screen and (max-width: @screen-sm) {
+ .antd-pro-pages-profile-advanced-profile-stepDescription {
+ left: 8px;
+ }
+}
+
+
+
+.antd-pro-pages-profile-basic-profile-title {
+ color: @heading-color;
+ font-size: 16px;
+ font-weight: 500;
+ margin-bottom: 16px;
+}
+
+
+
+.antd-pro-pages-user-login-main {
+ width: 368px;
+ margin: 0 auto;
+ @media screen and (max-width: @screen-sm) {
+ width: 95%;
+ }
+
+ .antd-pro-pages-user-login-icon {
+ font-size: 24px;
+ color: rgba(0, 0, 0, 0.2);
+ margin-left: 16px;
+ vertical-align: middle;
+ cursor: pointer;
+ transition: color 0.3s;
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+
+ .antd-pro-pages-user-login-other {
+ text-align: left;
+ margin-top: 24px;
+ line-height: 22px;
+
+ .antd-pro-pages-user-login-register {
+ float: right;
+ }
+ }
+}
+
+
+
+.antd-pro-pages-user-register-main {
+ width: 368px;
+ margin: 0 auto;
+ .ant-form-item {
+ margin-bottom: 24px;
+ }
+
+ h3 {
+ font-size: 16px;
+ margin-bottom: 20px;
+ }
+
+ .antd-pro-pages-user-register-getCaptcha {
+ display: block;
+ width: 100%;
+ }
+
+ .antd-pro-pages-user-register-submit {
+ width: 50%;
+ }
+
+ .antd-pro-pages-user-register-login {
+ float: right;
+ line-height: @btn-height-lg;
+ }
+}
+
+.antd-pro-pages-user-register-success,
+.antd-pro-pages-user-register-warning,
+.antd-pro-pages-user-register-error {
+ transition: color 0.3s;
+}
+
+.antd-pro-pages-user-register-success {
+ color: @success-color;
+}
+
+.antd-pro-pages-user-register-warning {
+ color: @warning-color;
+}
+
+.antd-pro-pages-user-register-error {
+ color: @error-color;
+}
+
+.antd-pro-pages-user-register-progress-pass > .antd-pro-pages-user-register-progress {
+ .ant-progress-bg {
+ background-color: @warning-color;
+ }
+}
+
+.antd-pro-pages-user-register-result-registerResult {
+ .anticon {
+ font-size: 64px;
+ }
+ .antd-pro-pages-user-register-result-title {
+ margin-top: 32px;
+ font-size: 20px;
+ line-height: 28px;
+ }
+ .antd-pro-pages-user-register-result-actions {
+ margin-top: 40px;
+ a + a {
+ margin-left: 8px;
+ }
+ }
+}
+
+.textOverflow() {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ white-space: nowrap;
+}
+
+.textOverflowMulti(@line: 3, @bg: #fff) {
+ overflow: hidden;
+ position: relative;
+ line-height: 1.5em;
+ max-height: @line * 1.5em;
+ text-align: justify;
+ margin-right: -1em;
+ padding-right: 1em;
+ &:before {
+ background: @bg;
+ content: '...';
+ padding: 0 1px;
+ position: absolute;
+ right: 14px;
+ bottom: 0;
+ }
+ &:after {
+ background: white;
+ content: '';
+ margin-top: 0.2em;
+ position: absolute;
+ right: 14px;
+ width: 1em;
+ height: 1em;
+ }
+}
+
+// mixins for clearfix
+// ------------------------
+.clearfix() {
+ zoom: 1;
+ &:before,
+ &:after {
+ content: ' ';
+ display: table;
+ }
+ &:after {
+ clear: both;
+ visibility: hidden;
+ font-size: 0;
+ height: 0;
+ }
+}
diff --git a/.umirc.js b/.umirc.js
deleted file mode 100644
index 627d6da8..00000000
--- a/.umirc.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const path = require("path");
-export default {
- plugins: [
- [
- "umi-plugin-react",
- {
- antd: true,
- dva: true,
- routes: {
- update(routes) {
- return [...require("./src/pages/_routes"), ...routes];
- }
- },
- dll: {
- exclude: [],
- include: ["dva", "dva/router", "dva/saga", "dva/fetch", "antd/es"]
- }
- }
- ],
- [
- "umi-plugin-authorize",
- {
- authorize: [
- {
- guard: ["./src/components/Authorized/AuthorizedRoute.js"],
- include: /\//,
- exclude: /\/User/
- }
- ]
- }
- ]
- ],
- targets: {
- ie: 10
- },
- history: "hash",
- publicPath: "",
- alias: {
- utils: path.resolve(__dirname, "src/utils/"),
- assets: path.resolve(__dirname, "src/assets/"),
- components: path.resolve(__dirname, "src/components/"),
- common: path.resolve(__dirname, "src/common/"),
- services: path.resolve(__dirname, "src/services/")
- },
- proxy: {
- "/api/v1/th": {
- target: "http://xxx.com",
- changeOrigin: true,
- pathRewrite: { "^/api/v1/th": "" }
- }
- },
- theme: "./src/theme.js",
- extraBabelPlugins: [
- ["import", { libraryName: "antd", libraryDirectory: "es", style: true }]
- ]
-};
diff --git a/config/config.js b/config/config.js
new file mode 100644
index 00000000..e74d14f5
--- /dev/null
+++ b/config/config.js
@@ -0,0 +1,131 @@
+// https://umijs.org/config/
+import os from 'os';
+import pageRoutes from './router.config';
+import webpackPlugin from './plugin.config';
+import defaultSettings from '../src/defaultSettings';
+
+const plugins = [
+ [
+ 'umi-plugin-react',
+ {
+ antd: true,
+ dva: {
+ hmr: true,
+ },
+ targets: {
+ ie: 11,
+ },
+ locale: {
+ enable: true, // default false
+ default: 'zh-CN', // default zh-CN
+ baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
+ },
+ routes: {
+ update(routes) {
+ return [...require("../src/pages/_routes"), ...routes];
+ }
+ },
+ dynamicImport: {
+ loadingComponent: './components/PageLoading/index',
+ },
+ pwa: {
+ workboxPluginMode: 'InjectManifest',
+ workboxOptions: {
+ importWorkboxFrom: 'local',
+ },
+ },
+ ...(!process.env.TEST && os.platform() === 'darwin'
+ ? {
+ dll: {
+ include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
+ exclude: ['@babel/runtime'],
+ },
+ hardSource: true,
+ }
+ : {}),
+ },
+ ],
+ [
+ "umi-plugin-authorize",
+ {
+ authorize: [
+ {
+ guard: ["./src/pages/Authorized"],
+ include: /\//,
+ exclude: /\/User/
+ }
+ ]
+ }
+ ]
+];
+
+// 针对 preview.pro.ant.design 的 GA 统计代码
+// 业务上不需要这个
+if (process.env.APP_TYPE === 'site') {
+ plugins.push([
+ 'umi-plugin-ga',
+ {
+ code: 'UA-72788897-6',
+ },
+ ]);
+}
+
+export default {
+ // add for transfer to umi
+ plugins,
+ targets: {
+ ie: 11,
+ },
+ define: {
+ APP_TYPE: process.env.APP_TYPE || '',
+ },
+ // 路由配置
+ // routes: pageRoutes,
+ // Theme for antd
+ // https://ant.design/docs/react/customize-theme-cn
+ theme: {
+ 'primary-color': defaultSettings.primaryColor,
+ },
+ externals: {
+ '@antv/data-set': 'DataSet',
+ },
+ // proxy: {
+ // '/server/api/': {
+ // target: 'https://preview.pro.ant.design/',
+ // changeOrigin: true,
+ // pathRewrite: { '^/server': '' },
+ // },
+ // },
+ ignoreMomentLocale: true,
+ lessLoaderOptions: {
+ javascriptEnabled: true,
+ },
+ disableRedirectHoist: true,
+ cssLoaderOptions: {
+ modules: true,
+ getLocalIdent: (context, localIdentName, localName) => {
+ if (
+ context.resourcePath.includes('node_modules') ||
+ context.resourcePath.includes('ant.design.pro.less') ||
+ context.resourcePath.includes('global.less')
+ ) {
+ return localName;
+ }
+ const match = context.resourcePath.match(/src(.*)/);
+ if (match && match[1]) {
+ const antdProPath = match[1].replace('.less', '');
+ const arr = antdProPath
+ .split('/')
+ .map(a => a.replace(/([A-Z])/g, '-$1'))
+ .map(a => a.toLowerCase());
+ return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
+ }
+ return localName;
+ },
+ },
+ manifest: {
+ basePath: '/',
+ },
+
+ chainWebpack: webpackPlugin,
+};
diff --git a/config/plugin.config.js b/config/plugin.config.js
new file mode 100644
index 00000000..b9e842d2
--- /dev/null
+++ b/config/plugin.config.js
@@ -0,0 +1,33 @@
+// Change theme plugin
+
+import MergeLessPlugin from 'antd-pro-merge-less';
+import AntDesignThemePlugin from 'antd-theme-webpack-plugin';
+import path from 'path';
+
+export default config => {
+ // pro 和 开发环境再添加这个插件
+ if (process.env.APP_TYPE === 'site' || process.env.NODE_ENV !== 'production') {
+ // 将所有 less 合并为一个供 themePlugin使用
+ const outFile = path.join(__dirname, '../.temp/ant-design-pro.less');
+ const stylesDir = path.join(__dirname, '../src/');
+
+ config.plugin('merge-less').use(MergeLessPlugin, [
+ {
+ stylesDir,
+ outFile,
+ },
+ ]);
+
+ config.plugin('ant-design-theme').use(AntDesignThemePlugin, [
+ {
+ antDir: path.join(__dirname, '../node_modules/antd'),
+ stylesDir,
+ varFile: path.join(__dirname, '../node_modules/antd/lib/style/themes/default.less'),
+ mainLessFile: outFile, // themeVariables: ['@primary-color'],
+ indexFileName: 'index.html',
+ generateOne: true,
+ lessUrl: 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js',
+ },
+ ]);
+ }
+};
diff --git a/config/router.config.js b/config/router.config.js
new file mode 100644
index 00000000..656027d2
--- /dev/null
+++ b/config/router.config.js
@@ -0,0 +1,263 @@
+export default [
+ // user
+ {
+ path: '/user',
+ component: '../layouts/UserLayout',
+ routes: [
+ { path: '/user', redirect: '/user/login' },
+ { path: '/user/login', component: './User/Login' },
+ { path: '/user/register', component: './User/Register' },
+ { path: '/user/register-result', component: './User/RegisterResult' },
+ ],
+ },
+ // app
+ {
+ path: '/',
+ component: '../layouts/BasicLayout',
+ Routes: ['src/pages/Authorized'],
+ authority: ['admin', 'user'],
+ routes: [
+ // dashboard
+ { path: '/', redirect: '/dashboard/analysis' },
+ {
+ path: '/dashboard',
+ name: 'dashboard',
+ icon: 'dashboard',
+ routes: [
+ {
+ path: '/dashboard/analysis',
+ name: 'analysis',
+ component: './Dashboard/Analysis',
+ },
+ {
+ path: '/dashboard/monitor',
+ name: 'monitor',
+ component: './Dashboard/Monitor',
+ },
+ {
+ path: '/dashboard/workplace',
+ name: 'workplace',
+ component: './Dashboard/Workplace',
+ },
+ ],
+ },
+ // forms
+ {
+ path: '/form',
+ icon: 'form',
+ name: 'form',
+ routes: [
+ {
+ path: '/form/basic-form',
+ name: 'basicform',
+ component: './Forms/BasicForm',
+ },
+ {
+ path: '/form/step-form',
+ name: 'stepform',
+ component: './Forms/StepForm',
+ hideChildrenInMenu: true,
+ routes: [
+ {
+ path: '/form/step-form',
+ redirect: '/form/step-form/info',
+ },
+ {
+ path: '/form/step-form/info',
+ name: 'info',
+ component: './Forms/StepForm/Step1',
+ },
+ {
+ path: '/form/step-form/confirm',
+ name: 'confirm',
+ component: './Forms/StepForm/Step2',
+ },
+ {
+ path: '/form/step-form/result',
+ name: 'result',
+ component: './Forms/StepForm/Step3',
+ },
+ ],
+ },
+ {
+ path: '/form/advanced-form',
+ name: 'advancedform',
+ authority: ['admin'],
+ component: './Forms/AdvancedForm',
+ },
+ ],
+ },
+ // list
+ {
+ path: '/list',
+ icon: 'table',
+ name: 'list',
+ routes: [
+ {
+ path: '/list/table-list',
+ name: 'searchtable',
+ component: './List/TableList',
+ },
+ {
+ path: '/list/basic-list',
+ name: 'basiclist',
+ component: './List/BasicList',
+ },
+ {
+ path: '/list/card-list',
+ name: 'cardlist',
+ component: './List/CardList',
+ },
+ {
+ path: '/list/search',
+ name: 'searchlist',
+ component: './List/List',
+ routes: [
+ {
+ path: '/list/search',
+ redirect: '/list/search/articles',
+ },
+ {
+ path: '/list/search/articles',
+ name: 'articles',
+ component: './List/Articles',
+ },
+ {
+ path: '/list/search/projects',
+ name: 'projects',
+ component: './List/Projects',
+ },
+ {
+ path: '/list/search/applications',
+ name: 'applications',
+ component: './List/Applications',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ path: '/profile',
+ name: 'profile',
+ icon: 'profile',
+ routes: [
+ // profile
+ {
+ path: '/profile/basic',
+ name: 'basic',
+ component: './Profile/BasicProfile',
+ },
+ {
+ path: '/profile/advanced',
+ name: 'advanced',
+ authority: ['admin'],
+ component: './Profile/AdvancedProfile',
+ },
+ ],
+ },
+ {
+ name: 'result',
+ icon: 'check-circle-o',
+ path: '/result',
+ routes: [
+ // result
+ {
+ path: '/result/success',
+ name: 'success',
+ component: './Result/Success',
+ },
+ { path: '/result/fail', name: 'fail', component: './Result/Error' },
+ ],
+ },
+ {
+ name: 'exception',
+ icon: 'warning',
+ path: '/exception',
+ routes: [
+ // exception
+ {
+ path: '/exception/403',
+ name: 'not-permission',
+ component: './Exception/403',
+ },
+ {
+ path: '/exception/404',
+ name: 'not-find',
+ component: './Exception/404',
+ },
+ {
+ path: '/exception/500',
+ name: 'server-error',
+ component: './Exception/500',
+ },
+ {
+ path: '/exception/trigger',
+ name: 'trigger',
+ hideInMenu: true,
+ component: './Exception/TriggerException',
+ },
+ ],
+ },
+ {
+ name: 'account',
+ icon: 'user',
+ path: '/account',
+ routes: [
+ {
+ path: '/account/center',
+ name: 'center',
+ component: './Account/Center/Center',
+ routes: [
+ {
+ path: '/account/center',
+ redirect: '/account/center/articles',
+ },
+ {
+ path: '/account/center/articles',
+ component: './Account/Center/Articles',
+ },
+ {
+ path: '/account/center/applications',
+ component: './Account/Center/Applications',
+ },
+ {
+ path: '/account/center/projects',
+ component: './Account/Center/Projects',
+ },
+ ],
+ },
+ {
+ path: '/account/settings',
+ name: 'settings',
+ component: './Account/Settings/Info',
+ routes: [
+ {
+ path: '/account/settings',
+ redirect: '/account/settings/base',
+ },
+ {
+ path: '/account/settings/base',
+ component: './Account/Settings/BaseView',
+ },
+ {
+ path: '/account/settings/security',
+ component: './Account/Settings/SecurityView',
+ },
+ {
+ path: '/account/settings/binding',
+ component: './Account/Settings/BindingView',
+ },
+ {
+ path: '/account/settings/notification',
+ component: './Account/Settings/NotificationView',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ component: '404',
+ },
+ ],
+ },
+];
diff --git a/mock/.gitkeep b/mock/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/mock/api.js b/mock/api.js
index a274afb9..f1173bba 100644
--- a/mock/api.js
+++ b/mock/api.js
@@ -1,4 +1,4 @@
-import { parse } from 'url';
+import mockjs from 'mockjs';
const titles = [
'Alipay',
@@ -37,7 +37,7 @@ const avatars2 = [
const covers = [
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
- 'https://gw.alipayobjects.com/zos/rmsportal/uVZonEtjWwmUZPBQfycs.png',
+ 'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png',
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
];
const desc = [
@@ -61,7 +61,7 @@ const user = [
'仲尼',
];
-export function fakeList(count) {
+function fakeList(count) {
const list = [];
for (let i = 0; i < count; i += 1) {
list.push({
@@ -69,7 +69,7 @@ export function fakeList(count) {
owner: user[i % 10],
title: titles[i % 8],
avatar: avatars[i % 8],
- cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - i % 4],
+ cover: parseInt(i / 4, 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
status: ['active', 'exception', 'normal'][i % 3],
percent: Math.ceil(Math.random() * 50) + 50,
logo: avatars[i % 8],
@@ -90,14 +90,17 @@ export function fakeList(count) {
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
name: '曲丽丽',
+ id: 'member1',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
name: '王昭君',
+ id: 'member2',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
name: '董娜娜',
+ id: 'member3',
},
],
});
@@ -106,26 +109,51 @@ export function fakeList(count) {
return list;
}
-export function getFakeList(req, res, u) {
- let url = u;
- if (!url || Object.prototype.toString.call(url) !== '[object String]') {
- url = req.url; // eslint-disable-line
- }
+let sourceData;
- const params = parse(url, true).query;
+function getFakeList(req, res) {
+ const params = req.query;
const count = params.count * 1 || 20;
const result = fakeList(count);
+ sourceData = result;
+ return res.json(result);
+}
- if (res && res.json) {
- res.json(result);
- } else {
- return result;
+function postFakeList(req, res) {
+ const { /* url = '', */ body } = req;
+ // const params = getUrlParams(url);
+ const { method, id } = body;
+ // const count = (params.count * 1) || 20;
+ let result = sourceData;
+
+ switch (method) {
+ case 'delete':
+ result = result.filter(item => item.id !== id);
+ break;
+ case 'update':
+ result.forEach((item, i) => {
+ if (item.id === id) {
+ result[i] = Object.assign(item, body);
+ }
+ });
+ break;
+ case 'post':
+ result.unshift({
+ body,
+ id: `fake-list-${result.length}`,
+ createdAt: new Date().getTime(),
+ });
+ break;
+ default:
+ break;
}
+
+ return res.json(result);
}
-export const getNotice = [
+const getNotice = [
{
id: 'xxx1',
title: titles[0],
@@ -188,7 +216,7 @@ export const getNotice = [
},
];
-export const getActivities = [
+const getActivities = [
{
id: 'trend-1',
updatedAt: new Date(),
@@ -289,8 +317,20 @@ export const getActivities = [
},
];
+function getFakeCaptcha(req, res) {
+ return res.json('captcha-xxx');
+}
+
export default {
- getNotice,
- getActivities,
- getFakeList,
+ 'GET /api/project/notice': getNotice,
+ 'GET /api/activities': getActivities,
+ 'POST /api/forms': (req, res) => {
+ res.send({ message: 'Ok' });
+ },
+ 'GET /api/tags': mockjs.mock({
+ 'list|100': [{ name: '@city', 'value|1-100': 150, 'type|0-2': 1 }],
+ }),
+ 'GET /api/fake_list': getFakeList,
+ 'POST /api/fake_list': postFakeList,
+ 'GET /api/captcha': getFakeCaptcha,
};
diff --git a/mock/chart.js b/mock/chart.js
index 55c8a412..65037758 100644
--- a/mock/chart.js
+++ b/mock/chart.js
@@ -98,15 +98,15 @@ const salesTypeDataOffline = [
y: 99,
},
{
- x: '个护健康',
+ x: '食用酒水',
y: 188,
},
{
- x: '服饰箱包',
+ x: '个护健康',
y: 344,
},
{
- x: '母婴产品',
+ x: '服饰箱包',
y: 255,
},
{
@@ -118,7 +118,7 @@ const salesTypeDataOffline = [
const offlineData = [];
for (let i = 0; i < 10; i += 1) {
offlineData.push({
- name: `门店${i}`,
+ name: `Stores ${i}`,
cvr: Math.ceil(Math.random() * 9) / 10,
});
}
@@ -158,7 +158,6 @@ const radarOriginData = [
},
];
-//
const radarData = [];
const radarTitleMap = {
ref: '引用',
@@ -179,7 +178,7 @@ radarOriginData.forEach(item => {
});
});
-export const getFakeChartData = {
+const getFakeChartData = {
visitData,
visitData2,
salesData,
@@ -193,5 +192,5 @@ export const getFakeChartData = {
};
export default {
- getFakeChartData,
+ 'GET /api/fake_chart_data': getFakeChartData,
};
diff --git a/mock/geographic.js b/mock/geographic.js
new file mode 100644
index 00000000..e7772e8d
--- /dev/null
+++ b/mock/geographic.js
@@ -0,0 +1,15 @@
+import city from './geographic/city.json';
+import province from './geographic/province.json';
+
+function getProvince(req, res) {
+ return res.json(province);
+}
+
+function getCity(req, res) {
+ return res.json(city[req.params.province]);
+}
+
+export default {
+ 'GET /api/geographic/province': getProvince,
+ 'GET /api/geographic/city/:province': getCity,
+};
diff --git a/mock/geographic/city.json b/mock/geographic/city.json
new file mode 100644
index 00000000..29783747
--- /dev/null
+++ b/mock/geographic/city.json
@@ -0,0 +1,1784 @@
+{
+ "110000": [
+ {
+ "province": "北京市",
+ "name": "市辖区",
+ "id": "110100"
+ }
+ ],
+ "120000": [
+ {
+ "province": "天津市",
+ "name": "市辖区",
+ "id": "120100"
+ }
+ ],
+ "130000": [
+ {
+ "province": "河北省",
+ "name": "石家庄市",
+ "id": "130100"
+ },
+ {
+ "province": "河北省",
+ "name": "唐山市",
+ "id": "130200"
+ },
+ {
+ "province": "河北省",
+ "name": "秦皇岛市",
+ "id": "130300"
+ },
+ {
+ "province": "河北省",
+ "name": "邯郸市",
+ "id": "130400"
+ },
+ {
+ "province": "河北省",
+ "name": "邢台市",
+ "id": "130500"
+ },
+ {
+ "province": "河北省",
+ "name": "保定市",
+ "id": "130600"
+ },
+ {
+ "province": "河北省",
+ "name": "张家口市",
+ "id": "130700"
+ },
+ {
+ "province": "河北省",
+ "name": "承德市",
+ "id": "130800"
+ },
+ {
+ "province": "河北省",
+ "name": "沧州市",
+ "id": "130900"
+ },
+ {
+ "province": "河北省",
+ "name": "廊坊市",
+ "id": "131000"
+ },
+ {
+ "province": "河北省",
+ "name": "衡水市",
+ "id": "131100"
+ },
+ {
+ "province": "河北省",
+ "name": "省直辖县级行政区划",
+ "id": "139000"
+ }
+ ],
+ "140000": [
+ {
+ "province": "山西省",
+ "name": "太原市",
+ "id": "140100"
+ },
+ {
+ "province": "山西省",
+ "name": "大同市",
+ "id": "140200"
+ },
+ {
+ "province": "山西省",
+ "name": "阳泉市",
+ "id": "140300"
+ },
+ {
+ "province": "山西省",
+ "name": "长治市",
+ "id": "140400"
+ },
+ {
+ "province": "山西省",
+ "name": "晋城市",
+ "id": "140500"
+ },
+ {
+ "province": "山西省",
+ "name": "朔州市",
+ "id": "140600"
+ },
+ {
+ "province": "山西省",
+ "name": "晋中市",
+ "id": "140700"
+ },
+ {
+ "province": "山西省",
+ "name": "运城市",
+ "id": "140800"
+ },
+ {
+ "province": "山西省",
+ "name": "忻州市",
+ "id": "140900"
+ },
+ {
+ "province": "山西省",
+ "name": "临汾市",
+ "id": "141000"
+ },
+ {
+ "province": "山西省",
+ "name": "吕梁市",
+ "id": "141100"
+ }
+ ],
+ "150000": [
+ {
+ "province": "内蒙古自治区",
+ "name": "呼和浩特市",
+ "id": "150100"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "包头市",
+ "id": "150200"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "乌海市",
+ "id": "150300"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "赤峰市",
+ "id": "150400"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "通辽市",
+ "id": "150500"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "鄂尔多斯市",
+ "id": "150600"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "呼伦贝尔市",
+ "id": "150700"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "巴彦淖尔市",
+ "id": "150800"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "乌兰察布市",
+ "id": "150900"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "兴安盟",
+ "id": "152200"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "锡林郭勒盟",
+ "id": "152500"
+ },
+ {
+ "province": "内蒙古自治区",
+ "name": "阿拉善盟",
+ "id": "152900"
+ }
+ ],
+ "210000": [
+ {
+ "province": "辽宁省",
+ "name": "沈阳市",
+ "id": "210100"
+ },
+ {
+ "province": "辽宁省",
+ "name": "大连市",
+ "id": "210200"
+ },
+ {
+ "province": "辽宁省",
+ "name": "鞍山市",
+ "id": "210300"
+ },
+ {
+ "province": "辽宁省",
+ "name": "抚顺市",
+ "id": "210400"
+ },
+ {
+ "province": "辽宁省",
+ "name": "本溪市",
+ "id": "210500"
+ },
+ {
+ "province": "辽宁省",
+ "name": "丹东市",
+ "id": "210600"
+ },
+ {
+ "province": "辽宁省",
+ "name": "锦州市",
+ "id": "210700"
+ },
+ {
+ "province": "辽宁省",
+ "name": "营口市",
+ "id": "210800"
+ },
+ {
+ "province": "辽宁省",
+ "name": "阜新市",
+ "id": "210900"
+ },
+ {
+ "province": "辽宁省",
+ "name": "辽阳市",
+ "id": "211000"
+ },
+ {
+ "province": "辽宁省",
+ "name": "盘锦市",
+ "id": "211100"
+ },
+ {
+ "province": "辽宁省",
+ "name": "铁岭市",
+ "id": "211200"
+ },
+ {
+ "province": "辽宁省",
+ "name": "朝阳市",
+ "id": "211300"
+ },
+ {
+ "province": "辽宁省",
+ "name": "葫芦岛市",
+ "id": "211400"
+ }
+ ],
+ "220000": [
+ {
+ "province": "吉林省",
+ "name": "长春市",
+ "id": "220100"
+ },
+ {
+ "province": "吉林省",
+ "name": "吉林市",
+ "id": "220200"
+ },
+ {
+ "province": "吉林省",
+ "name": "四平市",
+ "id": "220300"
+ },
+ {
+ "province": "吉林省",
+ "name": "辽源市",
+ "id": "220400"
+ },
+ {
+ "province": "吉林省",
+ "name": "通化市",
+ "id": "220500"
+ },
+ {
+ "province": "吉林省",
+ "name": "白山市",
+ "id": "220600"
+ },
+ {
+ "province": "吉林省",
+ "name": "松原市",
+ "id": "220700"
+ },
+ {
+ "province": "吉林省",
+ "name": "白城市",
+ "id": "220800"
+ },
+ {
+ "province": "吉林省",
+ "name": "延边朝鲜族自治州",
+ "id": "222400"
+ }
+ ],
+ "230000": [
+ {
+ "province": "黑龙江省",
+ "name": "哈尔滨市",
+ "id": "230100"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "齐齐哈尔市",
+ "id": "230200"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "鸡西市",
+ "id": "230300"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "鹤岗市",
+ "id": "230400"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "双鸭山市",
+ "id": "230500"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "大庆市",
+ "id": "230600"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "伊春市",
+ "id": "230700"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "佳木斯市",
+ "id": "230800"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "七台河市",
+ "id": "230900"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "牡丹江市",
+ "id": "231000"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "黑河市",
+ "id": "231100"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "绥化市",
+ "id": "231200"
+ },
+ {
+ "province": "黑龙江省",
+ "name": "大兴安岭地区",
+ "id": "232700"
+ }
+ ],
+ "310000": [
+ {
+ "province": "上海市",
+ "name": "市辖区",
+ "id": "310100"
+ }
+ ],
+ "320000": [
+ {
+ "province": "江苏省",
+ "name": "南京市",
+ "id": "320100"
+ },
+ {
+ "province": "江苏省",
+ "name": "无锡市",
+ "id": "320200"
+ },
+ {
+ "province": "江苏省",
+ "name": "徐州市",
+ "id": "320300"
+ },
+ {
+ "province": "江苏省",
+ "name": "常州市",
+ "id": "320400"
+ },
+ {
+ "province": "江苏省",
+ "name": "苏州市",
+ "id": "320500"
+ },
+ {
+ "province": "江苏省",
+ "name": "南通市",
+ "id": "320600"
+ },
+ {
+ "province": "江苏省",
+ "name": "连云港市",
+ "id": "320700"
+ },
+ {
+ "province": "江苏省",
+ "name": "淮安市",
+ "id": "320800"
+ },
+ {
+ "province": "江苏省",
+ "name": "盐城市",
+ "id": "320900"
+ },
+ {
+ "province": "江苏省",
+ "name": "扬州市",
+ "id": "321000"
+ },
+ {
+ "province": "江苏省",
+ "name": "镇江市",
+ "id": "321100"
+ },
+ {
+ "province": "江苏省",
+ "name": "泰州市",
+ "id": "321200"
+ },
+ {
+ "province": "江苏省",
+ "name": "宿迁市",
+ "id": "321300"
+ }
+ ],
+ "330000": [
+ {
+ "province": "浙江省",
+ "name": "杭州市",
+ "id": "330100"
+ },
+ {
+ "province": "浙江省",
+ "name": "宁波市",
+ "id": "330200"
+ },
+ {
+ "province": "浙江省",
+ "name": "温州市",
+ "id": "330300"
+ },
+ {
+ "province": "浙江省",
+ "name": "嘉兴市",
+ "id": "330400"
+ },
+ {
+ "province": "浙江省",
+ "name": "湖州市",
+ "id": "330500"
+ },
+ {
+ "province": "浙江省",
+ "name": "绍兴市",
+ "id": "330600"
+ },
+ {
+ "province": "浙江省",
+ "name": "金华市",
+ "id": "330700"
+ },
+ {
+ "province": "浙江省",
+ "name": "衢州市",
+ "id": "330800"
+ },
+ {
+ "province": "浙江省",
+ "name": "舟山市",
+ "id": "330900"
+ },
+ {
+ "province": "浙江省",
+ "name": "台州市",
+ "id": "331000"
+ },
+ {
+ "province": "浙江省",
+ "name": "丽水市",
+ "id": "331100"
+ }
+ ],
+ "340000": [
+ {
+ "province": "安徽省",
+ "name": "合肥市",
+ "id": "340100"
+ },
+ {
+ "province": "安徽省",
+ "name": "芜湖市",
+ "id": "340200"
+ },
+ {
+ "province": "安徽省",
+ "name": "蚌埠市",
+ "id": "340300"
+ },
+ {
+ "province": "安徽省",
+ "name": "淮南市",
+ "id": "340400"
+ },
+ {
+ "province": "安徽省",
+ "name": "马鞍山市",
+ "id": "340500"
+ },
+ {
+ "province": "安徽省",
+ "name": "淮北市",
+ "id": "340600"
+ },
+ {
+ "province": "安徽省",
+ "name": "铜陵市",
+ "id": "340700"
+ },
+ {
+ "province": "安徽省",
+ "name": "安庆市",
+ "id": "340800"
+ },
+ {
+ "province": "安徽省",
+ "name": "黄山市",
+ "id": "341000"
+ },
+ {
+ "province": "安徽省",
+ "name": "滁州市",
+ "id": "341100"
+ },
+ {
+ "province": "安徽省",
+ "name": "阜阳市",
+ "id": "341200"
+ },
+ {
+ "province": "安徽省",
+ "name": "宿州市",
+ "id": "341300"
+ },
+ {
+ "province": "安徽省",
+ "name": "六安市",
+ "id": "341500"
+ },
+ {
+ "province": "安徽省",
+ "name": "亳州市",
+ "id": "341600"
+ },
+ {
+ "province": "安徽省",
+ "name": "池州市",
+ "id": "341700"
+ },
+ {
+ "province": "安徽省",
+ "name": "宣城市",
+ "id": "341800"
+ }
+ ],
+ "350000": [
+ {
+ "province": "福建省",
+ "name": "福州市",
+ "id": "350100"
+ },
+ {
+ "province": "福建省",
+ "name": "厦门市",
+ "id": "350200"
+ },
+ {
+ "province": "福建省",
+ "name": "莆田市",
+ "id": "350300"
+ },
+ {
+ "province": "福建省",
+ "name": "三明市",
+ "id": "350400"
+ },
+ {
+ "province": "福建省",
+ "name": "泉州市",
+ "id": "350500"
+ },
+ {
+ "province": "福建省",
+ "name": "漳州市",
+ "id": "350600"
+ },
+ {
+ "province": "福建省",
+ "name": "南平市",
+ "id": "350700"
+ },
+ {
+ "province": "福建省",
+ "name": "龙岩市",
+ "id": "350800"
+ },
+ {
+ "province": "福建省",
+ "name": "宁德市",
+ "id": "350900"
+ }
+ ],
+ "360000": [
+ {
+ "province": "江西省",
+ "name": "南昌市",
+ "id": "360100"
+ },
+ {
+ "province": "江西省",
+ "name": "景德镇市",
+ "id": "360200"
+ },
+ {
+ "province": "江西省",
+ "name": "萍乡市",
+ "id": "360300"
+ },
+ {
+ "province": "江西省",
+ "name": "九江市",
+ "id": "360400"
+ },
+ {
+ "province": "江西省",
+ "name": "新余市",
+ "id": "360500"
+ },
+ {
+ "province": "江西省",
+ "name": "鹰潭市",
+ "id": "360600"
+ },
+ {
+ "province": "江西省",
+ "name": "赣州市",
+ "id": "360700"
+ },
+ {
+ "province": "江西省",
+ "name": "吉安市",
+ "id": "360800"
+ },
+ {
+ "province": "江西省",
+ "name": "宜春市",
+ "id": "360900"
+ },
+ {
+ "province": "江西省",
+ "name": "抚州市",
+ "id": "361000"
+ },
+ {
+ "province": "江西省",
+ "name": "上饶市",
+ "id": "361100"
+ }
+ ],
+ "370000": [
+ {
+ "province": "山东省",
+ "name": "济南市",
+ "id": "370100"
+ },
+ {
+ "province": "山东省",
+ "name": "青岛市",
+ "id": "370200"
+ },
+ {
+ "province": "山东省",
+ "name": "淄博市",
+ "id": "370300"
+ },
+ {
+ "province": "山东省",
+ "name": "枣庄市",
+ "id": "370400"
+ },
+ {
+ "province": "山东省",
+ "name": "东营市",
+ "id": "370500"
+ },
+ {
+ "province": "山东省",
+ "name": "烟台市",
+ "id": "370600"
+ },
+ {
+ "province": "山东省",
+ "name": "潍坊市",
+ "id": "370700"
+ },
+ {
+ "province": "山东省",
+ "name": "济宁市",
+ "id": "370800"
+ },
+ {
+ "province": "山东省",
+ "name": "泰安市",
+ "id": "370900"
+ },
+ {
+ "province": "山东省",
+ "name": "威海市",
+ "id": "371000"
+ },
+ {
+ "province": "山东省",
+ "name": "日照市",
+ "id": "371100"
+ },
+ {
+ "province": "山东省",
+ "name": "莱芜市",
+ "id": "371200"
+ },
+ {
+ "province": "山东省",
+ "name": "临沂市",
+ "id": "371300"
+ },
+ {
+ "province": "山东省",
+ "name": "德州市",
+ "id": "371400"
+ },
+ {
+ "province": "山东省",
+ "name": "聊城市",
+ "id": "371500"
+ },
+ {
+ "province": "山东省",
+ "name": "滨州市",
+ "id": "371600"
+ },
+ {
+ "province": "山东省",
+ "name": "菏泽市",
+ "id": "371700"
+ }
+ ],
+ "410000": [
+ {
+ "province": "河南省",
+ "name": "郑州市",
+ "id": "410100"
+ },
+ {
+ "province": "河南省",
+ "name": "开封市",
+ "id": "410200"
+ },
+ {
+ "province": "河南省",
+ "name": "洛阳市",
+ "id": "410300"
+ },
+ {
+ "province": "河南省",
+ "name": "平顶山市",
+ "id": "410400"
+ },
+ {
+ "province": "河南省",
+ "name": "安阳市",
+ "id": "410500"
+ },
+ {
+ "province": "河南省",
+ "name": "鹤壁市",
+ "id": "410600"
+ },
+ {
+ "province": "河南省",
+ "name": "新乡市",
+ "id": "410700"
+ },
+ {
+ "province": "河南省",
+ "name": "焦作市",
+ "id": "410800"
+ },
+ {
+ "province": "河南省",
+ "name": "濮阳市",
+ "id": "410900"
+ },
+ {
+ "province": "河南省",
+ "name": "许昌市",
+ "id": "411000"
+ },
+ {
+ "province": "河南省",
+ "name": "漯河市",
+ "id": "411100"
+ },
+ {
+ "province": "河南省",
+ "name": "三门峡市",
+ "id": "411200"
+ },
+ {
+ "province": "河南省",
+ "name": "南阳市",
+ "id": "411300"
+ },
+ {
+ "province": "河南省",
+ "name": "商丘市",
+ "id": "411400"
+ },
+ {
+ "province": "河南省",
+ "name": "信阳市",
+ "id": "411500"
+ },
+ {
+ "province": "河南省",
+ "name": "周口市",
+ "id": "411600"
+ },
+ {
+ "province": "河南省",
+ "name": "驻马店市",
+ "id": "411700"
+ },
+ {
+ "province": "河南省",
+ "name": "省直辖县级行政区划",
+ "id": "419000"
+ }
+ ],
+ "420000": [
+ {
+ "province": "湖北省",
+ "name": "武汉市",
+ "id": "420100"
+ },
+ {
+ "province": "湖北省",
+ "name": "黄石市",
+ "id": "420200"
+ },
+ {
+ "province": "湖北省",
+ "name": "十堰市",
+ "id": "420300"
+ },
+ {
+ "province": "湖北省",
+ "name": "宜昌市",
+ "id": "420500"
+ },
+ {
+ "province": "湖北省",
+ "name": "襄阳市",
+ "id": "420600"
+ },
+ {
+ "province": "湖北省",
+ "name": "鄂州市",
+ "id": "420700"
+ },
+ {
+ "province": "湖北省",
+ "name": "荆门市",
+ "id": "420800"
+ },
+ {
+ "province": "湖北省",
+ "name": "孝感市",
+ "id": "420900"
+ },
+ {
+ "province": "湖北省",
+ "name": "荆州市",
+ "id": "421000"
+ },
+ {
+ "province": "湖北省",
+ "name": "黄冈市",
+ "id": "421100"
+ },
+ {
+ "province": "湖北省",
+ "name": "咸宁市",
+ "id": "421200"
+ },
+ {
+ "province": "湖北省",
+ "name": "随州市",
+ "id": "421300"
+ },
+ {
+ "province": "湖北省",
+ "name": "恩施土家族苗族自治州",
+ "id": "422800"
+ },
+ {
+ "province": "湖北省",
+ "name": "省直辖县级行政区划",
+ "id": "429000"
+ }
+ ],
+ "430000": [
+ {
+ "province": "湖南省",
+ "name": "长沙市",
+ "id": "430100"
+ },
+ {
+ "province": "湖南省",
+ "name": "株洲市",
+ "id": "430200"
+ },
+ {
+ "province": "湖南省",
+ "name": "湘潭市",
+ "id": "430300"
+ },
+ {
+ "province": "湖南省",
+ "name": "衡阳市",
+ "id": "430400"
+ },
+ {
+ "province": "湖南省",
+ "name": "邵阳市",
+ "id": "430500"
+ },
+ {
+ "province": "湖南省",
+ "name": "岳阳市",
+ "id": "430600"
+ },
+ {
+ "province": "湖南省",
+ "name": "常德市",
+ "id": "430700"
+ },
+ {
+ "province": "湖南省",
+ "name": "张家界市",
+ "id": "430800"
+ },
+ {
+ "province": "湖南省",
+ "name": "益阳市",
+ "id": "430900"
+ },
+ {
+ "province": "湖南省",
+ "name": "郴州市",
+ "id": "431000"
+ },
+ {
+ "province": "湖南省",
+ "name": "永州市",
+ "id": "431100"
+ },
+ {
+ "province": "湖南省",
+ "name": "怀化市",
+ "id": "431200"
+ },
+ {
+ "province": "湖南省",
+ "name": "娄底市",
+ "id": "431300"
+ },
+ {
+ "province": "湖南省",
+ "name": "湘西土家族苗族自治州",
+ "id": "433100"
+ }
+ ],
+ "440000": [
+ {
+ "province": "广东省",
+ "name": "广州市",
+ "id": "440100"
+ },
+ {
+ "province": "广东省",
+ "name": "韶关市",
+ "id": "440200"
+ },
+ {
+ "province": "广东省",
+ "name": "深圳市",
+ "id": "440300"
+ },
+ {
+ "province": "广东省",
+ "name": "珠海市",
+ "id": "440400"
+ },
+ {
+ "province": "广东省",
+ "name": "汕头市",
+ "id": "440500"
+ },
+ {
+ "province": "广东省",
+ "name": "佛山市",
+ "id": "440600"
+ },
+ {
+ "province": "广东省",
+ "name": "江门市",
+ "id": "440700"
+ },
+ {
+ "province": "广东省",
+ "name": "湛江市",
+ "id": "440800"
+ },
+ {
+ "province": "广东省",
+ "name": "茂名市",
+ "id": "440900"
+ },
+ {
+ "province": "广东省",
+ "name": "肇庆市",
+ "id": "441200"
+ },
+ {
+ "province": "广东省",
+ "name": "惠州市",
+ "id": "441300"
+ },
+ {
+ "province": "广东省",
+ "name": "梅州市",
+ "id": "441400"
+ },
+ {
+ "province": "广东省",
+ "name": "汕尾市",
+ "id": "441500"
+ },
+ {
+ "province": "广东省",
+ "name": "河源市",
+ "id": "441600"
+ },
+ {
+ "province": "广东省",
+ "name": "阳江市",
+ "id": "441700"
+ },
+ {
+ "province": "广东省",
+ "name": "清远市",
+ "id": "441800"
+ },
+ {
+ "province": "广东省",
+ "name": "东莞市",
+ "id": "441900"
+ },
+ {
+ "province": "广东省",
+ "name": "中山市",
+ "id": "442000"
+ },
+ {
+ "province": "广东省",
+ "name": "潮州市",
+ "id": "445100"
+ },
+ {
+ "province": "广东省",
+ "name": "揭阳市",
+ "id": "445200"
+ },
+ {
+ "province": "广东省",
+ "name": "云浮市",
+ "id": "445300"
+ }
+ ],
+ "450000": [
+ {
+ "province": "广西壮族自治区",
+ "name": "南宁市",
+ "id": "450100"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "柳州市",
+ "id": "450200"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "桂林市",
+ "id": "450300"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "梧州市",
+ "id": "450400"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "北海市",
+ "id": "450500"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "防城港市",
+ "id": "450600"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "钦州市",
+ "id": "450700"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "贵港市",
+ "id": "450800"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "玉林市",
+ "id": "450900"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "百色市",
+ "id": "451000"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "贺州市",
+ "id": "451100"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "河池市",
+ "id": "451200"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "来宾市",
+ "id": "451300"
+ },
+ {
+ "province": "广西壮族自治区",
+ "name": "崇左市",
+ "id": "451400"
+ }
+ ],
+ "460000": [
+ {
+ "province": "海南省",
+ "name": "海口市",
+ "id": "460100"
+ },
+ {
+ "province": "海南省",
+ "name": "三亚市",
+ "id": "460200"
+ },
+ {
+ "province": "海南省",
+ "name": "三沙市",
+ "id": "460300"
+ },
+ {
+ "province": "海南省",
+ "name": "儋州市",
+ "id": "460400"
+ },
+ {
+ "province": "海南省",
+ "name": "省直辖县级行政区划",
+ "id": "469000"
+ }
+ ],
+ "500000": [
+ {
+ "province": "重庆市",
+ "name": "市辖区",
+ "id": "500100"
+ },
+ {
+ "province": "重庆市",
+ "name": "县",
+ "id": "500200"
+ }
+ ],
+ "510000": [
+ {
+ "province": "四川省",
+ "name": "成都市",
+ "id": "510100"
+ },
+ {
+ "province": "四川省",
+ "name": "自贡市",
+ "id": "510300"
+ },
+ {
+ "province": "四川省",
+ "name": "攀枝花市",
+ "id": "510400"
+ },
+ {
+ "province": "四川省",
+ "name": "泸州市",
+ "id": "510500"
+ },
+ {
+ "province": "四川省",
+ "name": "德阳市",
+ "id": "510600"
+ },
+ {
+ "province": "四川省",
+ "name": "绵阳市",
+ "id": "510700"
+ },
+ {
+ "province": "四川省",
+ "name": "广元市",
+ "id": "510800"
+ },
+ {
+ "province": "四川省",
+ "name": "遂宁市",
+ "id": "510900"
+ },
+ {
+ "province": "四川省",
+ "name": "内江市",
+ "id": "511000"
+ },
+ {
+ "province": "四川省",
+ "name": "乐山市",
+ "id": "511100"
+ },
+ {
+ "province": "四川省",
+ "name": "南充市",
+ "id": "511300"
+ },
+ {
+ "province": "四川省",
+ "name": "眉山市",
+ "id": "511400"
+ },
+ {
+ "province": "四川省",
+ "name": "宜宾市",
+ "id": "511500"
+ },
+ {
+ "province": "四川省",
+ "name": "广安市",
+ "id": "511600"
+ },
+ {
+ "province": "四川省",
+ "name": "达州市",
+ "id": "511700"
+ },
+ {
+ "province": "四川省",
+ "name": "雅安市",
+ "id": "511800"
+ },
+ {
+ "province": "四川省",
+ "name": "巴中市",
+ "id": "511900"
+ },
+ {
+ "province": "四川省",
+ "name": "资阳市",
+ "id": "512000"
+ },
+ {
+ "province": "四川省",
+ "name": "阿坝藏族羌族自治州",
+ "id": "513200"
+ },
+ {
+ "province": "四川省",
+ "name": "甘孜藏族自治州",
+ "id": "513300"
+ },
+ {
+ "province": "四川省",
+ "name": "凉山彝族自治州",
+ "id": "513400"
+ }
+ ],
+ "520000": [
+ {
+ "province": "贵州省",
+ "name": "贵阳市",
+ "id": "520100"
+ },
+ {
+ "province": "贵州省",
+ "name": "六盘水市",
+ "id": "520200"
+ },
+ {
+ "province": "贵州省",
+ "name": "遵义市",
+ "id": "520300"
+ },
+ {
+ "province": "贵州省",
+ "name": "安顺市",
+ "id": "520400"
+ },
+ {
+ "province": "贵州省",
+ "name": "毕节市",
+ "id": "520500"
+ },
+ {
+ "province": "贵州省",
+ "name": "铜仁市",
+ "id": "520600"
+ },
+ {
+ "province": "贵州省",
+ "name": "黔西南布依族苗族自治州",
+ "id": "522300"
+ },
+ {
+ "province": "贵州省",
+ "name": "黔东南苗族侗族自治州",
+ "id": "522600"
+ },
+ {
+ "province": "贵州省",
+ "name": "黔南布依族苗族自治州",
+ "id": "522700"
+ }
+ ],
+ "530000": [
+ {
+ "province": "云南省",
+ "name": "昆明市",
+ "id": "530100"
+ },
+ {
+ "province": "云南省",
+ "name": "曲靖市",
+ "id": "530300"
+ },
+ {
+ "province": "云南省",
+ "name": "玉溪市",
+ "id": "530400"
+ },
+ {
+ "province": "云南省",
+ "name": "保山市",
+ "id": "530500"
+ },
+ {
+ "province": "云南省",
+ "name": "昭通市",
+ "id": "530600"
+ },
+ {
+ "province": "云南省",
+ "name": "丽江市",
+ "id": "530700"
+ },
+ {
+ "province": "云南省",
+ "name": "普洱市",
+ "id": "530800"
+ },
+ {
+ "province": "云南省",
+ "name": "临沧市",
+ "id": "530900"
+ },
+ {
+ "province": "云南省",
+ "name": "楚雄彝族自治州",
+ "id": "532300"
+ },
+ {
+ "province": "云南省",
+ "name": "红河哈尼族彝族自治州",
+ "id": "532500"
+ },
+ {
+ "province": "云南省",
+ "name": "文山壮族苗族自治州",
+ "id": "532600"
+ },
+ {
+ "province": "云南省",
+ "name": "西双版纳傣族自治州",
+ "id": "532800"
+ },
+ {
+ "province": "云南省",
+ "name": "大理白族自治州",
+ "id": "532900"
+ },
+ {
+ "province": "云南省",
+ "name": "德宏傣族景颇族自治州",
+ "id": "533100"
+ },
+ {
+ "province": "云南省",
+ "name": "怒江傈僳族自治州",
+ "id": "533300"
+ },
+ {
+ "province": "云南省",
+ "name": "迪庆藏族自治州",
+ "id": "533400"
+ }
+ ],
+ "540000": [
+ {
+ "province": "西藏自治区",
+ "name": "拉萨市",
+ "id": "540100"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "日喀则市",
+ "id": "540200"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "昌都市",
+ "id": "540300"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "林芝市",
+ "id": "540400"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "山南市",
+ "id": "540500"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "那曲地区",
+ "id": "542400"
+ },
+ {
+ "province": "西藏自治区",
+ "name": "阿里地区",
+ "id": "542500"
+ }
+ ],
+ "610000": [
+ {
+ "province": "陕西省",
+ "name": "西安市",
+ "id": "610100"
+ },
+ {
+ "province": "陕西省",
+ "name": "铜川市",
+ "id": "610200"
+ },
+ {
+ "province": "陕西省",
+ "name": "宝鸡市",
+ "id": "610300"
+ },
+ {
+ "province": "陕西省",
+ "name": "咸阳市",
+ "id": "610400"
+ },
+ {
+ "province": "陕西省",
+ "name": "渭南市",
+ "id": "610500"
+ },
+ {
+ "province": "陕西省",
+ "name": "延安市",
+ "id": "610600"
+ },
+ {
+ "province": "陕西省",
+ "name": "汉中市",
+ "id": "610700"
+ },
+ {
+ "province": "陕西省",
+ "name": "榆林市",
+ "id": "610800"
+ },
+ {
+ "province": "陕西省",
+ "name": "安康市",
+ "id": "610900"
+ },
+ {
+ "province": "陕西省",
+ "name": "商洛市",
+ "id": "611000"
+ }
+ ],
+ "620000": [
+ {
+ "province": "甘肃省",
+ "name": "兰州市",
+ "id": "620100"
+ },
+ {
+ "province": "甘肃省",
+ "name": "嘉峪关市",
+ "id": "620200"
+ },
+ {
+ "province": "甘肃省",
+ "name": "金昌市",
+ "id": "620300"
+ },
+ {
+ "province": "甘肃省",
+ "name": "白银市",
+ "id": "620400"
+ },
+ {
+ "province": "甘肃省",
+ "name": "天水市",
+ "id": "620500"
+ },
+ {
+ "province": "甘肃省",
+ "name": "武威市",
+ "id": "620600"
+ },
+ {
+ "province": "甘肃省",
+ "name": "张掖市",
+ "id": "620700"
+ },
+ {
+ "province": "甘肃省",
+ "name": "平凉市",
+ "id": "620800"
+ },
+ {
+ "province": "甘肃省",
+ "name": "酒泉市",
+ "id": "620900"
+ },
+ {
+ "province": "甘肃省",
+ "name": "庆阳市",
+ "id": "621000"
+ },
+ {
+ "province": "甘肃省",
+ "name": "定西市",
+ "id": "621100"
+ },
+ {
+ "province": "甘肃省",
+ "name": "陇南市",
+ "id": "621200"
+ },
+ {
+ "province": "甘肃省",
+ "name": "临夏回族自治州",
+ "id": "622900"
+ },
+ {
+ "province": "甘肃省",
+ "name": "甘南藏族自治州",
+ "id": "623000"
+ }
+ ],
+ "630000": [
+ {
+ "province": "青海省",
+ "name": "西宁市",
+ "id": "630100"
+ },
+ {
+ "province": "青海省",
+ "name": "海东市",
+ "id": "630200"
+ },
+ {
+ "province": "青海省",
+ "name": "海北藏族自治州",
+ "id": "632200"
+ },
+ {
+ "province": "青海省",
+ "name": "黄南藏族自治州",
+ "id": "632300"
+ },
+ {
+ "province": "青海省",
+ "name": "海南藏族自治州",
+ "id": "632500"
+ },
+ {
+ "province": "青海省",
+ "name": "果洛藏族自治州",
+ "id": "632600"
+ },
+ {
+ "province": "青海省",
+ "name": "玉树藏族自治州",
+ "id": "632700"
+ },
+ {
+ "province": "青海省",
+ "name": "海西蒙古族藏族自治州",
+ "id": "632800"
+ }
+ ],
+ "640000": [
+ {
+ "province": "宁夏回族自治区",
+ "name": "银川市",
+ "id": "640100"
+ },
+ {
+ "province": "宁夏回族自治区",
+ "name": "石嘴山市",
+ "id": "640200"
+ },
+ {
+ "province": "宁夏回族自治区",
+ "name": "吴忠市",
+ "id": "640300"
+ },
+ {
+ "province": "宁夏回族自治区",
+ "name": "固原市",
+ "id": "640400"
+ },
+ {
+ "province": "宁夏回族自治区",
+ "name": "中卫市",
+ "id": "640500"
+ }
+ ],
+ "650000": [
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "乌鲁木齐市",
+ "id": "650100"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "克拉玛依市",
+ "id": "650200"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "吐鲁番市",
+ "id": "650400"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "哈密市",
+ "id": "650500"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "昌吉回族自治州",
+ "id": "652300"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "博尔塔拉蒙古自治州",
+ "id": "652700"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "巴音郭楞蒙古自治州",
+ "id": "652800"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "阿克苏地区",
+ "id": "652900"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "克孜勒苏柯尔克孜自治州",
+ "id": "653000"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "喀什地区",
+ "id": "653100"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "和田地区",
+ "id": "653200"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "伊犁哈萨克自治州",
+ "id": "654000"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "塔城地区",
+ "id": "654200"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "阿勒泰地区",
+ "id": "654300"
+ },
+ {
+ "province": "新疆维吾尔自治区",
+ "name": "自治区直辖县级行政区划",
+ "id": "659000"
+ }
+ ]
+}
diff --git a/mock/geographic/province.json b/mock/geographic/province.json
new file mode 100644
index 00000000..910c83f0
--- /dev/null
+++ b/mock/geographic/province.json
@@ -0,0 +1,138 @@
+[
+ {
+ "name": "北京市",
+ "id": "110000"
+ },
+ {
+ "name": "天津市",
+ "id": "120000"
+ },
+ {
+ "name": "河北省",
+ "id": "130000"
+ },
+ {
+ "name": "山西省",
+ "id": "140000"
+ },
+ {
+ "name": "内蒙古自治区",
+ "id": "150000"
+ },
+ {
+ "name": "辽宁省",
+ "id": "210000"
+ },
+ {
+ "name": "吉林省",
+ "id": "220000"
+ },
+ {
+ "name": "黑龙江省",
+ "id": "230000"
+ },
+ {
+ "name": "上海市",
+ "id": "310000"
+ },
+ {
+ "name": "江苏省",
+ "id": "320000"
+ },
+ {
+ "name": "浙江省",
+ "id": "330000"
+ },
+ {
+ "name": "安徽省",
+ "id": "340000"
+ },
+ {
+ "name": "福建省",
+ "id": "350000"
+ },
+ {
+ "name": "江西省",
+ "id": "360000"
+ },
+ {
+ "name": "山东省",
+ "id": "370000"
+ },
+ {
+ "name": "河南省",
+ "id": "410000"
+ },
+ {
+ "name": "湖北省",
+ "id": "420000"
+ },
+ {
+ "name": "湖南省",
+ "id": "430000"
+ },
+ {
+ "name": "广东省",
+ "id": "440000"
+ },
+ {
+ "name": "广西壮族自治区",
+ "id": "450000"
+ },
+ {
+ "name": "海南省",
+ "id": "460000"
+ },
+ {
+ "name": "重庆市",
+ "id": "500000"
+ },
+ {
+ "name": "四川省",
+ "id": "510000"
+ },
+ {
+ "name": "贵州省",
+ "id": "520000"
+ },
+ {
+ "name": "云南省",
+ "id": "530000"
+ },
+ {
+ "name": "西藏自治区",
+ "id": "540000"
+ },
+ {
+ "name": "陕西省",
+ "id": "610000"
+ },
+ {
+ "name": "甘肃省",
+ "id": "620000"
+ },
+ {
+ "name": "青海省",
+ "id": "630000"
+ },
+ {
+ "name": "宁夏回族自治区",
+ "id": "640000"
+ },
+ {
+ "name": "新疆维吾尔自治区",
+ "id": "650000"
+ },
+ {
+ "name": "台湾省",
+ "id": "710000"
+ },
+ {
+ "name": "香港特别行政区",
+ "id": "810000"
+ },
+ {
+ "name": "澳门特别行政区",
+ "id": "820000"
+ }
+]
diff --git a/mock/notices.js b/mock/notices.js
index 7e1b3ce4..505088e0 100644
--- a/mock/notices.js
+++ b/mock/notices.js
@@ -1,18 +1,18 @@
-export const getNotices = (req, res) => {
+const getNotices = (req, res) =>
res.json([
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
- type: '通知',
+ type: 'notification',
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
- type: '通知',
+ type: 'notification',
},
{
id: '000000003',
@@ -20,21 +20,21 @@ export const getNotices = (req, res) => {
title: '这种模板可以区分多种通知类型',
datetime: '2017-08-07',
read: true,
- type: '通知',
+ type: 'notification',
},
{
id: '000000004',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
datetime: '2017-08-07',
- type: '通知',
+ type: 'notification',
},
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '内容不要超过两行字,超出时自动截断',
datetime: '2017-08-07',
- type: '通知',
+ type: 'notification',
},
{
id: '000000006',
@@ -42,7 +42,8 @@ export const getNotices = (req, res) => {
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
- type: '消息',
+ type: 'message',
+ clickClose: true,
},
{
id: '000000007',
@@ -50,7 +51,8 @@ export const getNotices = (req, res) => {
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
- type: '消息',
+ type: 'message',
+ clickClose: true,
},
{
id: '000000008',
@@ -58,7 +60,8 @@ export const getNotices = (req, res) => {
title: '标题',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
- type: '消息',
+ type: 'message',
+ clickClose: true,
},
{
id: '000000009',
@@ -66,7 +69,7 @@ export const getNotices = (req, res) => {
description: '任务需要在 2017-01-12 20:00 前启动',
extra: '未开始',
status: 'todo',
- type: '待办',
+ type: 'event',
},
{
id: '000000010',
@@ -74,7 +77,7 @@ export const getNotices = (req, res) => {
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
extra: '马上到期',
status: 'urgent',
- type: '待办',
+ type: 'event',
},
{
id: '000000011',
@@ -82,7 +85,7 @@ export const getNotices = (req, res) => {
description: '指派竹尔于 2017-01-09 前完成更新并发布',
extra: '已耗时 8 天',
status: 'doing',
- type: '待办',
+ type: 'event',
},
{
id: '000000012',
@@ -90,10 +93,10 @@ export const getNotices = (req, res) => {
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
extra: '进行中',
status: 'processing',
- type: '待办',
+ type: 'event',
},
]);
-};
+
export default {
- getNotices,
+ 'GET /api/notices': getNotices,
};
diff --git a/mock/pro.js b/mock/pro.js
deleted file mode 100644
index 8527602f..00000000
--- a/mock/pro.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import antdServer from "antd-pro-server";
-
-export default { ...antdServer }
\ No newline at end of file
diff --git a/mock/profile.js b/mock/profile.js
index 7c24be1b..03aecc56 100644
--- a/mock/profile.js
+++ b/mock/profile.js
@@ -141,18 +141,18 @@ const advancedOperation3 = [
},
];
-export const getProfileBasicData = {
+const getProfileBasicData = {
basicGoods,
basicProgress,
};
-export const getProfileAdvancedData = {
+const getProfileAdvancedData = {
advancedOperation1,
advancedOperation2,
advancedOperation3,
};
export default {
- getProfileBasicData,
- getProfileAdvancedData,
+ 'GET /api/profile/advanced': getProfileAdvancedData,
+ 'GET /api/profile/basic': getProfileBasicData,
};
diff --git a/mock/route.js b/mock/route.js
new file mode 100644
index 00000000..56b45263
--- /dev/null
+++ b/mock/route.js
@@ -0,0 +1,263 @@
+export default {
+ "/api/auth_routes":
+ // [
+ // // user
+ // {
+ // path: '/user',
+ // component: '../layouts/UserLayout',
+ // routes: [
+ // { path: '/user', redirect: '/user/login' },
+ // { path: '/user/login', component: './User/Login' },
+ // { path: '/user/register', component: './User/Register' },
+ // { path: '/user/registerresult', component: './User/RegisterResult' },
+ // ],
+ // },
+ // app
+ {
+ path: '/',
+ component: '../layouts/BasicLayout',
+ Routes: ['src/pages/Authorized'],
+ routes: [
+ // dashboard
+ { path: '/', redirect: '/dashboard/analysis' },
+ {
+ path: '/dashboard',
+ name: 'dashboard',
+ icon: 'dashboard',
+ routes: [
+ {
+ path: '/dashboard/analysis',
+ name: 'analysis',
+ component: './Dashboard/Analysis',
+ },
+ {
+ path: '/dashboard/monitor',
+ name: 'monitor',
+ component: './Dashboard/Monitor',
+ },
+ {
+ path: '/dashboard/workplace',
+ name: 'workplace',
+ component: './Dashboard/Workplace',
+ },
+ ],
+ },
+ // forms
+ {
+ path: '/form',
+ icon: 'form',
+ name: 'form',
+ routes: [
+ {
+ path: '/form/basicform',
+ name: 'basicform',
+ component: './Forms/BasicForm',
+ },
+ {
+ path: '/form/stepform',
+ name: 'stepform',
+ component: './Forms/StepForm',
+ hideChildrenInMenu: true,
+ routes: [
+ {
+ path: '/form/stepform',
+ redirect: '/form/stepform/info',
+ },
+ {
+ path: '/form/stepform/info',
+ name: 'info',
+ component: './Forms/StepForm/Step1',
+ },
+ {
+ path: '/form/stepform/confirm',
+ name: 'confirm',
+ component: './Forms/StepForm/Step2',
+ },
+ {
+ path: '/form/stepform/result',
+ name: 'result',
+ component: './Forms/StepForm/Step3',
+ },
+ ],
+ },
+ {
+ path: '/form/advancedform',
+ name: 'advancedform',
+ component: './Forms/AdvancedForm',
+ },
+ ],
+ },
+ // list
+ {
+ path: '/list',
+ icon: 'table',
+ name: 'list',
+ routes: [
+ {
+ path: '/list/tablelist',
+ name: 'searchtable',
+ component: './List/TableList',
+ },
+ {
+ path: '/list/basiclist',
+ name: 'basiclist',
+ component: './List/BasicList',
+ },
+ {
+ path: '/list/cardlist',
+ name: 'cardlist',
+ component: './List/CardList',
+ },
+ {
+ path: '/list/search',
+ name: 'searchlist',
+ component: './List/List',
+ routes: [
+ {
+ path: '/list/search',
+ redirect: '/list/search/articles',
+ },
+ {
+ path: '/list/search/articles',
+ name: 'articles',
+ component: './List/Articles',
+ },
+ {
+ path: '/list/search/projects',
+ name: 'projects',
+ component: './List/Projects',
+ },
+ {
+ path: '/list/search/applications',
+ name: 'applications',
+ component: './List/Applications',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ path: '/profile',
+ name: 'profile',
+ icon: 'profile',
+ routes: [
+ // profile
+ {
+ path: '/profile/basicprofile',
+ name: 'basic',
+ component: './Profile/BasicProfile',
+ },
+ {
+ path: '/profile/advancedprofile',
+ name: 'advanced',
+ component: './Profile/AdvancedProfile',
+ },
+ ],
+ },
+ {
+ name: 'result',
+ icon: 'check-circle-o',
+ path: '/result',
+ routes: [
+ // result
+ {
+ path: '/result/success',
+ name: 'success',
+ component: './Result/Success',
+ },
+ { path: '/result/fail', name: 'fail', component: './Result/Error' },
+ ],
+ },
+ {
+ name: 'exception',
+ icon: 'warning',
+ path: '/exception',
+ routes: [
+ // exception
+ {
+ path: '/exception/403',
+ name: 'not-permission',
+ component: './Exception/403',
+ },
+ {
+ path: '/exception/404',
+ name: 'not-find',
+ component: './Exception/404',
+ },
+ {
+ path: '/exception/500',
+ name: 'server-error',
+ component: './Exception/500',
+ },
+ {
+ path: '/exception/trigger',
+ name: 'trigger',
+ hideInMenu: true,
+ component: './Exception/TriggerException',
+ },
+ ],
+ },
+ {
+ name: 'account',
+ icon: 'user',
+ path: '/account',
+ routes: [
+ {
+ path: '/account/center',
+ name: 'center',
+ component: './Account/Center/Center',
+ routes: [
+ {
+ path: '/account/center',
+ redirect: '/account/center/articles',
+ },
+ {
+ path: '/account/center/articles',
+ component: './Account/Center/Articles',
+ },
+ {
+ path: '/account/center/applications',
+ component: './Account/Center/Applications',
+ },
+ {
+ path: '/account/center/projects',
+ component: './Account/Center/Projects',
+ },
+ ],
+ },
+ {
+ path: '/account/settings',
+ name: 'settings',
+ component: './Account/Settings/Info',
+ routes: [
+ {
+ path: '/account/settings',
+ redirect: '/account/settings/base',
+ },
+ {
+ path: '/account/settings/base',
+ component: './Account/Settings/BaseView',
+ },
+ {
+ path: '/account/settings/security',
+ component: './Account/Settings/SecurityView',
+ },
+ {
+ path: '/account/settings/binding',
+ component: './Account/Settings/BindingView',
+ },
+ {
+ path: '/account/settings/notification',
+ component: './Account/Settings/NotificationView',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ component: '404',
+ },
+ ],
+ },
+ // ]
+};
diff --git a/mock/rule.js b/mock/rule.js
index e89b9496..35d36c3b 100644
--- a/mock/rule.js
+++ b/mock/rule.js
@@ -11,10 +11,10 @@ for (let i = 0; i < 46; i += 1) {
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
- no: `TradeCode ${i}`,
+ name: `TradeCode ${i}`,
title: `一个任务名称 ${i}`,
owner: '曲丽丽',
- description: '这是一段描述',
+ desc: '这是一段描述',
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 4,
updatedAt: new Date(`2017-07-${Math.floor(i / 2) + 1}`),
@@ -23,7 +23,7 @@ for (let i = 0; i < 46; i += 1) {
});
}
-export function getRule(req, res, u) {
+function getRule(req, res, u) {
let url = u;
if (!url || Object.prototype.toString.call(url) !== '[object String]') {
url = req.url; // eslint-disable-line
@@ -31,7 +31,7 @@ export function getRule(req, res, u) {
const params = parse(url, true).query;
- let dataSource = [...tableListDataSource];
+ let dataSource = tableListDataSource;
if (params.sorter) {
const s = params.sorter.split('_');
@@ -48,14 +48,14 @@ export function getRule(req, res, u) {
let filterDataSource = [];
status.forEach(s => {
filterDataSource = filterDataSource.concat(
- [...dataSource].filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
+ dataSource.filter(data => parseInt(data.status, 10) === parseInt(s[0], 10))
);
});
dataSource = filterDataSource;
}
- if (params.no) {
- dataSource = dataSource.filter(data => data.no.indexOf(params.no) > -1);
+ if (params.name) {
+ dataSource = dataSource.filter(data => data.name.indexOf(params.name) > -1);
}
let pageSize = 10;
@@ -72,26 +72,22 @@ export function getRule(req, res, u) {
},
};
- if (res && res.json) {
- res.json(result);
- } else {
- return result;
- }
+ return res.json(result);
}
-export function postRule(req, res, u, b) {
+function postRule(req, res, u, b) {
let url = u;
if (!url || Object.prototype.toString.call(url) !== '[object String]') {
url = req.url; // eslint-disable-line
}
const body = (b && b.body) || req.body;
- const { method, no, description } = body;
+ const { method, name, desc, key } = body;
switch (method) {
/* eslint no-case-declarations:0 */
case 'delete':
- tableListDataSource = tableListDataSource.filter(item => no.indexOf(item.no) === -1);
+ tableListDataSource = tableListDataSource.filter(item => key.indexOf(item.key) === -1);
break;
case 'post':
const i = Math.ceil(Math.random() * 10000);
@@ -102,10 +98,10 @@ export function postRule(req, res, u, b) {
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
][i % 2],
- no: `TradeCode ${i}`,
+ name: `TradeCode ${i}`,
title: `一个任务名称 ${i}`,
owner: '曲丽丽',
- description,
+ desc,
callNo: Math.floor(Math.random() * 1000),
status: Math.floor(Math.random() * 10) % 2,
updatedAt: new Date(),
@@ -113,6 +109,15 @@ export function postRule(req, res, u, b) {
progress: Math.ceil(Math.random() * 100),
});
break;
+ case 'update':
+ tableListDataSource = tableListDataSource.map(item => {
+ if (item.key === key) {
+ Object.assign(item, { desc, name });
+ return item;
+ }
+ return item;
+ });
+ break;
default:
break;
}
@@ -124,14 +129,10 @@ export function postRule(req, res, u, b) {
},
};
- if (res && res.json) {
- res.json(result);
- } else {
- return result;
- }
+ return res.json(result);
}
export default {
- getRule,
- postRule,
+ 'GET /api/rule': getRule,
+ 'POST /api/rule': postRule,
};
diff --git a/mock/user.js b/mock/user.js
new file mode 100644
index 00000000..dd3f2402
--- /dev/null
+++ b/mock/user.js
@@ -0,0 +1,138 @@
+// 代码中会兼容本地 service mock 以及部署站点的静态数据
+export default {
+ // 支持值为 Object 和 Array
+ 'GET /api/currentUser': {
+ name: 'Serati Ma',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
+ userid: '00000001',
+ email: 'antdesign@alipay.com',
+ signature: '海纳百川,有容乃大',
+ title: '交互专家',
+ group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
+ tags: [
+ {
+ key: '0',
+ label: '很有想法的',
+ },
+ {
+ key: '1',
+ label: '专注设计',
+ },
+ {
+ key: '2',
+ label: '辣~',
+ },
+ {
+ key: '3',
+ label: '大长腿',
+ },
+ {
+ key: '4',
+ label: '川妹子',
+ },
+ {
+ key: '5',
+ label: '海纳百川',
+ },
+ ],
+ notifyCount: 12,
+ unreadCount: 11,
+ country: 'China',
+ geographic: {
+ province: {
+ label: '浙江省',
+ key: '330000',
+ },
+ city: {
+ label: '杭州市',
+ key: '330100',
+ },
+ },
+ address: '西湖区工专路 77 号',
+ phone: '0752-268888888',
+ },
+ // GET POST 可省略
+ 'GET /api/users': [
+ {
+ key: '1',
+ name: 'John Brown',
+ age: 32,
+ address: 'New York No. 1 Lake Park',
+ },
+ {
+ key: '2',
+ name: 'Jim Green',
+ age: 42,
+ address: 'London No. 1 Lake Park',
+ },
+ {
+ key: '3',
+ name: 'Joe Black',
+ age: 32,
+ address: 'Sidney No. 1 Lake Park',
+ },
+ ],
+ 'POST /api/login/account': (req, res) => {
+ const { password, userName, type } = req.body;
+ if (password === 'ant.design' && userName === 'admin') {
+ res.send({
+ status: 'ok',
+ type,
+ currentAuthority: 'admin',
+ });
+ return;
+ }
+ if (password === 'ant.design' && userName === 'user') {
+ res.send({
+ status: 'ok',
+ type,
+ currentAuthority: 'user',
+ });
+ return;
+ }
+ res.send({
+ status: 'error',
+ type,
+ currentAuthority: 'guest',
+ });
+ },
+ 'POST /api/register': (req, res) => {
+ res.send({ status: 'ok', currentAuthority: 'user' });
+ },
+ 'GET /api/500': (req, res) => {
+ res.status(500).send({
+ timestamp: 1513932555104,
+ status: 500,
+ error: 'error',
+ message: 'error',
+ path: '/base/category/list',
+ });
+ },
+ 'GET /api/404': (req, res) => {
+ res.status(404).send({
+ timestamp: 1513932643431,
+ status: 404,
+ error: 'Not Found',
+ message: 'No message available',
+ path: '/base/category/list/2121212',
+ });
+ },
+ 'GET /api/403': (req, res) => {
+ res.status(403).send({
+ timestamp: 1513932555104,
+ status: 403,
+ error: 'Unauthorized',
+ message: 'Unauthorized',
+ path: '/base/category/list',
+ });
+ },
+ 'GET /api/401': (req, res) => {
+ res.status(401).send({
+ timestamp: 1513932555104,
+ status: 401,
+ error: 'Unauthorized',
+ message: 'Unauthorized',
+ path: '/base/category/list',
+ });
+ },
+};
diff --git a/package.json b/package.json
index 2d9d95a0..e2b52e5d 100644
--- a/package.json
+++ b/package.json
@@ -28,29 +28,44 @@
},
"homepage": "https://github.com/xiaohuoni/umi-antd-pro#readme",
"devDependencies": {
- "cross-env": "^5.1.4",
+ "antd-pro-merge-less": "^0.2.0",
+ "antd-theme-webpack-plugin": "^1.1.8",
+ "cross-env": "^5.2.0",
"mockjs": "^1.0.1-beta3",
- "moment": "^2.22.1",
- "umi": "^2.1.2",
+ "umi": "^2.2.7",
"umi-plugin-authorize": "^0.0.2",
- "umi-plugin-react": "^1.1.1"
+ "umi-plugin-ga": "^1.1.3",
+ "umi-plugin-react": "^1.2.3"
},
"dependencies": {
- "@antv/data-set": "^0.8.7",
- "antd-pro-server": "^1.0.1",
- "bizcharts": "^3.1.7",
- "bizcharts-plugin-slider": "^2.0.3",
+ "oni-delay": "^1.0.2",
+ "@babel/runtime": "^7.1.5",
+ "antd": "^3.10.9",
+ "bizcharts": "^3.4.0",
+ "bizcharts-plugin-slider": "^2.1.1-beta.1",
+ "@antv/data-set": "^0.10.0",
+ "classnames": "^2.2.6",
+ "dva": "^2.4.0",
"enquire-js": "^0.2.1",
- "lodash-decorators": "^5.0.0",
+ "hash.js": "^1.1.5",
+ "lodash": "^4.17.10",
+ "lodash-decorators": "^6.0.0",
+ "memoize-one": "^4.0.0",
+ "moment": "^2.22.2",
"numeral": "^2.0.6",
- "oni-delay": "^1.0.2",
- "rc-drawer-menu": "^0.5.7",
+ "nzh": "^1.0.3",
+ "omit.js": "^1.0.0",
+ "path-to-regexp": "^2.4.0",
+ "prop-types": "^15.5.10",
+ "qs": "^6.6.0",
+ "rc-animate": "^2.4.4",
+ "react": "^16.6.3",
"react-container-query": "^0.11.0",
+ "react-copy-to-clipboard": "^5.0.1",
"react-document-title": "^2.0.3",
+ "react-dom": "^16.6.3",
"react-fittext": "^1.0.0",
- "react-router-breadcrumbs-hoc": "^2.1.0",
- "react-transition-group": "^2.3.1",
- "rollbar": "^2.3.9",
- "url-polyfill": "^1.0.13"
+ "react-media": "^1.8.0",
+ "react-router-dom": "^4.3.1"
}
}
diff --git a/public/favicon.png b/public/favicon.png
new file mode 100644
index 00000000..ece59ce5
Binary files /dev/null and b/public/favicon.png differ
diff --git a/public/icons/icon-128x128.png b/public/icons/icon-128x128.png
new file mode 100644
index 00000000..48d0e233
Binary files /dev/null and b/public/icons/icon-128x128.png differ
diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png
new file mode 100644
index 00000000..938e9b53
Binary files /dev/null and b/public/icons/icon-192x192.png differ
diff --git a/src/common/menu.js b/src/common/menu.js
deleted file mode 100644
index 1b581c8d..00000000
--- a/src/common/menu.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import { isUrl } from '../utils/utils';
-
-const menuData = [{
- name: 'dashboard',
- icon: 'dashboard',
- path: 'Dashboard',
- children: [{
- name: '分析页',
- path: 'Analysis',
- }, {
- name: '监控页',
- path: 'Monitor',
- }, {
- name: '工作台',
- path: 'Workplace',
- // hideInMenu: true,
- }],
-}, {
- name: '表单页',
- icon: 'form',
- path: 'Forms',
- children: [{
- name: '基础表单',
- path: 'BasicForm',
- }, {
- name: '分步表单',
- path: 'StepForm/Step1',
- }, {
- name: '高级表单',
- authority: 'admin',
- path: 'AdvancedForm',
- }],
-}, {
- name: '列表页',
- icon: 'table',
- path: 'List',
- children: [{
- name: '查询表格',
- path: 'TableList',
- }, {
- name: '标准列表',
- path: 'BasicList',
- }, {
- name: '卡片列表',
- path: 'CardList',
- }, {
- name: '搜索列表',
- path: 'Search',
- children: [{
- name: '搜索列表(文章)',
- path: 'Articles',
- }, {
- name: '搜索列表(项目)',
- path: 'Projects',
- }, {
- name: '搜索列表(应用)',
- path: 'Applications',
- }],
- }],
-}, {
- name: '详情页',
- icon: 'profile',
- path: 'Profile',
- children: [{
- name: '基础详情页',
- path: 'BasicProfile',
- }, {
- name: '高级详情页',
- path: 'AdvancedProfile',
- authority: 'admin',
- }],
-}, {
- name: '结果页',
- icon: 'check-circle-o',
- path: 'Result',
- children: [{
- name: '成功',
- path: 'Success',
- }, {
- name: '失败',
- path: 'Error',
- }],
-}, {
- name: '异常页',
- icon: 'warning',
- path: 'Exception',
- children: [{
- name: '403',
- path: '403',
- }, {
- name: '404',
- path: '404',
- }, {
- name: '500',
- path: '500',
- }, {
- name: '触发异常',
- path: 'triggerException',
- hideInMenu: true,
- }],
-}
-// ,
-// {
-// name: '账户',
-// icon: 'user',
-// path: 'User',
-// authority: 'guest',
-// children: [{
-// name: '登录',
-// path: 'Login',
-// }, {
-// name: '注册',
-// path: 'Register',
-// }, {
-// name: '注册结果',
-// path: 'RegisterResult',
-// }],
-// }
-];
-
-function formatter(data, parentPath = '/', parentAuthority) {
- return data.map((item) => {
- let { path } = item;
- if (!isUrl(path)) {
- path = parentPath + item.path;
- }
- const result = {
- ...item,
- path,
- authority: item.authority || parentAuthority||'admin',
- };
- if (item.children) {
- result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
- }
-
- return result;
- });
-}
-//TODO:先去掉了authority,等调试完了再加上
-export const getMenuData = () => formatter(menuData);
diff --git a/src/common/router.js b/src/common/router.js
deleted file mode 100644
index ab87819b..00000000
--- a/src/common/router.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import pathToRegexp from 'path-to-regexp';
-import { getMenuData } from './menu';
-
-function getFlatMenuData(menus) {
- let keys = {};
- menus.forEach((item) => {
- if (item.children) {
- keys[item.path] = { ...item };
- keys = { ...keys, ...getFlatMenuData(item.children) };
- } else {
- keys[item.path] = { ...item };
- }
- });
- return keys;
-}
-
-export const getRouterData = (app) => {
- const routerConfig = {
- '/': {
- name:'xiaohuOni'
- },
- '/Dashboard/Analysis': {
- name:'分析页'
- },
- '/Dashboard/Monitor': {
- },
- '/Dashboard/Workplace': {
- // component: dynamicWrapper(app, ['project', 'activities', 'chart'], () => import('../routes/Dashboard/Workplace')),
- // hideInBreadcrumb: true,
- // name: '工作台',
- // authority: 'admin',
- },
- '/Forms/BasicForm': {
- },
- '/Forms/StepForm': {
- },
- '/Forms/StepForm/Step1': {
- name: '分步表单(填写转账信息)',
- },
- '/Forms/StepForm/Step2': {
- name: '分步表单(确认转账信息)',
- },
- '/Forms/StepForm/Step3': {
- name: '分步表单(完成)',
- },
- '/Forms/AdvancedForm': {
- },
- '/List/TableList': {
- },
- '/List/BasicList': {
- },
- '/List/CardList': {
- },
- '/List': {
- },
- '/List/Search/Projects': {
- },
- '/List/Search/Applications': {
- },
- '/List/Search/Articles': {
- },
- '/Profile/BasicProfile': {
- },
- '/Profile/AdvancedProfile': {
- },
- '/Result/success': {
- },
- '/Result/Error': {
- },
- '/Exception/403': {
- },
- '/Exception/404': {
- },
- '/Exception/500': {
- },
- '/Exception/triggerException': {
- },
- '/User': {
- },
- '/User/Login': {
- },
- '/User/Register': {
- },
- '/User/RegisterResult': {
- },
- // '/user/:id': {
- // component: dynamicWrapper(app, [], () => console.log('../routes/User/SomeComponent')),
- // },
- };
- // Get name from ./menu.js or just set it in the router data.
- const menuData = getFlatMenuData(getMenuData());
-
- // Route configuration data
- // eg. {name,authority ...routerConfig }
- const routerData = {};
- // The route matches the menu
- Object.keys(routerConfig).forEach((path) => {
- // Regular match item name
- // eg. router /user/:id === /user/chen
- const pathRegexp = pathToRegexp(path);
- const menuKey = Object.keys(menuData).find(key => pathRegexp.test(`${key}`));
- let menuItem = {};
- // If menuKey is not empty
- if (menuKey) {
- menuItem = menuData[menuKey];
- }
- let router = routerConfig[path];
- // If you need to configure complex parameter routing,
- // https://github.com/ant-design/ant-design-pro-site/blob/master/docs/router-and-nav.md#%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E8%B7%AF%E7%94%B1%E8%8F%9C%E5%8D%95
- // eg . /list/:type/user/info/:id
- router = {
- ...router,
- name: router.name || menuItem.name
- };
- routerData[path] = router;
- });
- // console.log("routerData");
- // console.log(routerData);
-
- return routerData;
-};
diff --git a/src/components/ActiveChart/index.js b/src/components/ActiveChart/index.js
index cd6119b9..3d9fbbaf 100644
--- a/src/components/ActiveChart/index.js
+++ b/src/components/ActiveChart/index.js
@@ -1,5 +1,4 @@
import React, { Component } from 'react';
-
import { MiniArea } from '../Charts';
import NumberInfo from '../NumberInfo';
@@ -26,17 +25,29 @@ export default class ActiveChart extends Component {
};
componentDidMount() {
- this.timer = setInterval(() => {
- this.setState({
- activeData: getActiveData(),
- });
- }, 1000);
+ this.loopData();
}
componentWillUnmount() {
- clearInterval(this.timer);
+ clearTimeout(this.timer);
+ cancelAnimationFrame(this.requestRef);
}
+ loopData = () => {
+ this.requestRef = requestAnimationFrame(() => {
+ this.timer = setTimeout(() => {
+ this.setState(
+ {
+ activeData: getActiveData(),
+ },
+ () => {
+ this.loopData();
+ }
+ );
+ }, 1000);
+ });
+ };
+
render() {
const { activeData = [] } = this.state;
@@ -64,9 +75,17 @@ export default class ActiveChart extends Component {
/>
{activeData && (
-
-
{[...activeData].sort()[activeData.length - 1].y + 200} 亿元
-
{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元
+
+
+
{[...activeData].sort()[activeData.length - 1].y + 200} 亿元
+
{[...activeData].sort()[Math.floor(activeData.length / 2)].y} 亿元
+
+
+
)}
{activeData && (
diff --git a/src/components/ActiveChart/index.less b/src/components/ActiveChart/index.less
index 8ecc3237..99079a55 100644
--- a/src/components/ActiveChart/index.less
+++ b/src/components/ActiveChart/index.less
@@ -29,3 +29,23 @@
text-align: right;
}
}
+.dashedLine {
+ position: relative;
+ height: 1px;
+ top: -70px;
+ left: -3px;
+
+ .line {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-image: linear-gradient(to right, transparent 50%, #e9e9e9 50%);
+ background-size: 6px;
+ }
+}
+
+.dashedLine:last-child {
+ top: -36px;
+}
diff --git a/src/components/ArticleListContent/index.js b/src/components/ArticleListContent/index.js
new file mode 100644
index 00000000..c4525d6a
--- /dev/null
+++ b/src/components/ArticleListContent/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import moment from 'moment';
+import { Avatar } from 'antd';
+import styles from './index.less';
+
+const ArticleListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
+
+
{content}
+
+
+
{owner} 发布在
{href}
+
{moment(updatedAt).format('YYYY-MM-DD HH:mm')}
+
+
+);
+
+export default ArticleListContent;
diff --git a/src/components/ArticleListContent/index.less b/src/components/ArticleListContent/index.less
new file mode 100644
index 00000000..acf2c919
--- /dev/null
+++ b/src/components/ArticleListContent/index.less
@@ -0,0 +1,38 @@
+@import '~antd/lib/style/themes/default.less';
+
+.listContent {
+ .description {
+ line-height: 22px;
+ max-width: 720px;
+ }
+ .extra {
+ color: @text-color-secondary;
+ margin-top: 16px;
+ line-height: 22px;
+ & > :global(.ant-avatar) {
+ vertical-align: top;
+ margin-right: 8px;
+ width: 20px;
+ height: 20px;
+ position: relative;
+ top: 1px;
+ }
+ & > em {
+ color: @disabled-color;
+ font-style: normal;
+ margin-left: 16px;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xs) {
+ .listContent {
+ .extra {
+ & > em {
+ display: block;
+ margin-left: 0;
+ margin-top: 8px;
+ }
+ }
+ }
+}
diff --git a/src/components/Authorized/Authorized.js b/src/components/Authorized/Authorized.js
old mode 100755
new mode 100644
index 8e7aacb2..75d57b88
--- a/src/components/Authorized/Authorized.js
+++ b/src/components/Authorized/Authorized.js
@@ -1,12 +1,8 @@
-import React from 'react';
import CheckPermissions from './CheckPermissions';
-class Authorized extends React.Component {
- render() {
- const { children, authority, noMatch = null } = this.props;
- const childrenRender = typeof children === 'undefined' ? null : children;
- return CheckPermissions(authority, childrenRender, noMatch);
- }
-}
+const Authorized = ({ children, authority, noMatch = null }) => {
+ const childrenRender = typeof children === 'undefined' ? null : children;
+ return CheckPermissions(authority, childrenRender, noMatch);
+};
export default Authorized;
diff --git a/src/components/Authorized/AuthorizedRoute.d.ts b/src/components/Authorized/AuthorizedRoute.d.ts
new file mode 100644
index 00000000..912b283a
--- /dev/null
+++ b/src/components/Authorized/AuthorizedRoute.d.ts
@@ -0,0 +1,13 @@
+import * as React from 'react';
+import { RouteProps } from 'react-router';
+
+type authorityFN = (currentAuthority?: string) => boolean;
+
+type authority = string | string[] | authorityFN | Promise
;
+
+export interface IAuthorizedRouteProps extends RouteProps {
+ authority: authority;
+}
+export { authority };
+
+export class AuthorizedRoute extends React.Component {}
diff --git a/src/components/Authorized/AuthorizedRoute.js b/src/components/Authorized/AuthorizedRoute.js
index 1038d320..39c6a665 100644
--- a/src/components/Authorized/AuthorizedRoute.js
+++ b/src/components/Authorized/AuthorizedRoute.js
@@ -1,19 +1,15 @@
-import React from "react";
-import { Route, Redirect } from "react-router-dom";
-import {getAuthority} from "utils/authority";
+import React from 'react';
+import { Route, Redirect } from 'react-router-dom';
+import Authorized from './Authorized';
-//umi只会返回render和rest
-class AuthorizedRoute extends React.Component {
- render() {
- const { component: Component ,render}= this.props;
- const redirectPath = "/User/Login";
- const authority = getAuthority();
- if(authority){
- return (Component ? : render(this.props))
- }else{
- return ( )
- }
- }
-}
+// TODO: umi只会返回render和rest
+const AuthorizedRoute = ({ component: Component, render, authority, redirectPath, ...rest }) => (
+ } />}
+ >
+ (Component ? : render(props))} />
+
+);
export default AuthorizedRoute;
diff --git a/src/components/Authorized/CheckPermissions.js b/src/components/Authorized/CheckPermissions.js
old mode 100755
new mode 100644
index 8aaad1ce..ba83f5b9
--- a/src/components/Authorized/CheckPermissions.js
+++ b/src/components/Authorized/CheckPermissions.js
@@ -48,7 +48,7 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
if (Array.isArray(currentAuthority)) {
for (let i = 0; i < currentAuthority.length; i += 1) {
const element = currentAuthority[i];
- if (authority.indexOf(element) >= 0) {
+ if (authority === element) {
return target;
}
}
@@ -82,8 +82,7 @@ const checkPermissions = (authority, currentAuthority, target, Exception) => {
export { checkPermissions };
-const check = (authority, target, Exception) => {
- return checkPermissions(authority, CURRENT, target, Exception);
-};
+const check = (authority, target, Exception) =>
+ checkPermissions(authority, CURRENT, target, Exception);
export default check;
diff --git a/src/components/Authorized/CheckPermissions.test.js b/src/components/Authorized/CheckPermissions.test.js
old mode 100755
new mode 100644
index 1e66cb9e..3988d85a
--- a/src/components/Authorized/CheckPermissions.test.js
+++ b/src/components/Authorized/CheckPermissions.test.js
@@ -1,4 +1,4 @@
-import { checkPermissions } from './CheckPermissions.js';
+import { checkPermissions } from './CheckPermissions';
const target = 'ok';
const error = 'error';
diff --git a/src/components/Authorized/PromiseRender.js b/src/components/Authorized/PromiseRender.js
old mode 100755
new mode 100644
index 292d3b1a..8e2a4059
--- a/src/components/Authorized/PromiseRender.js
+++ b/src/components/Authorized/PromiseRender.js
@@ -10,7 +10,7 @@ export default class PromiseRender extends React.PureComponent {
this.setRenderComponent(this.props);
}
- componentWillReceiveProps(nextProps) {
+ componentDidUpdate(nextProps) {
// new Props enter
this.setRenderComponent(nextProps);
}
@@ -45,8 +45,9 @@ export default class PromiseRender extends React.PureComponent {
render() {
const { component: Component } = this.state;
+ const { ok, error, promise, ...rest } = this.props;
return Component ? (
-
+
) : (
;
+const Exception403 = () =>
;
// Determine whether the incoming component has been instantiated
// AuthorizedRoute is already instantiated
@@ -20,12 +20,12 @@ const checkIsInstantiation = target => {
/**
* 用于判断是否拥有权限访问此view权限
- * authority 支持传入 string ,funtion:()=>boolean|Promise
+ * authority 支持传入 string, function:()=>boolean|Promise
* e.g. 'user' 只有user用户能访问
* e.g. 'user,admin' user和 admin 都能访问
* e.g. ()=>boolean 返回true能访问,返回false不能访问
* e.g. Promise then 能访问 catch不能访问
- * e.g. authority support incoming string, funtion: () => boolean | Promise
+ * e.g. authority support incoming string, function: () => boolean | Promise
* e.g. 'user' only user user can access
* e.g. 'user, admin' user and admin can access
* e.g. () => boolean true to be able to visit, return false can not be accessed
diff --git a/src/components/Authorized/demo/AuthorizedArray.md b/src/components/Authorized/demo/AuthorizedArray.md
old mode 100755
new mode 100644
diff --git a/src/components/Authorized/demo/AuthorizedFunction.md b/src/components/Authorized/demo/AuthorizedFunction.md
old mode 100755
new mode 100644
diff --git a/src/components/Authorized/demo/basic.md b/src/components/Authorized/demo/basic.md
old mode 100755
new mode 100644
diff --git a/src/components/Authorized/demo/secured.md b/src/components/Authorized/demo/secured.md
old mode 100755
new mode 100644
diff --git a/src/components/Authorized/index.d.ts b/src/components/Authorized/index.d.ts
old mode 100755
new mode 100644
index ba9bc5b2..b3e2f56c
--- a/src/components/Authorized/index.d.ts
+++ b/src/components/Authorized/index.d.ts
@@ -1,41 +1,30 @@
import * as React from 'react';
-import { RouteProps } from 'react-router';
-
-type authorityFN = (currentAuthority?: string) => boolean;
-
-type authority = string | Array
| authorityFN | Promise;
-
+import AuthorizedRoute, { authority } from './AuthorizedRoute';
export type IReactComponent =
| React.StatelessComponent
| React.ComponentClass
| React.ClassicComponentClass
;
-interface Secured {
- (authority: authority, error?: React.ReactNode): (target: T) => T;
-}
-
-export interface AuthorizedRouteProps extends RouteProps {
- authority: authority;
-}
-export class AuthorizedRoute extends React.Component {}
+type Secured = (
+ authority: authority,
+ error?: React.ReactNode
+) => (target: T) => T;
-interface check {
- (
- authority: authority,
- target: T,
- Exception: S
- ): T | S;
-}
+type check = (
+ authority: authority,
+ target: T,
+ Exception: S
+) => T | S;
-interface AuthorizedProps {
+export interface IAuthorizedProps {
authority: authority;
noMatch?: React.ReactNode;
}
-export class Authorized extends React.Component {
- static Secured: Secured;
- static AuthorizedRoute: typeof AuthorizedRoute;
- static check: check;
+export class Authorized extends React.Component {
+ public static Secured: Secured;
+ public static AuthorizedRoute: typeof AuthorizedRoute;
+ public static check: check;
}
declare function renderAuthorize(currentAuthority: string): typeof Authorized;
diff --git a/src/components/Authorized/index.js b/src/components/Authorized/index.js
old mode 100755
new mode 100644
index 91eabf87..22ac664d
--- a/src/components/Authorized/index.js
+++ b/src/components/Authorized/index.js
@@ -1,7 +1,7 @@
import Authorized from './Authorized';
import AuthorizedRoute from './AuthorizedRoute';
import Secured from './Secured';
-import check from './CheckPermissions.js';
+import check from './CheckPermissions';
import renderAuthorize from './renderAuthorize';
Authorized.Secured = Secured;
diff --git a/src/components/Authorized/index.md b/src/components/Authorized/index.md
old mode 100755
new mode 100644
diff --git a/src/components/Authorized/renderAuthorize.js b/src/components/Authorized/renderAuthorize.js
old mode 100755
new mode 100644
index 16177ede..be373d99
--- a/src/components/Authorized/renderAuthorize.js
+++ b/src/components/Authorized/renderAuthorize.js
@@ -4,23 +4,21 @@ let CURRENT = 'NULL';
* use authority or getAuthority
* @param {string|()=>String} currentAuthority
*/
-const renderAuthorize = Authorized => {
- return currentAuthority => {
- if (currentAuthority) {
- if (currentAuthority.constructor.name === 'Function') {
- CURRENT = currentAuthority();
- }
- if (
- currentAuthority.constructor.name === 'String' ||
- currentAuthority.constructor.name === 'Array'
- ) {
- CURRENT = currentAuthority;
- }
- } else {
- CURRENT = 'NULL';
+const renderAuthorize = Authorized => currentAuthority => {
+ if (currentAuthority) {
+ if (typeof currentAuthority === 'function') {
+ CURRENT = currentAuthority();
}
- return Authorized;
- };
+ if (
+ Object.prototype.toString.call(currentAuthority) === '[object String]' ||
+ Array.isArray(currentAuthority)
+ ) {
+ CURRENT = currentAuthority;
+ }
+ } else {
+ CURRENT = 'NULL';
+ }
+ return Authorized;
};
export { CURRENT };
diff --git a/src/components/AvatarList/index.en-US.md b/src/components/AvatarList/index.en-US.md
index 58daaf96..862446f2 100644
--- a/src/components/AvatarList/index.en-US.md
+++ b/src/components/AvatarList/index.en-US.md
@@ -18,5 +18,5 @@ A list of user's avatar for project or group member list frequently. If a large
| Property | Description | Type | Default |
|----------|------------------------------------------|-------------|-------|
-| tips | title tips for avatar item | ReactNode\/string | - |
+| tips | title tips for avatar item | ReactNode | - |
| src | the address of the image for an image avatar | string | - |
diff --git a/src/components/AvatarList/index.zh-CN.md b/src/components/AvatarList/index.zh-CN.md
index c229a5df..4ec0c136 100644
--- a/src/components/AvatarList/index.zh-CN.md
+++ b/src/components/AvatarList/index.zh-CN.md
@@ -19,5 +19,5 @@ cols: 1
| 参数 | 说明 | 类型 | 默认值 |
|----------|------------------------------------------|-------------|-------|
-| tips | 头像展示文案 | ReactNode\/string | - |
+| tips | 头像展示文案 | ReactNode | - |
| src | 头像图片连接 | string | - |
diff --git a/src/components/Charts/Bar/index.js b/src/components/Charts/Bar/index.js
index a3c36084..f0cb65ff 100644
--- a/src/components/Charts/Bar/index.js
+++ b/src/components/Charts/Bar/index.js
@@ -12,13 +12,21 @@ class Bar extends Component {
};
componentDidMount() {
- window.addEventListener('resize', this.resize);
+ window.addEventListener('resize', this.resize, { passive: true });
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
+ handleRoot = n => {
+ this.root = n;
+ };
+
+ handleRef = n => {
+ this.node = n;
+ };
+
@Bind()
@Debounce(400)
resize() {
@@ -46,14 +54,6 @@ class Bar extends Component {
}
}
- handleRoot = n => {
- this.root = n;
- };
-
- handleRef = n => {
- this.node = n;
- };
-
render() {
const {
height,
diff --git a/src/components/Charts/ChartCard/index.d.ts b/src/components/Charts/ChartCard/index.d.ts
index d174593f..0437c0c8 100644
--- a/src/components/Charts/ChartCard/index.d.ts
+++ b/src/components/Charts/ChartCard/index.d.ts
@@ -1,5 +1,7 @@
+import { CardProps } from 'antd/lib/card';
import * as React from 'react';
-export interface IChartCardProps {
+
+export interface IChartCardProps extends CardProps {
title: React.ReactNode;
action?: React.ReactNode;
total?: React.ReactNode | number | (() => React.ReactNode | number);
diff --git a/src/components/Charts/ChartCard/index.js b/src/components/Charts/ChartCard/index.js
index c3ee1865..ca6bcb2e 100644
--- a/src/components/Charts/ChartCard/index.js
+++ b/src/components/Charts/ChartCard/index.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { Card, Spin } from 'antd';
+import { Card } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
@@ -19,59 +19,64 @@ const renderTotal = total => {
return totalDom;
};
-const ChartCard = ({
- loading = false,
- contentHeight,
- title,
- avatar,
- action,
- total,
- footer,
- children,
- ...rest
-}) => {
- const content = (
-
-
-
{avatar}
-
-
- {title}
- {action}
-
- {renderTotal(total)}
-
-
- {children && (
-
- )}
- {footer && (
+class ChartCard extends React.PureComponent {
+ renderConnet = () => {
+ const { contentHeight, title, avatar, action, total, footer, children, loading } = this.props;
+ if (loading) {
+ return false;
+ }
+ return (
+
- {footer}
+
{avatar}
+
+
+ {title}
+ {action}
+
+ {renderTotal(total)}
+
- )}
-
- );
+ {children && (
+
+ )}
+ {footer && (
+
+ {footer}
+
+ )}
+
+ );
+ };
- return (
-
- {
-
- {content}
-
- }
-
- );
-};
+ render() {
+ const {
+ loading = false,
+ contentHeight,
+ title,
+ avatar,
+ action,
+ total,
+ footer,
+ children,
+ ...rest
+ } = this.props;
+ return (
+
+ {this.renderConnet()}
+
+ );
+ }
+}
export default ChartCard;
diff --git a/src/components/Charts/ChartCard/index.less b/src/components/Charts/ChartCard/index.less
index fa2eb16d..bee44938 100644
--- a/src/components/Charts/ChartCard/index.less
+++ b/src/components/Charts/ChartCard/index.less
@@ -72,7 +72,3 @@
margin-top: 20px;
}
}
-
-.spin :global(.ant-spin-container) {
- overflow: visible;
-}
diff --git a/src/components/Charts/Field/index.js b/src/components/Charts/Field/index.js
index 0f9ace22..22dca86c 100644
--- a/src/components/Charts/Field/index.js
+++ b/src/components/Charts/Field/index.js
@@ -4,8 +4,8 @@ import styles from './index.less';
const Field = ({ label, value, ...rest }) => (
- {label}
- {value}
+ {label}
+ {value}
);
diff --git a/src/components/Charts/Field/index.less b/src/components/Charts/Field/index.less
index aeafbcb8..170ddc1d 100644
--- a/src/components/Charts/Field/index.less
+++ b/src/components/Charts/Field/index.less
@@ -5,12 +5,13 @@
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
- span {
+ .label,
+ .number {
font-size: @font-size-base;
line-height: 22px;
}
- span:last-child {
- margin-left: 8px;
+ .number {
color: @heading-color;
+ margin-left: 8px;
}
}
diff --git a/src/components/Charts/Gauge/index.js b/src/components/Charts/Gauge/index.js
index d9289eaa..2249211a 100644
--- a/src/components/Charts/Gauge/index.js
+++ b/src/components/Charts/Gauge/index.js
@@ -52,7 +52,7 @@ Shape.registerShape('point', 'pointer', {
});
@autoHeight()
-export default class Gauge extends React.Component {
+class Gauge extends React.Component {
render() {
const {
title,
@@ -142,15 +142,13 @@ export default class Gauge extends React.Component {
/>
{
- return `
+ html={() => `
${title}
${data[0].value * 10}%
-
`;
- }}
+
`}
/>
;
}
diff --git a/src/components/Charts/MiniArea/index.js b/src/components/Charts/MiniArea/index.js
index a5526a95..d3209bec 100644
--- a/src/components/Charts/MiniArea/index.js
+++ b/src/components/Charts/MiniArea/index.js
@@ -4,7 +4,7 @@ import autoHeight from '../autoHeight';
import styles from '../index.less';
@autoHeight()
-export default class MiniArea extends React.Component {
+class MiniArea extends React.PureComponent {
render() {
const {
height,
@@ -104,3 +104,5 @@ export default class MiniArea extends React.Component {
);
}
}
+
+export default MiniArea;
diff --git a/src/components/Charts/MiniBar/index.js b/src/components/Charts/MiniBar/index.js
index 92ee6b51..18e4d8c6 100644
--- a/src/components/Charts/MiniBar/index.js
+++ b/src/components/Charts/MiniBar/index.js
@@ -4,7 +4,7 @@ import autoHeight from '../autoHeight';
import styles from '../index.less';
@autoHeight()
-export default class MiniBar extends React.Component {
+class MiniBar extends React.Component {
render() {
const { height, forceFit = true, color = '#1890FF', data = [] } = this.props;
@@ -48,3 +48,4 @@ export default class MiniBar extends React.Component {
);
}
}
+export default MiniBar;
diff --git a/src/components/Charts/Pie/index.d.ts b/src/components/Charts/Pie/index.d.ts
index 46e4600d..66c93eeb 100644
--- a/src/components/Charts/Pie/index.d.ts
+++ b/src/components/Charts/Pie/index.d.ts
@@ -2,6 +2,7 @@ import * as React from 'react';
export interface IPieProps {
animate?: boolean;
color?: string;
+ colors?: string[];
height: number;
hasLegend?: boolean;
padding?: [number, number, number, number];
diff --git a/src/components/Charts/Pie/index.js b/src/components/Charts/Pie/index.js
index 0d0dc3c5..7dff5123 100644
--- a/src/components/Charts/Pie/index.js
+++ b/src/components/Charts/Pie/index.js
@@ -12,46 +12,50 @@ import styles from './index.less';
/* eslint react/no-danger:0 */
@autoHeight()
-export default class Pie extends Component {
+class Pie extends Component {
state = {
legendData: [],
legendBlock: false,
};
componentDidMount() {
- this.getLegendData();
- this.resize();
- window.addEventListener('resize', this.resize);
+ window.addEventListener(
+ 'resize',
+ () => {
+ this.requestRef = requestAnimationFrame(() => this.resize());
+ },
+ { passive: true }
+ );
}
- componentWillReceiveProps(nextProps) {
- if (this.props.data !== nextProps.data) {
+ componentDidUpdate(preProps) {
+ const { data } = this.props;
+ if (data !== preProps.data) {
// because of charts data create when rendered
// so there is a trick for get rendered time
- this.setState(
- {
- legendData: [...this.state.legendData],
- },
- () => {
- this.getLegendData();
- }
- );
+ this.getLegendData();
}
}
componentWillUnmount() {
+ window.cancelAnimationFrame(this.requestRef);
window.removeEventListener('resize', this.resize);
this.resize.cancel();
}
getG2Instance = chart => {
this.chart = chart;
+ requestAnimationFrame(() => {
+ this.getLegendData();
+ this.resize();
+ });
};
// for custom lengend view
getLegendData = () => {
if (!this.chart) return;
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
+ if (!geom) return;
const items = geom.get('dataArray') || []; // 获取图形对应的
const legendData = items.map(item => {
@@ -67,28 +71,6 @@ export default class Pie extends Component {
});
};
- // for window resize auto responsive legend
- @Bind()
- @Debounce(300)
- resize() {
- const { hasLegend } = this.props;
- if (!hasLegend || !this.root) {
- window.removeEventListener('resize', this.resize);
- return;
- }
- if (this.root.parentNode.clientWidth <= 380) {
- if (!this.state.legendBlock) {
- this.setState({
- legendBlock: true,
- });
- }
- } else if (this.state.legendBlock) {
- this.setState({
- legendBlock: false,
- });
- }
- }
-
handleRoot = n => {
this.root = n;
};
@@ -111,6 +93,29 @@ export default class Pie extends Component {
});
};
+ // for window resize auto responsive legend
+ @Bind()
+ @Debounce(300)
+ resize() {
+ const { hasLegend } = this.props;
+ const { legendBlock } = this.state;
+ if (!hasLegend || !this.root) {
+ window.removeEventListener('resize', this.resize);
+ return;
+ }
+ if (this.root.parentNode.clientWidth <= 380) {
+ if (!legendBlock) {
+ this.setState({
+ legendBlock: true,
+ });
+ }
+ } else if (legendBlock) {
+ this.setState({
+ legendBlock: false,
+ });
+ }
+ }
+
render() {
const {
valueFormat,
@@ -121,7 +126,7 @@ export default class Pie extends Component {
style,
height,
forceFit = true,
- percent = 0,
+ percent,
color,
inner = 0.75,
animate = true,
@@ -135,10 +140,20 @@ export default class Pie extends Component {
[styles.legendBlock]: legendBlock,
});
+ const {
+ data: propsData,
+ selected: propsSelected = true,
+ tooltip: propsTooltip = true,
+ } = this.props;
+
+ let data = propsData || [];
+ let selected = propsSelected;
+ let tooltip = propsTooltip;
+
const defaultColors = colors;
- let data = this.props.data || [];
- let selected = this.props.selected || true;
- let tooltip = this.props.tooltip || true;
+ data = data || [];
+ selected = selected || true;
+ tooltip = tooltip || true;
let formatColor;
const scale = {
@@ -151,15 +166,14 @@ export default class Pie extends Component {
},
};
- if (percent) {
+ if (percent || percent === 0) {
selected = false;
tooltip = false;
formatColor = value => {
if (value === '占比') {
return color || 'rgba(24, 144, 255, 0.85)';
- } else {
- return '#F0F2F5';
}
+ return '#F0F2F5';
};
data = [
@@ -212,7 +226,7 @@ export default class Pie extends Component {
tooltip={tooltip && tooltipFormat}
type="intervalStack"
position="percent"
- color={['x', percent ? formatColor : defaultColors]}
+ color={['x', percent || percent === 0 ? formatColor : defaultColors]}
selected={selected}
/>
@@ -242,7 +256,7 @@ export default class Pie extends Component {
{item.x}
- {`${(isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
+ {`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}
{valueFormat ? valueFormat(item.y) : item.y}
@@ -253,3 +267,5 @@ export default class Pie extends Component {
);
}
}
+
+export default Pie;
diff --git a/src/components/Charts/Pie/index.less b/src/components/Charts/Pie/index.less
index 277274cd..7249d093 100644
--- a/src/components/Charts/Pie/index.less
+++ b/src/components/Charts/Pie/index.less
@@ -61,7 +61,7 @@
left: 50%;
top: 50%;
text-align: center;
- height: 62px;
+ max-height: 62px;
transform: translate(-50%, -50%);
& > h4 {
color: @text-color-secondary;
diff --git a/src/components/Charts/Radar/index.js b/src/components/Charts/Radar/index.js
index fc3ab444..a0aa7fab 100644
--- a/src/components/Charts/Radar/index.js
+++ b/src/components/Charts/Radar/index.js
@@ -6,18 +6,19 @@ import styles from './index.less';
/* eslint react/no-danger:0 */
@autoHeight()
-export default class Radar extends Component {
+class Radar extends Component {
state = {
legendData: [],
};
componentDidMount() {
- this.getLengendData();
+ this.getLegendData();
}
- componentWillReceiveProps(nextProps) {
- if (this.props.data !== nextProps.data) {
- this.getLengendData();
+ componentDidUpdate(preProps) {
+ const { data } = this.props;
+ if (data !== preProps.data) {
+ this.getLegendData();
}
}
@@ -26,9 +27,10 @@ export default class Radar extends Component {
};
// for custom lengend view
- getLengendData = () => {
+ getLegendData = () => {
if (!this.chart) return;
const geom = this.chart.getAllGeoms()[0]; // 获取所有的图形
+ if (!geom) return;
const items = geom.get('dataArray') || []; // 获取图形对应的
const legendData = items.map(item => {
@@ -90,7 +92,7 @@ export default class Radar extends Component {
title,
hasLegend = false,
forceFit = true,
- tickCount = 4,
+ tickCount = 5,
padding = [35, 30, 16, 30],
animate = true,
colors = defaultColors,
@@ -178,3 +180,5 @@ export default class Radar extends Component {
);
}
}
+
+export default Radar;
diff --git a/src/components/Charts/TagCloud/index.js b/src/components/Charts/TagCloud/index.js
index 08418ea1..d94699bd 100644
--- a/src/components/Charts/TagCloud/index.js
+++ b/src/components/Charts/TagCloud/index.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import { Chart, Geom, Coord, Shape } from 'bizcharts';
+import { Chart, Geom, Coord, Shape, Tooltip } from 'bizcharts';
import DataSet from '@antv/data-set';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
@@ -19,24 +19,30 @@ class TagCloud extends Component {
};
componentDidMount() {
- this.initTagCloud();
- this.renderChart();
- window.addEventListener('resize', this.resize);
+ requestAnimationFrame(() => {
+ this.initTagCloud();
+ this.renderChart();
+ });
+ window.addEventListener('resize', this.resize, { passive: true });
}
- componentWillReceiveProps(nextProps) {
- if (JSON.stringify(nextProps.data) !== JSON.stringify(this.props.data)) {
- this.renderChart(nextProps);
+ componentDidUpdate(preProps) {
+ const { data } = this.props;
+ if (JSON.stringify(preProps.data) !== JSON.stringify(data)) {
+ this.renderChart(this.props);
}
}
componentWillUnmount() {
this.isUnmount = true;
+ window.cancelAnimationFrame(this.requestRef);
window.removeEventListener('resize', this.resize);
}
resize = () => {
- this.renderChart();
+ this.requestRef = requestAnimationFrame(() => {
+ this.renderChart();
+ });
};
saveRootRef = node => {
@@ -85,8 +91,8 @@ class TagCloud extends Component {
return;
}
- const h = height * 4;
- const w = this.root.offsetWidth * 4;
+ const h = height;
+ const w = this.root.offsetWidth;
const onload = () => {
const dv = new DataSet.View().source(data);
@@ -98,14 +104,14 @@ class TagCloud extends Component {
imageMask: this.imageMask,
font: 'Verdana',
size: [w, h], // 宽高设置最好根据 imageMask 做调整
- padding: 5,
+ padding: 0,
timeInterval: 5000, // max execute time
rotate() {
return 0;
},
fontSize(d) {
// eslint-disable-next-line
- return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
+ return Math.pow((d.value - min) / (max - min), 2) * (17.5 - 5) + 5;
},
});
@@ -152,8 +158,20 @@ class TagCloud extends Component {
y: { nice: false },
}}
>
+
-
+
)}
diff --git a/src/components/Charts/TagCloud/index.less b/src/components/Charts/TagCloud/index.less
index f5c12ad4..db8e4dab 100644
--- a/src/components/Charts/TagCloud/index.less
+++ b/src/components/Charts/TagCloud/index.less
@@ -1,7 +1,6 @@
.tagCloud {
overflow: hidden;
canvas {
- transform: scale(0.25);
transform-origin: 0 0;
}
}
diff --git a/src/components/Charts/TimelineChart/index.d.ts b/src/components/Charts/TimelineChart/index.d.ts
index d9312fe6..40b94325 100644
--- a/src/components/Charts/TimelineChart/index.d.ts
+++ b/src/components/Charts/TimelineChart/index.d.ts
@@ -1,11 +1,11 @@
import * as React from 'react';
export interface ITimelineChartProps {
data: Array<{
- x: string;
- y1: string;
- y2: string;
+ x: number;
+ y1: number;
+ y2?: number;
}>;
- titleMap: { y1: string; y2: string };
+ titleMap: { y1: string; y2?: string };
padding?: [number, number, number, number];
height?: number;
style?: React.CSSProperties;
diff --git a/src/components/Charts/TimelineChart/index.js b/src/components/Charts/TimelineChart/index.js
index e7ab6b11..b76e782f 100644
--- a/src/components/Charts/TimelineChart/index.js
+++ b/src/components/Charts/TimelineChart/index.js
@@ -6,7 +6,7 @@ import autoHeight from '../autoHeight';
import styles from './index.less';
@autoHeight()
-export default class TimelineChart extends React.Component {
+class TimelineChart extends React.Component {
render() {
const {
title,
@@ -44,8 +44,7 @@ export default class TimelineChart extends React.Component {
});
const dv = ds.createView();
- dv
- .source(data)
+ dv.source(data)
.transform({
type: 'filter',
callback: obj => {
@@ -121,3 +120,5 @@ export default class TimelineChart extends React.Component {
);
}
}
+
+export default TimelineChart;
diff --git a/src/components/Charts/WaterWave/index.js b/src/components/Charts/WaterWave/index.js
index 5b463ad5..055f7c73 100644
--- a/src/components/Charts/WaterWave/index.js
+++ b/src/components/Charts/WaterWave/index.js
@@ -7,7 +7,7 @@ import styles from './index.less';
// riddle: https://riddle.alibaba-inc.com/riddles/2d9a4b90
@autoHeight()
-export default class WaterWave extends PureComponent {
+class WaterWave extends PureComponent {
state = {
radio: 1,
};
@@ -15,8 +15,21 @@ export default class WaterWave extends PureComponent {
componentDidMount() {
this.renderChart();
this.resize();
+ window.addEventListener(
+ 'resize',
+ () => {
+ requestAnimationFrame(() => this.resize());
+ },
+ { passive: true }
+ );
+ }
- window.addEventListener('resize', this.resize);
+ componentDidUpdate(props) {
+ const { percent } = this.props;
+ if (props.percent !== percent) {
+ // 不加这个会造成绘制缓慢
+ this.renderChart('update');
+ }
}
componentWillUnmount() {
@@ -28,25 +41,27 @@ export default class WaterWave extends PureComponent {
}
resize = () => {
- const { height } = this.props;
- const { offsetWidth } = this.root.parentNode;
- this.setState({
- radio: offsetWidth < height ? offsetWidth / height : 1,
- });
+ if (this.root) {
+ const { height } = this.props;
+ const { offsetWidth } = this.root.parentNode;
+ this.setState({
+ radio: offsetWidth < height ? offsetWidth / height : 1,
+ });
+ }
};
- renderChart() {
+ renderChart(type) {
const { percent, color = '#1890FF' } = this.props;
const data = percent / 100;
const self = this;
+ cancelAnimationFrame(this.timer);
- if (!this.node || !data) {
+ if (!this.node || (data !== 0 && !data)) {
return;
}
const canvas = this.node;
const ctx = canvas.getContext('2d');
-
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const radius = canvasWidth / 2;
@@ -101,7 +116,7 @@ export default class WaterWave extends PureComponent {
const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
gradient.addColorStop(0, '#ffffff');
- gradient.addColorStop(1, '#1890FF');
+ gradient.addColorStop(1, color);
ctx.fillStyle = gradient;
ctx.fill();
ctx.restore();
@@ -109,7 +124,7 @@ export default class WaterWave extends PureComponent {
function render() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
- if (circleLock) {
+ if (circleLock && type !== 'update') {
if (arcStack.length) {
const temp = arcStack.shift();
ctx.lineTo(temp[0], temp[1]);
@@ -131,7 +146,7 @@ export default class WaterWave extends PureComponent {
ctx.restore();
ctx.clip();
- ctx.fillStyle = '#1890FF';
+ ctx.fillStyle = color;
}
} else {
if (data >= 0.85) {
@@ -166,7 +181,6 @@ export default class WaterWave extends PureComponent {
}
self.timer = requestAnimationFrame(render);
}
-
render();
}
@@ -195,3 +209,5 @@ export default class WaterWave extends PureComponent {
);
}
}
+
+export default WaterWave;
diff --git a/src/components/Charts/autoHeight.js b/src/components/Charts/autoHeight.js
index 01ae92dc..6ee9e098 100644
--- a/src/components/Charts/autoHeight.js
+++ b/src/components/Charts/autoHeight.js
@@ -30,8 +30,8 @@ function getAutoHeight(n) {
return height;
}
-const autoHeight = () => WrappedComponent => {
- return class extends React.Component {
+const autoHeight = () => WrappedComponent =>
+ class extends React.Component {
state = {
computedHeight: 0,
};
@@ -58,6 +58,5 @@ const autoHeight = () => WrappedComponent => {
);
}
};
-};
export default autoHeight;
diff --git a/src/components/Charts/bizcharts.d.ts b/src/components/Charts/bizcharts.d.ts
new file mode 100644
index 00000000..0815ffee
--- /dev/null
+++ b/src/components/Charts/bizcharts.d.ts
@@ -0,0 +1,3 @@
+import * as BizChart from 'bizcharts';
+
+export = BizChart;
diff --git a/src/components/Charts/bizcharts.js b/src/components/Charts/bizcharts.js
new file mode 100644
index 00000000..e08db8d6
--- /dev/null
+++ b/src/components/Charts/bizcharts.js
@@ -0,0 +1,3 @@
+import * as BizChart from 'bizcharts';
+
+export default BizChart;
diff --git a/src/components/CountDown/index.js b/src/components/CountDown/index.js
index 875fb1e2..7565bd82 100644
--- a/src/components/CountDown/index.js
+++ b/src/components/CountDown/index.js
@@ -3,34 +3,57 @@ import React, { Component } from 'react';
function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
+const initTime = props => {
+ let lastTime = 0;
+ let targetTime = 0;
+ try {
+ if (Object.prototype.toString.call(props.target) === '[object Date]') {
+ targetTime = props.target.getTime();
+ } else {
+ targetTime = new Date(props.target).getTime();
+ }
+ } catch (e) {
+ throw new Error('invalid target prop', e);
+ }
+
+ lastTime = targetTime - new Date().getTime();
+ return {
+ lastTime: lastTime < 0 ? 0 : lastTime,
+ };
+};
class CountDown extends Component {
- constructor(props) {
- super(props);
+ timer = 0;
- const { lastTime } = this.initTime(props);
+ interval = 1000;
+ constructor(props) {
+ super(props);
+ const { lastTime } = initTime(props);
this.state = {
lastTime,
};
}
+ static getDerivedStateFromProps(nextProps, preState) {
+ const { lastTime } = initTime(nextProps);
+ if (preState.lastTime !== lastTime) {
+ return {
+ lastTime,
+ };
+ }
+ return null;
+ }
+
componentDidMount() {
this.tick();
}
- componentWillReceiveProps(nextProps) {
- if (this.props.target !== nextProps.target) {
+ componentDidUpdate(prevProps) {
+ const { target } = this.props;
+ if (target !== prevProps.target) {
clearTimeout(this.timer);
- const { lastTime } = this.initTime(nextProps);
- this.setState(
- {
- lastTime,
- },
- () => {
- this.tick();
- }
- );
+ this.tick();
}
}
@@ -38,26 +61,6 @@ class CountDown extends Component {
clearTimeout(this.timer);
}
- timer = 0;
- interval = 1000;
- initTime = props => {
- let lastTime = 0;
- let targetTime = 0;
- try {
- if (Object.prototype.toString.call(props.target) === '[object Date]') {
- targetTime = props.target.getTime();
- } else {
- targetTime = new Date(props.target).getTime();
- }
- } catch (e) {
- throw new Error('invalid target prop', e);
- }
-
- lastTime = targetTime - new Date().getTime();
- return {
- lastTime: lastTime < 0 ? 0 : lastTime,
- };
- };
// defaultFormat = time => (
// {moment(time).format('hh:mm:ss')}
// );
@@ -74,6 +77,7 @@ class CountDown extends Component {
);
};
+
tick = () => {
const { onEnd } = this.props;
let { lastTime } = this.state;
diff --git a/src/components/DescriptionList/Description.js b/src/components/DescriptionList/Description.js
index e024796e..fce9fd3c 100644
--- a/src/components/DescriptionList/Description.js
+++ b/src/components/DescriptionList/Description.js
@@ -1,19 +1,15 @@
import React from 'react';
import PropTypes from 'prop-types';
-import classNames from 'classnames';
import { Col } from 'antd';
import styles from './index.less';
import responsive from './responsive';
-const Description = ({ term, column, className, children, ...restProps }) => {
- const clsString = classNames(styles.description, className);
- return (
-
- {term && {term}
}
- {children && {children}
}
-
- );
-};
+const Description = ({ term, column, children, ...restProps }) => (
+
+ {term && {term}
}
+ {children !== null && children !== undefined && {children}
}
+
+);
Description.defaultProps = {
term: '',
diff --git a/src/components/DescriptionList/DescriptionList.js b/src/components/DescriptionList/DescriptionList.js
index 382d7e85..73bb5f5f 100644
--- a/src/components/DescriptionList/DescriptionList.js
+++ b/src/components/DescriptionList/DescriptionList.js
@@ -22,7 +22,10 @@ const DescriptionList = ({
{title ?
{title}
: null}
- {React.Children.map(children, child => child ? React.cloneElement(child, { column }) : child)}
+ {React.Children.map(
+ children,
+ child => (child ? React.cloneElement(child, { column }) : child)
+ )}
);
diff --git a/src/components/DescriptionList/index.less b/src/components/DescriptionList/index.less
index bcb6fd1d..bfb33fcc 100644
--- a/src/components/DescriptionList/index.less
+++ b/src/components/DescriptionList/index.less
@@ -34,7 +34,7 @@
}
.detail {
- line-height: 22px;
+ line-height: 20px;
width: 100%;
padding-bottom: 16px;
color: @text-color;
diff --git a/src/components/DescriptionList/index.md b/src/components/DescriptionList/index.md
deleted file mode 100644
index bfa60cb8..00000000
--- a/src/components/DescriptionList/index.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title:
- en-US: DescriptionList
- zh-CN: DescriptionList
-subtitle: 描述列表
-cols: 1
-order: 4
----
-
-成组展示多个只读字段,常见于详情页的信息展示。
-
-## API
-
-### DescriptionList
-
-| 参数 | 说明 | 类型 | 默认值 |
-|----------|------------------------------------------|-------------|-------|
-| layout | 布局方式 | Enum{'horizontal', 'vertical'} | 'horizontal' |
-| col | 指定信息最多分几列展示,最终一行几列由 col 配置结合[响应式规则](/components/DescriptionList#响应式规则)决定 | number(0 < col <= 4) | 3 |
-| title | 列表标题 | ReactNode | - |
-| gutter | 列表项间距,单位为 `px` | number | 32 |
-| size | 列表型号,可以设置为 `large` `small` | Enum{'large', 'small'} | - |
-
-#### 响应式规则
-
-| 窗口宽度 | 展示列数 |
-|---------------------|---------------------------------------------|
-| `≥768px` | `col` |
-| `≥576px` | `col < 2 ? col : 2` |
-| `<576px` | `1` |
-
-### DescriptionList.Description
-
-| 参数 | 说明 | 类型 | 默认值 |
-|----------|------------------------------------------|-------------|-------|
-| term | 列表项标题 | ReactNode | - |
-
-
-
diff --git a/src/components/EditableItem/index.js b/src/components/EditableItem/index.js
index fcda844e..5b2ef002 100644
--- a/src/components/EditableItem/index.js
+++ b/src/components/EditableItem/index.js
@@ -3,23 +3,32 @@ import { Input, Icon } from 'antd';
import styles from './index.less';
export default class EditableItem extends PureComponent {
- state = {
- value: this.props.value,
- editable: false,
- };
+ constructor(props) {
+ super(props);
+ this.state = {
+ value: props.value,
+ editable: false,
+ };
+ }
+
handleChange = e => {
const { value } = e.target;
this.setState({ value });
};
+
check = () => {
this.setState({ editable: false });
- if (this.props.onChange) {
- this.props.onChange(this.state.value);
+ const { value } = this.state;
+ const { onChange } = this.state;
+ if (onChange) {
+ onChange(value);
}
};
+
edit = () => {
this.setState({ editable: true });
};
+
render() {
const { value, editable } = this.state;
return (
diff --git a/src/components/EditableLinkGroup/index.js b/src/components/EditableLinkGroup/index.js
index e230c3dd..ae3d93c7 100644
--- a/src/components/EditableLinkGroup/index.js
+++ b/src/components/EditableLinkGroup/index.js
@@ -6,18 +6,18 @@ import styles from './index.less';
// TODO: 添加逻辑
class EditableLinkGroup extends PureComponent {
- static defaultProps = {
- links: [],
- onAdd: () => {},
- linkElement: 'a',
- };
-
static propTypes = {
links: PropTypes.array,
onAdd: PropTypes.func,
linkElement: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
};
+ static defaultProps = {
+ links: [],
+ onAdd: () => {},
+ linkElement: 'a',
+ };
+
render() {
const { links, linkElement, onAdd } = this.props;
return (
diff --git a/src/components/Ellipsis/index.d.ts b/src/components/Ellipsis/index.d.ts
index 4643ee76..6c95bc0a 100644
--- a/src/components/Ellipsis/index.d.ts
+++ b/src/components/Ellipsis/index.d.ts
@@ -1,10 +1,21 @@
import * as React from 'react';
+import { TooltipProps } from 'antd/lib/tooltip';
+
+export interface IEllipsisTooltipProps extends TooltipProps {
+ title?: undefined;
+ overlayStyle?: undefined;
+}
+
export interface IEllipsisProps {
- tooltip?: boolean;
+ tooltip?: boolean | IEllipsisTooltipProps;
length?: number;
lines?: number;
style?: React.CSSProperties;
className?: string;
+ fullWidthRecognition?: boolean;
}
+export function getStrFullLength(str: string): number;
+export function cutStrByFullLength(str: string, maxLength: number): number;
+
export default class Ellipsis extends React.Component {}
diff --git a/src/components/Ellipsis/index.en-US.md b/src/components/Ellipsis/index.en-US.md
index fa5beb64..15139cc9 100644
--- a/src/components/Ellipsis/index.en-US.md
+++ b/src/components/Ellipsis/index.en-US.md
@@ -13,3 +13,4 @@ Property | Description | Type | Default
tooltip | tooltip for showing the full text content when hovering over | boolean | -
length | maximum number of characters in the text before being truncated | number | -
lines | maximum number of rows in the text before being truncated | number | `1`
+fullWidthRecognition | whether consider full-width character length as 2 when calculate string length | boolean | -
diff --git a/src/components/Ellipsis/index.js b/src/components/Ellipsis/index.js
index 93c5c235..de700b74 100644
--- a/src/components/Ellipsis/index.js
+++ b/src/components/Ellipsis/index.js
@@ -8,11 +8,50 @@ import styles from './index.less';
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
-const EllipsisText = ({ text, length, tooltip, ...other }) => {
+const TooltipOverlayStyle = {
+ overflowWrap: 'break-word',
+ wordWrap: 'break-word',
+};
+
+export const getStrFullLength = (str = '') =>
+ str.split('').reduce((pre, cur) => {
+ const charCode = cur.charCodeAt(0);
+ if (charCode >= 0 && charCode <= 128) {
+ return pre + 1;
+ }
+ return pre + 2;
+ }, 0);
+
+export const cutStrByFullLength = (str = '', maxLength) => {
+ let showLength = 0;
+ return str.split('').reduce((pre, cur) => {
+ const charCode = cur.charCodeAt(0);
+ if (charCode >= 0 && charCode <= 128) {
+ showLength += 1;
+ } else {
+ showLength += 2;
+ }
+ if (showLength <= maxLength) {
+ return pre + cur;
+ }
+ return pre;
+ }, '');
+};
+
+const getTooltip = ({ tooltip, overlayStyle, title, children }) => {
+ if (tooltip) {
+ const props = tooltip === true ? { overlayStyle, title } : { ...tooltip, overlayStyle, title };
+ return {children} ;
+ }
+ return children;
+};
+
+const EllipsisText = ({ text, length, tooltip, fullWidthRecognition, ...other }) => {
if (typeof text !== 'string') {
throw new Error('Ellipsis children must be string.');
}
- if (text.length <= length || length < 0) {
+ const textLength = fullWidthRecognition ? getStrFullLength(text) : text.length;
+ if (textLength <= length || length < 0) {
return {text} ;
}
const tail = '...';
@@ -20,26 +59,21 @@ const EllipsisText = ({ text, length, tooltip, ...other }) => {
if (length - tail.length <= 0) {
displayText = '';
} else {
- displayText = text.slice(0, length - tail.length);
+ displayText = fullWidthRecognition ? cutStrByFullLength(text, length) : text.slice(0, length);
}
- if (tooltip) {
- return (
-
-
- {displayText}
- {tail}
-
-
- );
- }
-
- return (
-
- {displayText}
- {tail}
-
- );
+ const spanAttrs = tooltip ? {} : { ...other };
+ return getTooltip({
+ tooltip,
+ overlayStyle: TooltipOverlayStyle,
+ title: text,
+ children: (
+
+ {displayText}
+ {tail}
+
+ ),
+ });
};
export default class Ellipsis extends Component {
@@ -54,8 +88,9 @@ export default class Ellipsis extends Component {
}
}
- componentWillReceiveProps(nextProps) {
- if (this.props.lines !== nextProps.lines) {
+ componentDidUpdate(perProps) {
+ const { lines } = this.props;
+ if (lines !== perProps.lines) {
this.computeLine();
}
}
@@ -63,7 +98,7 @@ export default class Ellipsis extends Component {
computeLine = () => {
const { lines } = this.props;
if (lines && !isSupportLineClamp) {
- const text = this.shadowChildren.innerText;
+ const text = this.shadowChildren.innerText || this.shadowChildren.textContent;
const lineHeight = parseInt(getComputedStyle(this.root).lineHeight, 10);
const targetHeight = lines * lineHeight;
this.content.style.height = `${targetHeight}px`;
@@ -80,7 +115,7 @@ export default class Ellipsis extends Component {
// bisection
const len = text.length;
- const mid = Math.floor(len / 2);
+ const mid = Math.ceil(len / 2);
const count = this.bisection(targetHeight, mid, 0, len, text, shadowNode);
@@ -102,27 +137,28 @@ export default class Ellipsis extends Component {
if (sh <= th) {
shadowNode.innerHTML = text.substring(0, mid + 1) + suffix;
sh = shadowNode.offsetHeight;
- if (sh > th) {
+ if (sh > th || mid === begin) {
return mid;
- } else {
- begin = mid;
- mid = Math.floor((end - begin) / 2) + begin;
- return this.bisection(th, mid, begin, end, text, shadowNode);
}
- } else {
- if (mid - 1 < 0) {
- return mid;
- }
- shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
- sh = shadowNode.offsetHeight;
- if (sh <= th) {
- return mid - 1;
+ begin = mid;
+ if (end - begin === 1) {
+ mid = 1 + begin;
} else {
- end = mid;
mid = Math.floor((end - begin) / 2) + begin;
- return this.bisection(th, mid, begin, end, text, shadowNode);
}
+ return this.bisection(th, mid, begin, end, text, shadowNode);
}
+ if (mid - 1 < 0) {
+ return mid;
+ }
+ shadowNode.innerHTML = text.substring(0, mid - 1) + suffix;
+ sh = shadowNode.offsetHeight;
+ if (sh <= th) {
+ return mid - 1;
+ }
+ end = mid;
+ mid = Math.floor((end - begin) / 2) + begin;
+ return this.bisection(th, mid, begin, end, text, shadowNode);
};
handleRoot = n => {
@@ -147,7 +183,15 @@ export default class Ellipsis extends Component {
render() {
const { text, targetCount } = this.state;
- const { children, lines, length, className, tooltip, ...restProps } = this.props;
+ const {
+ children,
+ lines,
+ length,
+ className,
+ tooltip,
+ fullWidthRecognition,
+ ...restProps
+ } = this.props;
const cls = classNames(styles.ellipsis, className, {
[styles.lines]: lines && !isSupportLineClamp,
@@ -170,6 +214,7 @@ export default class Ellipsis extends Component {
length={length}
text={children || ''}
tooltip={tooltip}
+ fullWidthRecognition={fullWidthRecognition}
{...restProps}
/>
);
@@ -180,18 +225,20 @@ export default class Ellipsis extends Component {
// support document.body.style.webkitLineClamp
if (isSupportLineClamp) {
const style = `#${id}{-webkit-line-clamp:${lines};-webkit-box-orient: vertical;}`;
- return (
+
+ const node = (
- {tooltip ? (
-
- {children}
-
- ) : (
- children
- )}
+ {children}
);
+
+ return getTooltip({
+ tooltip,
+ overlayStyle: TooltipOverlayStyle,
+ title: children,
+ children: node,
+ });
}
const childNode = (
@@ -204,13 +251,12 @@ export default class Ellipsis extends Component {
return (
- {tooltip ? (
-
- {childNode}
-
- ) : (
- childNode
- )}
+ {getTooltip({
+ tooltip,
+ overlayStyle: TooltipOverlayStyle,
+ title: text,
+ children: childNode,
+ })}
{children}
diff --git a/src/components/Ellipsis/index.less b/src/components/Ellipsis/index.less
index dd59e3fa..2c4a867c 100644
--- a/src/components/Ellipsis/index.less
+++ b/src/components/Ellipsis/index.less
@@ -9,7 +9,7 @@
position: relative;
.shadow {
display: block;
- position: relative;
+ position: absolute;
color: transparent;
opacity: 0;
z-index: -999;
diff --git a/src/components/Ellipsis/index.md b/src/components/Ellipsis/index.md
deleted file mode 100644
index e53cd1ce..00000000
--- a/src/components/Ellipsis/index.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title:
- en-US: Ellipsis
- zh-CN: Ellipsis
-subtitle: 文本自动省略号
-cols: 1
-order: 10
----
-
-文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
-
-## API
-
-参数 | 说明 | 类型 | 默认值
-----|------|-----|------
-tooltip | 移动到文本展示完整内容的提示 | boolean | -
-length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -
-lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1`
diff --git a/src/components/Ellipsis/index.test.js b/src/components/Ellipsis/index.test.js
new file mode 100644
index 00000000..4d057b24
--- /dev/null
+++ b/src/components/Ellipsis/index.test.js
@@ -0,0 +1,13 @@
+import { getStrFullLength, cutStrByFullLength } from './index';
+
+describe('test calculateShowLength', () => {
+ it('get full length', () => {
+ expect(getStrFullLength('一二,a,')).toEqual(8);
+ });
+ it('cut str by full length', () => {
+ expect(cutStrByFullLength('一二,a,', 7)).toEqual('一二,a');
+ });
+ it('cut str when length small', () => {
+ expect(cutStrByFullLength('一22三', 5)).toEqual('一22');
+ });
+});
diff --git a/src/components/Ellipsis/index.zh-CN.md b/src/components/Ellipsis/index.zh-CN.md
index 8fe98bb0..f7a70ead 100644
--- a/src/components/Ellipsis/index.zh-CN.md
+++ b/src/components/Ellipsis/index.zh-CN.md
@@ -14,3 +14,4 @@ order: 10
tooltip | 移动到文本展示完整内容的提示 | boolean | -
length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -
lines | 在按照行数截取下最大的行数,超过则截取省略 | number | `1`
+fullWidthRecognition | 是否将全角字符的长度视为2来计算字符串长度 | boolean | -
diff --git a/src/components/Exception/index.d.ts b/src/components/Exception/index.d.ts
index 5e32516a..a74abb1f 100644
--- a/src/components/Exception/index.d.ts
+++ b/src/components/Exception/index.d.ts
@@ -5,8 +5,11 @@ export interface IExceptionProps {
desc?: React.ReactNode;
img?: string;
actions?: React.ReactNode;
- linkElement?: React.ReactNode;
+ linkElement?: string | React.ComponentType;
style?: React.CSSProperties;
+ className?: string;
+ backText?: React.ReactNode;
+ redirect?: string;
}
export default class Exception extends React.Component
{}
diff --git a/src/components/Exception/index.en-US.md b/src/components/Exception/index.en-US.md
index 320b0e71..37e7e807 100644
--- a/src/components/Exception/index.en-US.md
+++ b/src/components/Exception/index.en-US.md
@@ -10,9 +10,11 @@ Exceptions page is used to provide feedback on specific abnormal state. Usually,
Property | Description | Type | Default
---------|-------------|------|--------
+| backText | default return button text | ReactNode | back to home |
type | type of exception, the corresponding default `title`, `desc`, `img` will be given if set, which can be overridden by explicit setting of `title`, `desc`, `img` | Enum {'403', '404', '500'} | -
title | title | ReactNode | -
desc | supplementary description | ReactNode | -
img | the url of background image | string | -
actions | suggested operations, a default 'Home' link will show if not set | ReactNode | -
-linkElement | to specify the element of link | string\|ReactElement | 'a'
\ No newline at end of file
+linkElement | to specify the element of link | string\|ReactElement | 'a'
+redirect | redirect path | string | '/'
\ No newline at end of file
diff --git a/src/components/Exception/index.js b/src/components/Exception/index.js
index 14703e34..2c7223cc 100644
--- a/src/components/Exception/index.js
+++ b/src/components/Exception/index.js
@@ -4,34 +4,58 @@ import { Button } from 'antd';
import config from './typeConfig';
import styles from './index.less';
-const Exception = ({ className, linkElement = 'a', type, title, desc, img, actions, ...rest }) => {
- const pageType = type in config ? type : '404';
- const clsString = classNames(styles.exception, className);
- return (
-
-
-
-
{title || config[pageType].title}
-
{desc || config[pageType].desc}
-
- {actions ||
- createElement(
- linkElement,
- {
- to: '/',
- href: '/',
- },
-
返回首页
- )}
+class Exception extends React.PureComponent {
+ static defaultProps = {
+ backText: 'back to home',
+ redirect: '/',
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {};
+ }
+
+ render() {
+ const {
+ className,
+ backText,
+ linkElement = 'a',
+ type,
+ title,
+ desc,
+ img,
+ actions,
+ redirect,
+ ...rest
+ } = this.props;
+ const pageType = type in config ? type : '404';
+ const clsString = classNames(styles.exception, className);
+ return (
+
+
+
+
{title || config[pageType].title}
+
{desc || config[pageType].desc}
+
+ {actions ||
+ createElement(
+ linkElement,
+ {
+ to: redirect,
+ href: redirect,
+ },
+ {backText}
+ )}
+
-
- );
-};
+ );
+ }
+}
export default Exception;
diff --git a/src/components/Exception/index.less b/src/components/Exception/index.less
index 5ef378be..b55fe3a9 100644
--- a/src/components/Exception/index.less
+++ b/src/components/Exception/index.less
@@ -3,7 +3,8 @@
.exception {
display: flex;
align-items: center;
- height: 100%;
+ height: 80%;
+ min-height: 500px;
.imgBlock {
flex: 0 0 62.5%;
diff --git a/src/components/Exception/index.md b/src/components/Exception/index.md
deleted file mode 100644
index 844146e0..00000000
--- a/src/components/Exception/index.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title:
- en-US: Exception
- zh-CN: Exception
-subtitle: 异常
-cols: 1
-order: 5
----
-
-异常页用于对页面特定的异常状态进行反馈。通常,它包含对错误状态的阐述,并向用户提供建议或操作,避免用户感到迷失和困惑。
-
-## API
-
-| 参数 | 说明 | 类型 | 默认值 |
-|-------------|------------------------------------------|-------------|-------|
-| type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - |
-| title | 标题 | ReactNode | - |
-| desc | 补充描述 | ReactNode | - |
-| img | 背景图片地址 | string | - |
-| actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - |
-| linkElement | 定义链接的元素,默认为 `a` | string\|ReactElement | - |
diff --git a/src/components/Exception/index.zh-CN.md b/src/components/Exception/index.zh-CN.md
index 13e4e7ef..2e64399f 100644
--- a/src/components/Exception/index.zh-CN.md
+++ b/src/components/Exception/index.zh-CN.md
@@ -9,11 +9,13 @@ order: 5
## API
-| 参数 | 说明 | 类型 | 默认值 |
+| 参数 | 说明| 类型 | 默认值 |
|-------------|------------------------------------------|-------------|-------|
-| type | 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - |
-| title | 标题 | ReactNode | - |
-| desc | 补充描述 | ReactNode | - |
-| img | 背景图片地址 | string | - |
-| actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效 | ReactNode | - |
+| backText| 默认的返回按钮文本 | ReactNode| back to home |
+| type| 页面类型,若配置,则自带对应类型默认的 `title`,`desc`,`img`,此默认设置可以被 `title`,`desc`,`img` 覆盖 | Enum {'403', '404', '500'} | - |
+| title | 标题 | ReactNode| -|
+| desc| 补充描述| ReactNode| -|
+| img | 背景图片地址 | string| -|
+| actions | 建议操作,配置此属性时默认的『返回首页』按钮不生效| ReactNode| -|
| linkElement | 定义链接的元素 | string\|ReactElement | 'a' |
+| redirect | 返回按钮的跳转地址 | string | '/'
diff --git a/src/components/FooterToolbar/index.js b/src/components/FooterToolbar/index.js
index d5ce75b8..d43f72fb 100644
--- a/src/components/FooterToolbar/index.js
+++ b/src/components/FooterToolbar/index.js
@@ -1,12 +1,44 @@
import React, { Component } from 'react';
+import PropTypes from 'prop-types';
import classNames from 'classnames';
import styles from './index.less';
export default class FooterToolbar extends Component {
+ static contextTypes = {
+ isMobile: PropTypes.bool,
+ };
+
+ state = {
+ width: undefined,
+ };
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resizeFooterToolbar);
+ this.resizeFooterToolbar();
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.resizeFooterToolbar);
+ }
+
+ resizeFooterToolbar = () => {
+ const sider = document.querySelector('.ant-layout-sider');
+ if (sider == null) {
+ return;
+ }
+ const { isMobile } = this.context;
+ const width = isMobile ? null : `calc(100% - ${sider.style.width})`;
+ const { width: stateWidth } = this.state;
+ if (stateWidth !== width) {
+ this.setState({ width });
+ }
+ };
+
render() {
const { children, className, extra, ...restProps } = this.props;
+ const { width } = this.state;
return (
-
+
diff --git a/src/components/GlobalFooter/index.js b/src/components/GlobalFooter/index.js
index f5572131..1c2fb74e 100644
--- a/src/components/GlobalFooter/index.js
+++ b/src/components/GlobalFooter/index.js
@@ -5,18 +5,23 @@ import styles from './index.less';
const GlobalFooter = ({ className, links, copyright }) => {
const clsString = classNames(styles.globalFooter, className);
return (
-
+
{links && (
)}
{copyright && {copyright}
}
-
+
);
};
diff --git a/src/components/GlobalHeader/RightContent.js b/src/components/GlobalHeader/RightContent.js
new file mode 100644
index 00000000..e916fb83
--- /dev/null
+++ b/src/components/GlobalHeader/RightContent.js
@@ -0,0 +1,189 @@
+import React, { PureComponent } from 'react';
+import { FormattedMessage, formatMessage } from 'umi/locale';
+import { Spin, Tag, Menu, Icon, Dropdown, Avatar, Tooltip } from 'antd';
+import moment from 'moment';
+import groupBy from 'lodash/groupBy';
+import NoticeIcon from '../NoticeIcon';
+import HeaderSearch from '../HeaderSearch';
+import SelectLang from '../SelectLang';
+import styles from './index.less';
+
+export default class GlobalHeaderRight extends PureComponent {
+ getNoticeData() {
+ const { notices = [] } = this.props;
+ if (notices.length === 0) {
+ return {};
+ }
+ const newNotices = notices.map(notice => {
+ const newNotice = { ...notice };
+ if (newNotice.datetime) {
+ newNotice.datetime = moment(notice.datetime).fromNow();
+ }
+ if (newNotice.id) {
+ newNotice.key = newNotice.id;
+ }
+ if (newNotice.extra && newNotice.status) {
+ const color = {
+ todo: '',
+ processing: 'blue',
+ urgent: 'red',
+ doing: 'gold',
+ }[newNotice.status];
+ newNotice.extra = (
+
+ {newNotice.extra}
+
+ );
+ }
+ return newNotice;
+ });
+ return groupBy(newNotices, 'type');
+ }
+
+ getUnreadData = noticeData => {
+ const unreadMsg = {};
+ Object.entries(noticeData).forEach(([key, value]) => {
+ if (!unreadMsg[key]) {
+ unreadMsg[key] = 0;
+ }
+ if (Array.isArray(value)) {
+ unreadMsg[key] = value.filter(item => !item.read).length;
+ }
+ });
+ return unreadMsg;
+ };
+
+ changeReadState = clickedItem => {
+ const { id } = clickedItem;
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'global/changeNoticeReadState',
+ payload: id,
+ });
+ };
+
+ render() {
+ const {
+ currentUser,
+ fetchingNotices,
+ onNoticeVisibleChange,
+ onMenuClick,
+ onNoticeClear,
+ theme,
+ } = this.props;
+ const menu = (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ const noticeData = this.getNoticeData();
+ const unreadMsg = this.getUnreadData(noticeData);
+ let className = styles.right;
+ if (theme === 'dark') {
+ className = `${styles.right} ${styles.dark}`;
+ }
+ return (
+
+
{
+ console.log('input', value); // eslint-disable-line
+ }}
+ onPressEnter={value => {
+ console.log('enter', value); // eslint-disable-line
+ }}
+ />
+
+
+
+
+
+ {
+ console.log(item, tabProps); // eslint-disable-line
+ this.changeReadState(item, tabProps);
+ }}
+ locale={{
+ emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
+ clear: formatMessage({ id: 'component.noticeIcon.clear' }),
+ }}
+ onClear={onNoticeClear}
+ onPopupVisibleChange={onNoticeVisibleChange}
+ loading={fetchingNotices}
+ popupAlign={{ offset: [20, -16] }}
+ clearClose
+ >
+
+
+
+
+ {currentUser.name ? (
+
+
+
+ {currentUser.name}
+
+
+ ) : (
+
+ )}
+
+
+ );
+ }
+}
diff --git a/src/components/GlobalHeader/index.js b/src/components/GlobalHeader/index.js
index 10aea499..156f1b7d 100644
--- a/src/components/GlobalHeader/index.js
+++ b/src/components/GlobalHeader/index.js
@@ -1,165 +1,42 @@
import React, { PureComponent } from 'react';
-import { Menu, Icon, Spin, Tag, Dropdown, Avatar, Divider, Tooltip } from 'antd';
-import moment from 'moment';
-import groupBy from 'lodash/groupBy';
+import { Icon } from 'antd';
+import Link from 'umi/link';
import Debounce from 'lodash-decorators/debounce';
-import { Link } from 'dva/router';
-import NoticeIcon from '../NoticeIcon';
-import HeaderSearch from '../HeaderSearch';
import styles from './index.less';
+import RightContent from './RightContent';
export default class GlobalHeader extends PureComponent {
componentWillUnmount() {
this.triggerResizeEvent.cancel();
}
- getNoticeData() {
- const { notices = [] } = this.props;
- if (notices.length === 0) {
- return {};
- }
- const newNotices = notices.map(notice => {
- const newNotice = { ...notice };
- if (newNotice.datetime) {
- newNotice.datetime = moment(notice.datetime).fromNow();
- }
- // transform id to item key
- if (newNotice.id) {
- newNotice.key = newNotice.id;
- }
- if (newNotice.extra && newNotice.status) {
- const color = {
- todo: '',
- processing: 'blue',
- urgent: 'red',
- doing: 'gold',
- }[newNotice.status];
- newNotice.extra = (
-
- {newNotice.extra}
-
- );
- }
- return newNotice;
- });
- return groupBy(newNotices, 'type');
- }
- toggle = () => {
- const { collapsed, onCollapse } = this.props;
- onCollapse(!collapsed);
- this.triggerResizeEvent();
- };
/* eslint-disable*/
@Debounce(600)
triggerResizeEvent() {
+ // eslint-disable-line
const event = document.createEvent('HTMLEvents');
event.initEvent('resize', true, false);
window.dispatchEvent(event);
}
+ toggle = () => {
+ const { collapsed, onCollapse } = this.props;
+ onCollapse(!collapsed);
+ this.triggerResizeEvent();
+ };
render() {
- const {
- currentUser = {},
- collapsed,
- fetchingNotices,
- isMobile,
- logo,
- onNoticeVisibleChange,
- onMenuClick,
- onNoticeClear,
- } = this.props;
- const menu = (
-
-
- 个人中心
-
-
- 设置
-
-
- 触发报错
-
-
-
- 退出登录
-
-
- );
- const noticeData = this.getNoticeData();
+ const { collapsed, isMobile, logo } = this.props;
return (
- {isMobile && [
+ {isMobile && (
- ,
-
,
- ]}
+
+ )}
-
-
{
- console.log('input', value); // eslint-disable-line
- }}
- onPressEnter={value => {
- console.log('enter', value); // eslint-disable-line
- }}
- />
-
-
-
-
-
- {
- console.log(item, tabProps); // eslint-disable-line
- }}
- onClear={onNoticeClear}
- onPopupVisibleChange={onNoticeVisibleChange}
- loading={fetchingNotices}
- popupAlign={{ offset: [20, -16] }}
- >
-
-
-
-
- {currentUser.name ? (
-
-
-
- {currentUser.name}
-
-
- ) : (
-
- )}
-
+
);
}
diff --git a/src/components/GlobalHeader/index.less b/src/components/GlobalHeader/index.less
index 8508930a..5ed7fbaf 100644
--- a/src/components/GlobalHeader/index.less
+++ b/src/components/GlobalHeader/index.less
@@ -1,20 +1,15 @@
@import '~antd/lib/style/themes/default.less';
+@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
+
.header {
height: 64px;
- padding: 0 12px 0 0;
+ padding: 0;
background: #fff;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
position: relative;
}
-:global {
- .ant-layout {
- min-height: 100vh;
- overflow-x: hidden;
- }
-}
-
.logo {
height: 64px;
line-height: 58px;
@@ -40,18 +35,19 @@
i.trigger {
font-size: 20px;
- line-height: 64px;
+ height: 64px;
cursor: pointer;
transition: all 0.3s, padding 0s;
- padding: 0 24px;
+ padding: 22px 24px;
&:hover {
- background: @primary-1;
+ background: @pro-header-hover-bg;
}
}
.right {
float: right;
height: 100%;
+ overflow: hidden;
.action {
cursor: pointer;
padding: 0 12px;
@@ -59,18 +55,18 @@ i.trigger {
transition: all 0.3s;
height: 100%;
> i {
- font-size: 16px;
vertical-align: middle;
color: @text-color;
}
- &:hover,
- &:global(.ant-popover-open) {
- background: @primary-1;
+ &:hover {
+ background: @pro-header-hover-bg;
+ }
+ :global(&.ant-popover-open) {
+ background: @pro-header-hover-bg;
}
}
.search {
- padding: 0;
- margin: 0 12px;
+ padding: 0 12px;
&:hover {
background: transparent;
}
@@ -80,7 +76,24 @@ i.trigger {
margin: 20px 8px 20px 0;
color: @primary-color;
background: rgba(255, 255, 255, 0.85);
- vertical-align: middle;
+ vertical-align: top;
+ }
+ }
+}
+
+.dark {
+ height: 64px;
+ .action {
+ color: rgba(255, 255, 255, 0.85);
+ > i {
+ color: rgba(255, 255, 255, 0.85);
+ }
+ &:hover,
+ &:global(.ant-popover-open) {
+ background: @primary-color;
+ }
+ :global(.ant-badge) {
+ color: rgba(255, 255, 255, 0.85);
}
}
}
@@ -94,9 +107,10 @@ i.trigger {
display: none;
}
i.trigger {
- padding: 0 12px;
+ padding: 22px 12px;
}
.logo {
+ padding-left: 12px;
padding-right: 12px;
position: relative;
}
diff --git a/src/components/HeaderSearch/index.d.ts b/src/components/HeaderSearch/index.d.ts
index 3a06d758..d78fde47 100644
--- a/src/components/HeaderSearch/index.d.ts
+++ b/src/components/HeaderSearch/index.d.ts
@@ -2,10 +2,14 @@ import * as React from 'react';
export interface IHeaderSearchProps {
placeholder?: string;
dataSource?: string[];
+ defaultOpen?: boolean;
+ open?: boolean;
onSearch?: (value: string) => void;
onChange?: (value: string) => void;
+ onVisibleChange?: (visible: boolean) => void;
onPressEnter?: (value: string) => void;
style?: React.CSSProperties;
+ className?: string;
}
export default class HeaderSearch extends React.Component
{}
diff --git a/src/components/HeaderSearch/index.en-US.md b/src/components/HeaderSearch/index.en-US.md
new file mode 100644
index 00000000..a1cf841f
--- /dev/null
+++ b/src/components/HeaderSearch/index.en-US.md
@@ -0,0 +1,23 @@
+---
+title:
+ en-US: HeaderSearch
+ zh-CN: HeaderSearch
+subtitle: Top search box
+cols: 1
+order: 8
+---
+
+Usually placed as an entry to the global search, placed on the right side of the navigation toolbar.
+
+## API
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+placeholder | placeholder text | string | -
+dataSource | current list of prompts | string[] | -
+onSearch | Callback when selecting an item or pressing Enter | function(value) | -
+onChange | Enter a callback for the search text | function(value) | -
+onPressEnter | Callback when pressing Enter | function(value) | -
+onVisibleChange | Show or hide the callback of the text box | function(value) |-
+defaultOpen | The input box is displayed for the first time. | boolean | false
+open | The input box is displayed | booelan |false
\ No newline at end of file
diff --git a/src/components/HeaderSearch/index.js b/src/components/HeaderSearch/index.js
index 295a837a..95d946c0 100644
--- a/src/components/HeaderSearch/index.js
+++ b/src/components/HeaderSearch/index.js
@@ -2,18 +2,11 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { Input, Icon, AutoComplete } from 'antd';
import classNames from 'classnames';
+import Debounce from 'lodash-decorators/debounce';
+import Bind from 'lodash-decorators/bind';
import styles from './index.less';
export default class HeaderSearch extends PureComponent {
- static defaultProps = {
- defaultActiveFirstOption: false,
- onPressEnter: () => {},
- onSearch: () => {},
- className: '',
- placeholder: '',
- dataSource: [],
- defaultOpen: false,
- };
static propTypes = {
className: PropTypes.string,
placeholder: PropTypes.string,
@@ -22,61 +15,121 @@ export default class HeaderSearch extends PureComponent {
defaultActiveFirstOption: PropTypes.bool,
dataSource: PropTypes.array,
defaultOpen: PropTypes.bool,
+ onVisibleChange: PropTypes.func,
};
- state = {
- searchMode: this.props.defaultOpen,
- value: '',
+
+ static defaultProps = {
+ defaultActiveFirstOption: false,
+ onPressEnter: () => {},
+ onSearch: () => {},
+ className: '',
+ placeholder: '',
+ dataSource: [],
+ defaultOpen: false,
+ onVisibleChange: () => {},
};
+
+ static getDerivedStateFromProps(props) {
+ if ('open' in props) {
+ return {
+ searchMode: props.open,
+ };
+ }
+ return null;
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ searchMode: props.defaultOpen,
+ value: '',
+ };
+ }
+
componentWillUnmount() {
clearTimeout(this.timeout);
}
+
onKeyDown = e => {
if (e.key === 'Enter') {
+ const { onPressEnter } = this.props;
+ const { value } = this.state;
this.timeout = setTimeout(() => {
- this.props.onPressEnter(this.state.value); // Fix duplicate onPressEnter
+ onPressEnter(value); // Fix duplicate onPressEnter
}, 0);
}
};
+
onChange = value => {
+ const { onChange } = this.props;
this.setState({ value });
- if (this.props.onChange) {
- this.props.onChange();
+ if (onChange) {
+ onChange(value);
}
};
+
enterSearchMode = () => {
+ const { onVisibleChange } = this.props;
+ onVisibleChange(true);
this.setState({ searchMode: true }, () => {
- if (this.state.searchMode) {
+ const { searchMode } = this.state;
+ if (searchMode) {
this.input.focus();
}
});
};
+
leaveSearchMode = () => {
this.setState({
searchMode: false,
value: '',
});
};
+
+ // NOTE: 不能小于500,如果长按某键,第一次触发auto repeat的间隔是500ms,小于500会导致触发2次
+ @Bind()
+ @Debounce(500, {
+ leading: true,
+ trailing: false,
+ })
+ debouncePressEnter() {
+ const { onPressEnter } = this.props;
+ const { value } = this.state;
+ onPressEnter(value);
+ }
+
render() {
- const { className, placeholder, ...restProps } = this.props;
+ const { className, placeholder, open, ...restProps } = this.props;
+ const { searchMode, value } = this.state;
delete restProps.defaultOpen; // for rc-select not affected
const inputClass = classNames(styles.input, {
- [styles.show]: this.state.searchMode,
+ [styles.show]: searchMode,
});
return (
-
+ {
+ if (propertyName === 'width' && !searchMode) {
+ const { onVisibleChange } = this.props;
+ onVisibleChange(searchMode);
+ }
+ }}
+ >
{
this.input = node;
}}
+ aria-label={placeholder}
+ placeholder={placeholder}
onKeyDown={this.onKeyDown}
onBlur={this.leaveSearchMode}
/>
diff --git a/src/components/HeaderSearch/index.md b/src/components/HeaderSearch/index.zh-CN.md
similarity index 73%
rename from src/components/HeaderSearch/index.md
rename to src/components/HeaderSearch/index.zh-CN.md
index 3d4d3c22..a9a04754 100644
--- a/src/components/HeaderSearch/index.md
+++ b/src/components/HeaderSearch/index.zh-CN.md
@@ -18,4 +18,6 @@ dataSource | 当前提示内容列表 | string[] | -
onSearch | 选择某项或按下回车时的回调 | function(value) | -
onChange | 输入搜索字符的回调 | function(value) | -
onPressEnter | 按下回车时的回调 | function(value) | -
-defaultOpen | 输入框首次显示是否打开 | boolean | false
+onVisibleChange | 显示或隐藏文本框的回调 | function(value) |-
+defaultOpen | 输入框首次显示是否显示 | boolean | false
+open | 控制输入框是否显示 | booelan |false
\ No newline at end of file
diff --git a/src/components/Login/LoginItem.d.ts b/src/components/Login/LoginItem.d.ts
new file mode 100644
index 00000000..30a7a2d6
--- /dev/null
+++ b/src/components/Login/LoginItem.d.ts
@@ -0,0 +1,11 @@
+import * as React from 'react';
+export interface ILoginItemProps {
+ name?: string;
+ rules?: any[];
+ style?: React.CSSProperties;
+ onGetCaptcha?: () => void;
+ placeholder?: string;
+ buttonText?: React.ReactNode;
+}
+
+export class LoginItem extends React.Component {}
diff --git a/src/components/Login/LoginItem.js b/src/components/Login/LoginItem.js
index 98efb333..b3cc4d48 100644
--- a/src/components/Login/LoginItem.js
+++ b/src/components/Login/LoginItem.js
@@ -1,104 +1,147 @@
import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import { Form, Button, Row, Col } from 'antd';
+import { Form, Input, Button, Row, Col } from 'antd';
import omit from 'omit.js';
import styles from './index.less';
-import map from './map';
+import ItemMap from './map';
+import LoginContext from './loginContext';
const FormItem = Form.Item;
-function generator({ defaultProps, defaultRules, type }) {
- return WrappedComponent => {
- return class BasicComponent extends Component {
- static contextTypes = {
- form: PropTypes.object,
- updateActive: PropTypes.func,
- };
- constructor(props) {
- super(props);
- this.state = {
- count: 0,
- };
- }
- componentDidMount() {
- if (this.context.updateActive) {
- this.context.updateActive(this.props.name);
- }
- }
- componentWillUnmount() {
+class WrapFormItem extends Component {
+ static defaultProps = {
+ getCaptchaButtonText: 'captcha',
+ getCaptchaSecondText: 'second',
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ count: 0,
+ };
+ }
+
+ componentDidMount() {
+ const { updateActive, name } = this.props;
+ if (updateActive) {
+ updateActive(name);
+ }
+ }
+
+ componentWillUnmount() {
+ clearInterval(this.interval);
+ }
+
+ onGetCaptcha = () => {
+ const { onGetCaptcha } = this.props;
+ const result = onGetCaptcha ? onGetCaptcha() : null;
+ if (result === false) {
+ return;
+ }
+ if (result instanceof Promise) {
+ result.then(this.runGetCaptchaCountDown);
+ } else {
+ this.runGetCaptchaCountDown();
+ }
+ };
+
+ getFormItemOptions = ({ onChange, defaultValue, customprops, rules }) => {
+ const options = {
+ rules: rules || customprops.rules,
+ };
+ if (onChange) {
+ options.onChange = onChange;
+ }
+ if (defaultValue) {
+ options.initialValue = defaultValue;
+ }
+ return options;
+ };
+
+ runGetCaptchaCountDown = () => {
+ const { countDown } = this.props;
+ let count = countDown || 59;
+ this.setState({ count });
+ this.interval = setInterval(() => {
+ count -= 1;
+ this.setState({ count });
+ if (count === 0) {
clearInterval(this.interval);
}
- onGetCaptcha = () => {
- let count = 59;
- this.setState({ count });
- if (this.props.onGetCaptcha) {
- this.props.onGetCaptcha();
- }
- this.interval = setInterval(() => {
- count -= 1;
- this.setState({ count });
- if (count === 0) {
- clearInterval(this.interval);
- }
- }, 1000);
- };
- render() {
- const { getFieldDecorator } = this.context.form;
- const options = {};
- let otherProps = {};
- const { onChange, defaultValue, rules, name, ...restProps } = this.props;
- const { count } = this.state;
- options.rules = rules || defaultRules;
- if (onChange) {
- options.onChange = onChange;
- }
- if (defaultValue) {
- options.initialValue = defaultValue;
- }
- otherProps = restProps || otherProps;
- if (type === 'Captcha') {
- const inputProps = omit(otherProps, ['onGetCaptcha']);
- return (
-
-
-
- {getFieldDecorator(name, options)(
-
- )}
-
-
-
- {count ? `${count} s` : '获取验证码'}
-
-
-
-
- );
- }
- return (
-
- {getFieldDecorator(name, options)(
-
- )}
-
- );
- }
- };
+ }, 1000);
};
+
+ render() {
+ const { count } = this.state;
+
+ const {
+ form: { getFieldDecorator },
+ } = this.props;
+
+ // 这么写是为了防止restProps中 带入 onChange, defaultValue, rules props
+ const {
+ onChange,
+ customprops,
+ defaultValue,
+ rules,
+ name,
+ getCaptchaButtonText,
+ getCaptchaSecondText,
+ updateActive,
+ type,
+ ...restProps
+ } = this.props;
+
+ // get getFieldDecorator props
+ const options = this.getFormItemOptions(this.props);
+
+ const otherProps = restProps || {};
+ if (type === 'Captcha') {
+ const inputProps = omit(otherProps, ['onGetCaptcha', 'countDown']);
+ return (
+
+
+
+ {getFieldDecorator(name, options)( )}
+
+
+
+ {count ? `${count} ${getCaptchaSecondText}` : getCaptchaButtonText}
+
+
+
+
+ );
+ }
+ return (
+
+ {getFieldDecorator(name, options)( )}
+
+ );
+ }
}
const LoginItem = {};
-Object.keys(map).forEach(item => {
- LoginItem[item] = generator({
- defaultProps: map[item].props,
- defaultRules: map[item].rules,
- type: item,
- })(map[item].component);
+Object.keys(ItemMap).forEach(key => {
+ const item = ItemMap[key];
+ LoginItem[key] = props => (
+
+ {context => (
+
+ )}
+
+ );
});
export default LoginItem;
diff --git a/src/components/Login/LoginTab.js b/src/components/Login/LoginTab.js
index 750bfe7c..7c46db53 100644
--- a/src/components/Login/LoginTab.js
+++ b/src/components/Login/LoginTab.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
-import PropTypes from 'prop-types';
import { Tabs } from 'antd';
+import LoginContext from './loginContext';
const { TabPane } = Tabs;
@@ -12,21 +12,30 @@ const generateId = (() => {
};
})();
-export default class LoginTab extends Component {
- static __ANT_PRO_LOGIN_TAB = true;
- static contextTypes = {
- tabUtil: PropTypes.object,
- };
+class LoginTab extends Component {
constructor(props) {
super(props);
this.uniqueId = generateId('login-tab-');
}
- componentWillMount() {
- if (this.context.tabUtil) {
- this.context.tabUtil.addTab(this.uniqueId);
- }
+
+ componentDidMount() {
+ const { tabUtil } = this.props;
+ tabUtil.addTab(this.uniqueId);
}
+
render() {
- return ;
+ const { children } = this.props;
+ return {children} ;
}
}
+
+const wrapContext = props => (
+
+ {value => }
+
+);
+
+// 标志位 用来判断是不是自定义组件
+wrapContext.typeName = 'LoginTab';
+
+export default wrapContext;
diff --git a/src/components/Login/Tab.d.ts b/src/components/Login/Tab.d.ts
new file mode 100644
index 00000000..db651f7b
--- /dev/null
+++ b/src/components/Login/Tab.d.ts
@@ -0,0 +1,7 @@
+import * as React from 'react';
+
+export interface ILoginTabProps {
+ key?: string;
+ tab?: React.ReactNode;
+}
+export default class LoginTab extends React.Component {}
diff --git a/src/components/Login/index.d.ts b/src/components/Login/index.d.ts
index cd88a8b6..6a6f67fe 100644
--- a/src/components/Login/index.d.ts
+++ b/src/components/Login/index.d.ts
@@ -1,33 +1,20 @@
-import * as React from 'react';
import Button from 'antd/lib/button';
-export interface LoginProps {
+import * as React from 'react';
+import LoginItem from './LoginItem';
+import LoginTab from './LoginTab';
+
+export interface ILoginProps {
defaultActiveKey?: string;
onTabChange?: (key: string) => void;
style?: React.CSSProperties;
onSubmit?: (error: any, values: any) => void;
}
-export interface TabProps {
- key?: string;
- tab?: React.ReactNode;
-}
-export class Tab extends React.Component {}
-
-export interface LoginItemProps {
- name?: string;
- rules?: any[];
- style?: React.CSSProperties;
- onGetCaptcha?: () => void;
- placeholder?: string;
-}
-
-export class LoginItem extends React.Component {}
-
-export default class Login extends React.Component {
- static Tab: typeof Tab;
- static UserName: typeof LoginItem;
- static Password: typeof LoginItem;
- static Mobile: typeof LoginItem;
- static Captcha: typeof LoginItem;
- static Submit: typeof Button;
+export default class Login extends React.Component {
+ public static Tab: typeof LoginTab;
+ public static UserName: typeof LoginItem;
+ public static Password: typeof LoginItem;
+ public static Mobile: typeof LoginItem;
+ public static Captcha: typeof LoginItem;
+ public static Submit: typeof Button;
}
diff --git a/src/components/Login/index.en-US.md b/src/components/Login/index.en-US.md
index bc38579f..3b5992e2 100644
--- a/src/components/Login/index.en-US.md
+++ b/src/components/Login/index.en-US.md
@@ -38,7 +38,9 @@ Apart from the above properties, Login.Username also support all properties of a
Property | Description | Type | Default
----|------|-----|------
-onGetCaptcha | callback on getting a new Captcha | () => void | -
+onGetCaptcha | callback on getting a new Captcha | () => (void \| false \| Promise) | -
+countDown | count down | number |-
+buttonText | text on getting a new Captcha | ReactNode | '获取验证码'
Apart from the above properties, _Login.Captcha_ support the same properties with _Login.UserName_.
diff --git a/src/components/Login/index.js b/src/components/Login/index.js
index 02aa80c2..79cc56de 100644
--- a/src/components/Login/index.js
+++ b/src/components/Login/index.js
@@ -6,45 +6,57 @@ import LoginItem from './LoginItem';
import LoginTab from './LoginTab';
import LoginSubmit from './LoginSubmit';
import styles from './index.less';
+import LoginContext from './loginContext';
class Login extends Component {
- static defaultProps = {
- className: '',
- defaultActiveKey: '',
- onTabChange: () => {},
- onSubmit: () => {},
- };
static propTypes = {
className: PropTypes.string,
defaultActiveKey: PropTypes.string,
onTabChange: PropTypes.func,
onSubmit: PropTypes.func,
};
- static childContextTypes = {
- tabUtil: PropTypes.object,
- form: PropTypes.object,
- updateActive: PropTypes.func,
+
+ static defaultProps = {
+ className: '',
+ defaultActiveKey: '',
+ onTabChange: () => {},
+ onSubmit: () => {},
};
- state = {
- type: this.props.defaultActiveKey,
- tabs: [],
- active: {},
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ type: props.defaultActiveKey,
+ tabs: [],
+ active: {},
+ };
+ }
+
+ onSwitch = type => {
+ this.setState({
+ type,
+ });
+ const { onTabChange } = this.props;
+ onTabChange(type);
};
- getChildContext() {
+
+ getContext = () => {
+ const { tabs } = this.state;
+ const { form } = this.props;
return {
tabUtil: {
addTab: id => {
this.setState({
- tabs: [...this.state.tabs, id],
+ tabs: [...tabs, id],
});
},
removeTab: id => {
this.setState({
- tabs: this.state.tabs.filter(currentId => currentId !== id),
+ tabs: tabs.filter(currentId => currentId !== id),
});
},
},
- form: this.props.form,
+ form,
updateActive: activeItem => {
const { type, active } = this.state;
if (active[type]) {
@@ -57,21 +69,18 @@ class Login extends Component {
});
},
};
- }
- onSwitch = type => {
- this.setState({
- type,
- });
- this.props.onTabChange(type);
};
+
handleSubmit = e => {
e.preventDefault();
const { active, type } = this.state;
+ const { form, onSubmit } = this.props;
const activeFileds = active[type];
- this.props.form.validateFields(activeFileds, { force: true }, (err, values) => {
- this.props.onSubmit(err, values);
+ form.validateFields(activeFileds, { force: true }, (err, values) => {
+ onSubmit(err, values);
});
};
+
render() {
const { className, children } = this.props;
const { type, tabs } = this.state;
@@ -82,32 +91,34 @@ class Login extends Component {
return;
}
// eslint-disable-next-line
- if (item.type.__ANT_PRO_LOGIN_TAB) {
+ if (item.type.typeName === 'LoginTab') {
TabChildren.push(item);
} else {
otherChildren.push(item);
}
});
return (
-
+
+
+
+
+
);
}
}
diff --git a/src/components/Login/index.less b/src/components/Login/index.less
index 2894749b..646b6631 100644
--- a/src/components/Login/index.less
+++ b/src/components/Login/index.less
@@ -1,20 +1,6 @@
@import '~antd/lib/style/themes/default.less';
.login {
- .tabs {
- padding: 0 2px;
- margin: 0 -2px;
- :global {
- .ant-tabs-tab {
- font-size: 16px;
- line-height: 24px;
- }
- .ant-input-affix-wrapper .ant-input:not(:first-child) {
- padding-left: 34px;
- }
- }
- }
-
:global {
.ant-tabs .ant-tabs-bar {
border-bottom: 0;
@@ -23,19 +9,41 @@
}
.ant-form-item {
- margin-bottom: 24px;
+ margin: 0 2px 24px;
}
}
- .prefixIcon {
- font-size: @font-size-base;
- color: @disabled-color;
- }
-
.getCaptcha {
display: block;
width: 100%;
- height: 42px;
+ }
+
+ .icon {
+ font-size: 24px;
+ color: rgba(0, 0, 0, 0.2);
+ margin-left: 16px;
+ vertical-align: middle;
+ cursor: pointer;
+ transition: color 0.3s;
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+
+ .other {
+ text-align: left;
+ margin-top: 24px;
+ line-height: 22px;
+
+ .register {
+ float: right;
+ }
+ }
+
+ .prefixIcon {
+ font-size: @font-size-base;
+ color: @disabled-color;
}
.submit {
diff --git a/src/components/Login/index.zh-CN.md b/src/components/Login/index.zh-CN.md
index 98f0f624..a869e96c 100644
--- a/src/components/Login/index.zh-CN.md
+++ b/src/components/Login/index.zh-CN.md
@@ -32,14 +32,15 @@ name | 控件标记,提交数据中同样以此为 key | String | -
rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | -
除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。
-
-### Login.Password、Login.Mobile 同 Login.UserName
+## Login.Password、Login.Mobile 同 Login.UserName
### Login.Captcha
参数 | 说明 | 类型 | 默认值
----|------|-----|------
-onGetCaptcha | 点击获取校验码的回调 | () => void | -
+onGetCaptcha | 点击获取校验码的回调 | () => (void \| false \| Promise) | -
+countDown | 倒计时 | number |-
+buttonText | 点击获取校验码的说明文字 | ReactNode | '获取验证码'
除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。
diff --git a/src/components/Login/loginContext.js b/src/components/Login/loginContext.js
new file mode 100644
index 00000000..a13e6599
--- /dev/null
+++ b/src/components/Login/loginContext.js
@@ -0,0 +1,4 @@
+import { createContext } from 'react';
+
+const LoginContext = createContext();
+export default LoginContext;
diff --git a/src/components/Login/map.js b/src/components/Login/map.js
index c278ee08..dfa88199 100644
--- a/src/components/Login/map.js
+++ b/src/components/Login/map.js
@@ -1,12 +1,12 @@
import React from 'react';
-import { Input, Icon } from 'antd';
+import { Icon } from 'antd';
import styles from './index.less';
-const map = {
+export default {
UserName: {
- component: Input,
props: {
size: 'large',
+ id: 'userName',
prefix: ,
placeholder: 'admin',
},
@@ -18,11 +18,11 @@ const map = {
],
},
Password: {
- component: Input,
props: {
size: 'large',
prefix: ,
type: 'password',
+ id: 'password',
placeholder: '888888',
},
rules: [
@@ -33,7 +33,6 @@ const map = {
],
},
Mobile: {
- component: Input,
props: {
size: 'large',
prefix: ,
@@ -51,7 +50,6 @@ const map = {
],
},
Captcha: {
- component: Input,
props: {
size: 'large',
prefix: ,
@@ -65,5 +63,3 @@ const map = {
],
},
};
-
-export default map;
diff --git a/src/components/NoticeIcon/NoticeIconTab.d.ts b/src/components/NoticeIcon/NoticeIconTab.d.ts
index 5a577870..a44ecd9c 100644
--- a/src/components/NoticeIcon/NoticeIconTab.d.ts
+++ b/src/components/NoticeIcon/NoticeIconTab.d.ts
@@ -1,6 +1,6 @@
import * as React from 'react';
export interface INoticeIconData {
- avatar?: string;
+ avatar?: string | React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
datetime?: React.ReactNode;
@@ -10,10 +10,13 @@ export interface INoticeIconData {
export interface INoticeIconTabProps {
list?: INoticeIconData[];
+ count?: number;
title?: string;
+ name?: string;
emptyText?: React.ReactNode;
emptyImage?: string;
style?: React.CSSProperties;
+ showClear?: boolean;
}
export default class NoticeIconTab extends React.Component {}
diff --git a/src/components/NoticeIcon/NoticeList.js b/src/components/NoticeIcon/NoticeList.js
index c107e2d9..d0de9af5 100644
--- a/src/components/NoticeIcon/NoticeList.js
+++ b/src/components/NoticeIcon/NoticeList.js
@@ -11,6 +11,7 @@ export default function NoticeList({
locale,
emptyText,
emptyImage,
+ showClear = true,
}) {
if (data.length === 0) {
return (
@@ -27,11 +28,20 @@ export default function NoticeList({
const itemCls = classNames(styles.item, {
[styles.read]: item.read,
});
+ // eslint-disable-next-line no-nested-ternary
+ const leftIcon = item.avatar ? (
+ typeof item.avatar === 'string' ? (
+
+ ) : (
+ item.avatar
+ )
+ ) : null;
+
return (
onClick(item)}>
: null}
+ avatar={{leftIcon} }
title={
{item.title}
@@ -51,10 +61,11 @@ export default function NoticeList({
);
})}
-
- {locale.clear}
- {title}
-
+ {showClear ? (
+
+ {locale.clear} {title}
+
+ ) : null}
);
}
diff --git a/src/components/NoticeIcon/NoticeList.less b/src/components/NoticeIcon/NoticeList.less
index f99d59e8..e34efdc9 100644
--- a/src/components/NoticeIcon/NoticeList.less
+++ b/src/components/NoticeIcon/NoticeList.less
@@ -18,6 +18,9 @@
background: #fff;
margin-top: 4px;
}
+ .iconElement {
+ font-size: 32px;
+ }
&.read {
opacity: 0.4;
diff --git a/src/components/NoticeIcon/demo/popover.md b/src/components/NoticeIcon/demo/popover.md
index 6c047b7e..d128e4c4 100644
--- a/src/components/NoticeIcon/demo/popover.md
+++ b/src/components/NoticeIcon/demo/popover.md
@@ -49,6 +49,7 @@ const data = [{
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: '消息',
+ clickClose: true,
}, {
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
@@ -56,6 +57,7 @@ const data = [{
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: '消息',
+ clickClose: true,
}, {
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
@@ -63,6 +65,7 @@ const data = [{
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: '消息',
+ clickClose: true,
}, {
id: '000000009',
title: '任务名称',
diff --git a/src/components/NoticeIcon/index.d.ts b/src/components/NoticeIcon/index.d.ts
index 52c4dea3..6828634c 100644
--- a/src/components/NoticeIcon/index.d.ts
+++ b/src/components/NoticeIcon/index.d.ts
@@ -1,13 +1,14 @@
-import React from 'react';
+import * as React from 'react';
import NoticeIconTab, { INoticeIconData } from './NoticeIconTab';
export interface INoticeIconProps {
count?: number;
+ bell?: React.ReactNode;
className?: string;
loading?: boolean;
- onClear?: (tableTile: string) => void;
+ onClear?: (tabName: string) => void;
onItemClick?: (item: INoticeIconData, tabProps: INoticeIconProps) => void;
- onTabChange?: (tableTile: string) => void;
+ onTabChange?: (tabTile: string) => void;
popupAlign?: {
points?: [string, string];
offset?: [number, number];
@@ -21,6 +22,7 @@ export interface INoticeIconProps {
onPopupVisibleChange?: (visible: boolean) => void;
popupVisible?: boolean;
locale?: { emptyText: string; clear: string };
+ clearClose?: boolean;
}
export default class NoticeIcon extends React.Component {
diff --git a/src/components/NoticeIcon/index.en-US.md b/src/components/NoticeIcon/index.en-US.md
new file mode 100644
index 00000000..5352ce6c
--- /dev/null
+++ b/src/components/NoticeIcon/index.en-US.md
@@ -0,0 +1,45 @@
+---
+title: NoticeIcon
+subtitle: Notification Menu
+cols: 1
+order: 9
+---
+
+用在导航工具栏上,作为整个产品统一的通知中心。
+
+## API
+
+Property | Description | Type | Default
+----|------|-----|------
+count | Total number of messages | number | -
+bell | Change the bell Icon | ReactNode | ` `
+loading | Popup card loading status | boolean | false
+onClear | Click to clear button the callback | function(tabName) | -
+onItemClick | Click on the list item's callback | function(item, tabProps) | -
+onTabChange | Switching callbacks for tabs | function(tabTitle) | -
+popupAlign | Popup card location configuration | Object [alignConfig](https://github.com/yiminghe/dom-align#alignconfig-object-details) | -
+onPopupVisibleChange | Popup Card Showing or Hiding Callbacks | function(visible) | -
+popupVisible | Popup card display state | boolean | -
+locale | Default message text | Object | `{ emptyText: '暂无数据', clear: '清空' }`
+
+### NoticeIcon.Tab
+
+Property | Description | Type | Default
+----|------|-----|------
+title | header for message Tab | string | -
+name | identifier for message Tab | string | -
+list | List data, format refer to the following table | Array | `[]`
+showClear | Clear button display status | boolean | true
+emptyText | message text when list is empty | ReactNode | -
+emptyImage | image when list is empty | string | -
+
+
+### Tab data
+
+Property | Description | Type | Default
+----|------|-----|------
+avatar | avatar img url | string \| ReactNode | -
+title | title | ReactNode | -
+description | description info | ReactNode | -
+datetime | Timestamps | ReactNode | -
+extra |Additional information in the upper right corner of the list item | ReactNode | -
diff --git a/src/components/NoticeIcon/index.js b/src/components/NoticeIcon/index.js
index be2f44f9..21a5eb7c 100644
--- a/src/components/NoticeIcon/index.js
+++ b/src/components/NoticeIcon/index.js
@@ -1,4 +1,5 @@
import React, { PureComponent } from 'react';
+import ReactDOM from 'react-dom';
import { Popover, Icon, Tabs, Badge, Spin } from 'antd';
import classNames from 'classnames';
import List from './NoticeList';
@@ -7,52 +8,62 @@ import styles from './index.less';
const { TabPane } = Tabs;
export default class NoticeIcon extends PureComponent {
+ static Tab = TabPane;
+
static defaultProps = {
onItemClick: () => {},
onPopupVisibleChange: () => {},
onTabChange: () => {},
onClear: () => {},
loading: false,
+ clearClose: false,
locale: {
- emptyText: '暂无数据',
- clear: '清空',
+ emptyText: 'No notifications',
+ clear: 'Clear',
},
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
- static Tab = TabPane;
- constructor(props) {
- super(props);
- this.state = {};
- if (props.children && props.children[0]) {
- this.state.tabType = props.children[0].props.title;
- }
- }
+
onItemClick = (item, tabProps) => {
const { onItemClick } = this.props;
+ const { clickClose } = item;
onItemClick(item, tabProps);
+ if (clickClose) {
+ this.popover.click();
+ }
};
+
+ onClear = name => {
+ const { onClear, clearClose } = this.props;
+ onClear(name);
+ if (clearClose) {
+ this.popover.click();
+ }
+ };
+
onTabChange = tabType => {
- this.setState({ tabType });
- this.props.onTabChange(tabType);
+ const { onTabChange } = this.props;
+ onTabChange(tabType);
};
+
getNotificationBox() {
const { children, loading, locale } = this.props;
if (!children) {
return null;
}
const panes = React.Children.map(children, child => {
- const title =
- child.props.list && child.props.list.length > 0
- ? `${child.props.title} (${child.props.list.length})`
- : child.props.title;
+ const { list, title, name, count } = child.props;
+ const len = list && list.length ? list.length : 0;
+ const msgCount = count || count === 0 ? count : len;
+ const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title;
return (
-
+
this.onItemClick(item, child.props)}
- onClear={() => this.props.onClear(child.props.title)}
- title={child.props.title}
+ onClear={() => this.onClear(name)}
+ title={title}
locale={locale}
/>
@@ -66,14 +77,16 @@ export default class NoticeIcon extends PureComponent {
);
}
+
render() {
- const { className, count, popupAlign, onPopupVisibleChange } = this.props;
+ const { className, count, popupAlign, popupVisible, onPopupVisibleChange, bell } = this.props;
const noticeButtonClass = classNames(className, styles.noticeButton);
const notificationBox = this.getNotificationBox();
+ const NoticeBellIcon = bell || ;
const trigger = (
-
-
+
+ {NoticeBellIcon}
);
@@ -82,7 +95,7 @@ export default class NoticeIcon extends PureComponent {
}
const popoverProps = {};
if ('popupVisible' in this.props) {
- popoverProps.visible = this.props.popupVisible;
+ popoverProps.visible = popupVisible;
}
return (
(this.popover = ReactDOM.findDOMNode(node))} // eslint-disable-line
>
{trigger}
diff --git a/src/components/NoticeIcon/index.less b/src/components/NoticeIcon/index.less
index 6858f0c5..794376b5 100644
--- a/src/components/NoticeIcon/index.less
+++ b/src/components/NoticeIcon/index.less
@@ -14,7 +14,6 @@
}
.icon {
- font-size: 16px;
padding: 4px;
}
diff --git a/src/components/NoticeIcon/index.md b/src/components/NoticeIcon/index.zh-CN.md
similarity index 74%
rename from src/components/NoticeIcon/index.md
rename to src/components/NoticeIcon/index.zh-CN.md
index fefdbdcf..3c78dba1 100644
--- a/src/components/NoticeIcon/index.md
+++ b/src/components/NoticeIcon/index.zh-CN.md
@@ -1,7 +1,5 @@
---
-title:
- en-US: NoticeIcon
- zh-CN: NoticeIcon
+title: NoticeIcon
subtitle: 通知菜单
cols: 1
order: 9
@@ -14,30 +12,36 @@ order: 9
参数 | 说明 | 类型 | 默认值
----|------|-----|------
count | 图标上的消息总数 | number | -
+bell | translate this please -> Change the bell Icon | ReactNode | ` `
loading | 弹出卡片加载状态 | boolean | false
-onClear | 点击清空按钮的回调 | function(tabTitle) | -
+onClear | 点击清空按钮的回调 | function(tabName) | -
onItemClick | 点击列表项的回调 | function(item, tabProps) | -
onTabChange | 切换页签的回调 | function(tabTitle) | -
popupAlign | 弹出卡片的位置配置 | Object [alignConfig](https://github.com/yiminghe/dom-align#alignconfig-object-details) | -
onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | -
popupVisible | 控制弹层显隐 | boolean | -
locale | 默认文案 | Object | `{ emptyText: '暂无数据', clear: '清空' }`
+clearClose | 点击清空按钮后关闭通知菜单 | boolean | false
### NoticeIcon.Tab
参数 | 说明 | 类型 | 默认值
----|------|-----|------
title | 消息分类的页签标题 | string | -
+name | 消息分类的标识符 | string | -
list | 列表数据,格式参照下表 | Array | `[]`
+showClear | 是否显示清空按钮 | boolean | true
emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | -
emptyImage | 针对每个 Tab 定制空数据图片 | string | -
+
### Tab data
参数 | 说明 | 类型 | 默认值
----|------|-----|------
-avatar | 头像图片链接 | string | -
+avatar | 头像图片链接 | string \| ReactNode | -
title | 标题 | ReactNode | -
description | 描述信息 | ReactNode | -
datetime | 时间戳 | ReactNode | -
extra | 额外信息,在列表项右上角 | ReactNode | -
+clickClose | 点击列表项关闭通知菜单 | boolean | false
diff --git a/src/components/NumberInfo/index.js b/src/components/NumberInfo/index.js
index cf25d6a0..717aee9d 100644
--- a/src/components/NumberInfo/index.js
+++ b/src/components/NumberInfo/index.js
@@ -10,8 +10,19 @@ const NumberInfo = ({ theme, title, subTitle, total, subTotal, status, suffix, g
})}
{...rest}
>
- {title && {title}
}
- {subTitle && {subTitle}
}
+ {title && (
+
+ {title}
+
+ )}
+ {subTitle && (
+
+ {subTitle}
+
+ )}
{total}
diff --git a/src/components/NumberInfo/index.md b/src/components/NumberInfo/index.md
deleted file mode 100644
index 1116f990..00000000
--- a/src/components/NumberInfo/index.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title:
- en-US: NumberInfo
- zh-CN: NumberInfo
-subtitle: 数据文本
-cols: 1
-order: 10
----
-
-常用在数据卡片中,用于突出展示某个业务数据。
-
-## API
-
-参数 | 说明 | 类型 | 默认值
-----|------|-----|------
-title | 标题 | ReactNode\|string | -
-subTitle | 子标题 | ReactNode\|string | -
-total | 总量 | ReactNode\|string | -
-subTotal | 子总量 | ReactNode\|string | -
-status | 增加状态 | 'up \| down' | -
-theme | 状态样式 | string | 'light'
-gap | 设置数字和描述直接的间距(像素) | number | 8
diff --git a/src/components/PageHeader/breadcrumb.d.ts b/src/components/PageHeader/breadcrumb.d.ts
new file mode 100644
index 00000000..cfed4021
--- /dev/null
+++ b/src/components/PageHeader/breadcrumb.d.ts
@@ -0,0 +1,6 @@
+import * as React from 'react';
+import { IPageHeaderProps } from './index';
+
+export default class BreadcrumbView extends React.Component {}
+
+export function getBreadcrumb(breadcrumbNameMap: object, url: string): object;
diff --git a/src/components/PageHeader/breadcrumb.js b/src/components/PageHeader/breadcrumb.js
new file mode 100644
index 00000000..28c71b06
--- /dev/null
+++ b/src/components/PageHeader/breadcrumb.js
@@ -0,0 +1,176 @@
+import React, { PureComponent, createElement } from 'react';
+import pathToRegexp from 'path-to-regexp';
+import { Breadcrumb } from 'antd';
+import styles from './index.less';
+import { urlToList } from '../_utils/pathTools';
+
+export const getBreadcrumb = (breadcrumbNameMap, url) => {
+ let breadcrumb = breadcrumbNameMap[url];
+ if (!breadcrumb) {
+ Object.keys(breadcrumbNameMap).forEach(item => {
+ if (pathToRegexp(item).test(url)) {
+ breadcrumb = breadcrumbNameMap[item];
+ }
+ });
+ }
+ return breadcrumb || {};
+};
+
+export default class BreadcrumbView extends PureComponent {
+ state = {
+ breadcrumb: null,
+ };
+
+ componentDidMount() {
+ this.getBreadcrumbDom();
+ }
+
+ componentDidUpdate(preProps) {
+ const { location } = this.props;
+ if (!location || !preProps.location) {
+ return;
+ }
+ const prePathname = preProps.location.pathname;
+ if (prePathname !== location.pathname) {
+ this.getBreadcrumbDom();
+ }
+ }
+
+ getBreadcrumbDom = () => {
+ const breadcrumb = this.conversionBreadcrumbList();
+ this.setState({
+ breadcrumb,
+ });
+ };
+
+ getBreadcrumbProps = () => {
+ const { routes, params, location, breadcrumbNameMap } = this.props;
+ return {
+ routes,
+ params,
+ routerLocation: location,
+ breadcrumbNameMap,
+ };
+ };
+
+ // Generated according to props
+ conversionFromProps = () => {
+ const { breadcrumbList, breadcrumbSeparator, itemRender, linkElement = 'a' } = this.props;
+ return (
+
+ {breadcrumbList.map(item => {
+ const title = itemRender ? itemRender(item) : item.title;
+ return (
+
+ {item.href
+ ? createElement(
+ linkElement,
+ {
+ [linkElement === 'a' ? 'href' : 'to']: item.href,
+ },
+ title
+ )
+ : title}
+
+ );
+ })}
+
+ );
+ };
+
+ conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
+ const { breadcrumbSeparator, home, itemRender, linkElement = 'a' } = this.props;
+ // Convert the url to an array
+ const pathSnippets = urlToList(routerLocation.pathname);
+ // Loop data mosaic routing
+ const extraBreadcrumbItems = pathSnippets.map((url, index) => {
+ const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
+ if (currentBreadcrumb.inherited) {
+ return null;
+ }
+ const isLinkable = index !== pathSnippets.length - 1 && currentBreadcrumb.component;
+ const name = itemRender ? itemRender(currentBreadcrumb) : currentBreadcrumb.name;
+ return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
+
+ {createElement(
+ isLinkable ? linkElement : 'span',
+ { [linkElement === 'a' ? 'href' : 'to']: url },
+ name
+ )}
+
+ ) : null;
+ });
+ // Add home breadcrumbs to your head
+ extraBreadcrumbItems.unshift(
+
+ {createElement(
+ linkElement,
+ {
+ [linkElement === 'a' ? 'href' : 'to']: '/',
+ },
+ home || 'Home'
+ )}
+
+ );
+ return (
+
+ {extraBreadcrumbItems}
+
+ );
+ };
+
+ /**
+ * 将参数转化为面包屑
+ * Convert parameters into breadcrumbs
+ */
+ conversionBreadcrumbList = () => {
+ const { breadcrumbList, breadcrumbSeparator } = this.props;
+ const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps();
+ if (breadcrumbList && breadcrumbList.length) {
+ return this.conversionFromProps();
+ }
+ // 如果传入 routes 和 params 属性
+ // If pass routes and params attributes
+ if (routes && params) {
+ return (
+ route.breadcrumbName)}
+ params={params}
+ itemRender={this.itemRender}
+ separator={breadcrumbSeparator}
+ />
+ );
+ }
+ // 根据 location 生成 面包屑
+ // Generate breadcrumbs based on location
+ if (routerLocation && routerLocation.pathname) {
+ return this.conversionFromLocation(routerLocation, breadcrumbNameMap);
+ }
+ return null;
+ };
+
+ // 渲染Breadcrumb 子节点
+ // Render the Breadcrumb child node
+ itemRender = (route, params, routes, paths) => {
+ const { linkElement = 'a' } = this.props;
+ const last = routes.indexOf(route) === routes.length - 1;
+ return last || !route.component ? (
+ {route.breadcrumbName}
+ ) : (
+ createElement(
+ linkElement,
+ {
+ href: paths.join('/') || '/',
+ to: paths.join('/') || '/',
+ },
+ route.breadcrumbName
+ )
+ );
+ };
+
+ render() {
+ const { breadcrumb } = this.state;
+ return breadcrumb;
+ }
+}
diff --git a/src/components/PageHeader/index.d.ts b/src/components/PageHeader/index.d.ts
index a1c356e7..eacbb2de 100644
--- a/src/components/PageHeader/index.d.ts
+++ b/src/components/PageHeader/index.d.ts
@@ -15,6 +15,9 @@ export interface IPageHeaderProps {
tabBarExtraContent?: React.ReactNode;
linkElement?: React.ReactNode;
style?: React.CSSProperties;
+ home?: React.ReactNode;
+ wide?: boolean;
+ hiddenBreadcrumb?: boolean;
}
export default class PageHeader extends React.Component {}
diff --git a/src/components/PageHeader/index.js b/src/components/PageHeader/index.js
index 3080ed04..5d47b345 100644
--- a/src/components/PageHeader/index.js
+++ b/src/components/PageHeader/index.js
@@ -1,168 +1,16 @@
-import React, { PureComponent, createElement } from 'react';
-import PropTypes from 'prop-types';
-import pathToRegexp from 'path-to-regexp';
-import { Breadcrumb, Tabs } from 'antd';
+import React, { PureComponent } from 'react';
+import { Tabs, Skeleton } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
-import { urlToList } from '../_utils/pathTools';
+import BreadcrumbView from './breadcrumb';
const { TabPane } = Tabs;
-export function getBreadcrumb(breadcrumbNameMap, url) {
- let breadcrumb = breadcrumbNameMap[url];
- if (!breadcrumb) {
- Object.keys(breadcrumbNameMap).forEach(item => {
- if (pathToRegexp(item).test(url)) {
- breadcrumb = breadcrumbNameMap[item];
- }
- });
- }
- return breadcrumb || {};
-}
-
export default class PageHeader extends PureComponent {
- static contextTypes = {
- routes: PropTypes.array,
- params: PropTypes.object,
- location: PropTypes.object,
- breadcrumbNameMap: PropTypes.object,
- };
-
- state = {
- breadcrumb: null,
- };
-
- componentDidMount() {
- this.getBreadcrumbDom();
- }
-
- componentDidUpdate(preProps) {
- if (preProps.tabActiveKey !== this.props.tabActiveKey) {
- this.getBreadcrumbDom();
- }
- }
onChange = key => {
- if (this.props.onTabChange) {
- this.props.onTabChange(key);
- }
- };
- getBreadcrumbProps = () => {
- return {
- routes: this.props.routes || this.context.routes,
- params: this.props.params || this.context.params,
- routerLocation: this.props.location || this.context.location,
- breadcrumbNameMap: this.props.breadcrumbNameMap || this.context.breadcrumbNameMap,
- };
- };
- getBreadcrumbDom = () => {
- const breadcrumb = this.conversionBreadcrumbList();
- this.setState({
- breadcrumb,
- });
- };
- // Generated according to props
- conversionFromProps = () => {
- const { breadcrumbList, breadcrumbSeparator, linkElement = 'a' } = this.props;
- return (
-
- {breadcrumbList.map(item => (
-
- {item.href
- ? createElement(
- linkElement,
- {
- [linkElement === 'a' ? 'href' : 'to']: item.href,
- },
- item.title
- )
- : item.title}
-
- ))}
-
- );
- };
- conversionFromLocation = (routerLocation, breadcrumbNameMap) => {
- const { breadcrumbSeparator, linkElement = 'a' } = this.props;
- // Convert the url to an array
- const pathSnippets = urlToList(routerLocation.pathname);
- // Loop data mosaic routing
- const extraBreadcrumbItems = pathSnippets.map((url, index) => {
- const currentBreadcrumb = getBreadcrumb(breadcrumbNameMap, url);
- const isLinkable = index !== pathSnippets.length - 1 && currentBreadcrumb.component;
- return currentBreadcrumb.name && !currentBreadcrumb.hideInBreadcrumb ? (
-
- {createElement(
- isLinkable ? linkElement : 'span',
- { [linkElement === 'a' ? 'href' : 'to']: url },
- currentBreadcrumb.name
- )}
-
- ) : null;
- });
- // Add home breadcrumbs to your head
- extraBreadcrumbItems.unshift(
-
- {createElement(
- linkElement,
- {
- [linkElement === 'a' ? 'href' : 'to']: '/',
- },
- '首页'
- )}
-
- );
- return (
-
- {extraBreadcrumbItems}
-
- );
- };
- /**
- * 将参数转化为面包屑
- * Convert parameters into breadcrumbs
- */
- conversionBreadcrumbList = () => {
- const { breadcrumbList, breadcrumbSeparator } = this.props;
- const { routes, params, routerLocation, breadcrumbNameMap } = this.getBreadcrumbProps();
- if (breadcrumbList && breadcrumbList.length) {
- return this.conversionFromProps();
- }
- // 如果传入 routes 和 params 属性
- // If pass routes and params attributes
- if (routes && params) {
- return (
- route.breadcrumbName)}
- params={params}
- itemRender={this.itemRender}
- separator={breadcrumbSeparator}
- />
- );
+ const { onTabChange } = this.props;
+ if (onTabChange) {
+ onTabChange(key);
}
- // 根据 location 生成 面包屑
- // Generate breadcrumbs based on location
- if (routerLocation && routerLocation.pathname) {
- return this.conversionFromLocation(routerLocation, breadcrumbNameMap);
- }
- return null;
- };
- // 渲染Breadcrumb 子节点
- // Render the Breadcrumb child node
- itemRender = (route, params, routes, paths) => {
- const { linkElement = 'a' } = this.props;
- const last = routes.indexOf(route) === routes.length - 1;
- return last || !route.component ? (
- {route.breadcrumbName}
- ) : (
- createElement(
- linkElement,
- {
- href: paths.join('/') || '/',
- to: paths.join('/') || '/',
- },
- route.breadcrumbName
- )
- );
};
render() {
@@ -177,6 +25,9 @@ export default class PageHeader extends PureComponent {
tabActiveKey,
tabDefaultActiveKey,
tabBarExtraContent,
+ loading = false,
+ wide = false,
+ hiddenBreadcrumb = false,
} = this.props;
const clsString = classNames(styles.pageHeader, className);
@@ -187,34 +38,44 @@ export default class PageHeader extends PureComponent {
if (tabActiveKey !== undefined) {
activeKeyProps.activeKey = tabActiveKey;
}
-
return (
- {this.state.breadcrumb}
-
- {logo &&
{logo}
}
-
-
- {title &&
{title} }
- {action &&
{action}
}
-
-
- {content &&
{content}
}
- {extraContent &&
{extraContent}
}
+
+
+ {hiddenBreadcrumb ? null : }
+
+ {logo &&
{logo}
}
+
+
+ {title &&
{title} }
+ {action &&
{action}
}
+
+
+ {content &&
{content}
}
+ {extraContent &&
{extraContent}
}
+
+
-
+ {tabList && tabList.length ? (
+
+ {tabList.map(item => (
+
+ ))}
+
+ ) : null}
+
- {tabList &&
- tabList.length && (
-
- {tabList.map(item => )}
-
- )}
);
}
diff --git a/src/components/PageHeader/index.less b/src/components/PageHeader/index.less
index 2a8c9a6a..da947d09 100644
--- a/src/components/PageHeader/index.less
+++ b/src/components/PageHeader/index.less
@@ -4,13 +4,17 @@
background: @component-background;
padding: 16px 32px 0 32px;
border-bottom: @border-width-base @border-style-base @border-color-split;
-
+ .wide {
+ max-width: 1200px;
+ margin: auto;
+ }
.detail {
display: flex;
}
.row {
display: flex;
+ width: 100%;
}
.breadcrumb {
@@ -18,11 +22,13 @@
}
.tabs {
- margin: 0 0 -17px -8px;
+ margin: 0 0 0 -8px;
:global {
+ // 1px 可以让选中效果显示完成
.ant-tabs-bar {
- border-bottom: @border-width-base @border-style-base @border-color-split;
+ border-bottom: none;
+ margin-bottom: 1px;
}
}
}
@@ -62,12 +68,18 @@
}
.title,
+ .content {
+ flex: auto;
+ }
+
.action,
- .content,
.extraContent,
.main {
- // IE auto is no have height
- flex: 1;
+ flex: 0 1 auto;
+ }
+
+ .main {
+ width: 100%;
}
.title,
diff --git a/src/components/PageHeader/index.md b/src/components/PageHeader/index.md
index 288f8b6c..e82c8b89 100644
--- a/src/components/PageHeader/index.md
+++ b/src/components/PageHeader/index.md
@@ -16,9 +16,11 @@ order: 11
| title | title 区域 | ReactNode | - |
| logo | logo区域 | ReactNode | - |
| action | 操作区,位于 title 行的行尾 | ReactNode | - |
+| home | 默认的主页说明文字 | ReactNode | - |
| content | 内容区 | ReactNode | - |
| extraContent | 额外内容区,位于content的右侧 | ReactNode | - |
| breadcrumbList | 面包屑数据,配置了此属性时 `routes` `params` `location` `breadcrumbNameMap` 无效 | array<{title: ReactNode, href?: string}> | - |
+| hiddenBreadcrumb |隐藏面包屑 | boolean | false |
| routes | 面包屑相关属性,router 的路由栈信息 | object[] | - |
| params | 面包屑相关属性,路由的参数 | object | - |
| location | 面包屑相关属性,当前的路由信息 | object | - |
@@ -26,7 +28,9 @@ order: 11
| tabList | tab 标题列表 | array<{key: string, tab: ReactNode}> | - |
| tabActiveKey | 当前高亮的 tab 项 | string | - |
| tabDefaultActiveKey | 默认高亮的 tab 项 | string | 第一项 |
+| wide | 是否定宽 | boolean | false |
| onTabChange | 切换面板的回调 | (key) => void | - |
+| itemRender | 自定义节点方法 | (menuItem) => ReactNode | - |
| linkElement | 定义链接的元素,默认为 `a`,可传入 react-router 的 Link | string\|ReactElement | - |
> 面包屑的配置方式有三种,一是直接配置 `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 中,组件会自动获取。
diff --git a/src/components/PageHeader/index.test.js b/src/components/PageHeader/index.test.js
index 5238ac55..d22706e9 100644
--- a/src/components/PageHeader/index.test.js
+++ b/src/components/PageHeader/index.test.js
@@ -1,4 +1,4 @@
-import { getBreadcrumb } from './index';
+import { getBreadcrumb } from './breadcrumb';
import { urlToList } from '../_utils/pathTools';
const routerData = {
@@ -26,22 +26,18 @@ describe('test getBreadcrumb', () => {
expect(getBreadcrumb(routerData, '/userinfo/2144/addr').name).toEqual('收货订单');
});
it('Loop through the parameters', () => {
- const urlNameList = urlToList('/userinfo/2144/addr').map(url => {
- return getBreadcrumb(routerData, url).name;
- });
+ const urlNameList = urlToList('/userinfo/2144/addr').map(
+ url => getBreadcrumb(routerData, url).name
+ );
expect(urlNameList).toEqual(['用户列表', '用户信息', '收货订单']);
});
it('a path', () => {
- const urlNameList = urlToList('/userinfo').map(url => {
- return getBreadcrumb(routerData, url).name;
- });
+ const urlNameList = urlToList('/userinfo').map(url => getBreadcrumb(routerData, url).name);
expect(urlNameList).toEqual(['用户列表']);
});
it('Secondary path', () => {
- const urlNameList = urlToList('/userinfo/2144').map(url => {
- return getBreadcrumb(routerData, url).name;
- });
+ const urlNameList = urlToList('/userinfo/2144').map(url => getBreadcrumb(routerData, url).name);
expect(urlNameList).toEqual(['用户列表', '用户信息']);
});
});
diff --git a/src/components/PageHeaderWrapper/GridContent.js b/src/components/PageHeaderWrapper/GridContent.js
new file mode 100644
index 00000000..931ea20c
--- /dev/null
+++ b/src/components/PageHeaderWrapper/GridContent.js
@@ -0,0 +1,18 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import styles from './GridContent.less';
+
+class GridContent extends PureComponent {
+ render() {
+ const { contentWidth, children } = this.props;
+ let className = `${styles.main}`;
+ if (contentWidth === 'Fixed') {
+ className = `${styles.main} ${styles.wide}`;
+ }
+ return
{children}
;
+ }
+}
+
+export default connect(({ setting }) => ({
+ contentWidth: setting.contentWidth,
+}))(GridContent);
diff --git a/src/components/PageHeaderWrapper/GridContent.less b/src/components/PageHeaderWrapper/GridContent.less
new file mode 100644
index 00000000..d5496e9e
--- /dev/null
+++ b/src/components/PageHeaderWrapper/GridContent.less
@@ -0,0 +1,10 @@
+.main {
+ width: 100%;
+ height: 100%;
+ min-height: 100%;
+ transition: 0.3s;
+ &.wide {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+}
diff --git a/src/components/PageHeaderWrapper/index.js b/src/components/PageHeaderWrapper/index.js
new file mode 100644
index 00000000..cd745f66
--- /dev/null
+++ b/src/components/PageHeaderWrapper/index.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { FormattedMessage } from 'umi/locale';
+import Link from 'umi/link';
+import PageHeader from '@/components/PageHeader';
+import { connect } from 'dva';
+import GridContent from './GridContent';
+import styles from './index.less';
+import MenuContext from '@/layouts/MenuContext';
+
+const PageHeaderWrapper = ({ children, contentWidth, wrapperClassName, top, ...restProps }) => (
+
+ {top}
+
+ {value => (
+ }
+ {...value}
+ key="pageheader"
+ {...restProps}
+ linkElement={Link}
+ itemRender={item => {
+ if (item.locale) {
+ return ;
+ }
+ return item.title;
+ }}
+ />
+ )}
+
+ {children ? (
+
+ {children}
+
+ ) : null}
+
+);
+
+export default connect(({ setting }) => ({
+ contentWidth: setting.contentWidth,
+}))(PageHeaderWrapper);
diff --git a/src/layouts/PageHeaderLayout.less b/src/components/PageHeaderWrapper/index.less
similarity index 100%
rename from src/layouts/PageHeaderLayout.less
rename to src/components/PageHeaderWrapper/index.less
diff --git a/src/components/PageLoading/index.js b/src/components/PageLoading/index.js
new file mode 100644
index 00000000..77c0f165
--- /dev/null
+++ b/src/components/PageLoading/index.js
@@ -0,0 +1,10 @@
+import React from 'react';
+import { Spin } from 'antd';
+
+// loading components from code split
+// https://umijs.org/plugin/umi-plugin-react.html#dynamicimport
+export default () => (
+
+
+
+);
diff --git a/src/components/Result/demo/classic.md b/src/components/Result/demo/classic.md
index b4ccdcf1..0cd9d14b 100644
--- a/src/components/Result/demo/classic.md
+++ b/src/components/Result/demo/classic.md
@@ -15,7 +15,7 @@ const desc1 = (
@@ -25,7 +25,7 @@ const desc2 = (
diff --git a/src/components/Result/demo/error.md b/src/components/Result/demo/error.md
index 836bd8cb..5fd25cfd 100644
--- a/src/components/Result/demo/error.md
+++ b/src/components/Result/demo/error.md
@@ -15,11 +15,11 @@ const extra = (
您提交的内容有如下错误:
-
您的账户已被冻结
+
您的账户已被冻结
立即解冻
-
您的账户还不具备申请资格
+
您的账户还不具备申请资格
立即升级
diff --git a/src/components/Result/index.js b/src/components/Result/index.js
index ada2f5f4..89f9f31a 100644
--- a/src/components/Result/index.js
+++ b/src/components/Result/index.js
@@ -13,8 +13,8 @@ export default function Result({
...restProps
}) {
const iconMap = {
- error: ,
- success: ,
+ error: ,
+ success: ,
};
const clsString = classNames(styles.result, className);
return (
diff --git a/src/components/SelectLang/index.js b/src/components/SelectLang/index.js
new file mode 100644
index 00000000..ee91cd64
--- /dev/null
+++ b/src/components/SelectLang/index.js
@@ -0,0 +1,53 @@
+import React, { PureComponent } from 'react';
+import { formatMessage, setLocale, getLocale } from 'umi/locale';
+import { Menu, Icon, Dropdown } from 'antd';
+import classNames from 'classnames';
+import styles from './index.less';
+
+export default class SelectLang extends PureComponent {
+ changeLang = ({ key }) => {
+ setLocale(key);
+ };
+
+ render() {
+ const { className } = this.props;
+ const selectedLang = getLocale();
+ const langMenu = (
+
+
+
+ 🇨🇳
+ {' '}
+ 简体中文
+
+
+
+ 🇭🇰
+ {' '}
+ 繁体中文
+
+
+
+ 🇬🇧
+ {' '}
+ English
+
+
+
+ 🇵🇹
+ {' '}
+ Português
+
+
+ );
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/components/SelectLang/index.less b/src/components/SelectLang/index.less
new file mode 100644
index 00000000..819c0f8c
--- /dev/null
+++ b/src/components/SelectLang/index.less
@@ -0,0 +1,21 @@
+@import '~antd/lib/style/themes/default.less';
+
+.menu {
+ :global(.anticon) {
+ margin-right: 8px;
+ }
+ :global(.ant-dropdown-menu-item) {
+ width: 160px;
+ }
+}
+
+.dropDown {
+ cursor: pointer;
+ font-size: 14px;
+ vertical-align: top;
+ line-height: 64px;
+ > svg {
+ position: relative;
+ top: 2px;
+ }
+}
diff --git a/src/components/SettingDrawer/BlockChecbox.js b/src/components/SettingDrawer/BlockChecbox.js
new file mode 100644
index 00000000..49af42c7
--- /dev/null
+++ b/src/components/SettingDrawer/BlockChecbox.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import { Tooltip, Icon } from 'antd';
+import style from './index.less';
+
+const BlockChecbox = ({ value, onChange, list }) => (
+
+ {list.map(item => (
+
+ onChange(item.key)}>
+
+
+
+
+
+
+ ))}
+
+);
+
+export default BlockChecbox;
diff --git a/src/components/SettingDrawer/ThemeColor.js b/src/components/SettingDrawer/ThemeColor.js
new file mode 100644
index 00000000..e5d66d4b
--- /dev/null
+++ b/src/components/SettingDrawer/ThemeColor.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import { Tooltip, Icon } from 'antd';
+import { formatMessage } from 'umi/locale';
+import styles from './ThemeColor.less';
+
+const Tag = ({ color, check, ...rest }) => (
+
+ {check ? : ''}
+
+);
+
+const ThemeColor = ({ colors, title, value, onChange }) => {
+ let colorList = colors;
+ if (!colors) {
+ colorList = [
+ {
+ key: 'dust',
+ color: '#F5222D',
+ },
+ {
+ key: 'volcano',
+ color: '#FA541C',
+ },
+ {
+ key: 'sunset',
+ color: '#FAAD14',
+ },
+ {
+ key: 'cyan',
+ color: '#13C2C2',
+ },
+ {
+ key: 'green',
+ color: '#52C41A',
+ },
+ {
+ key: 'daybreak',
+ color: '#1890FF',
+ },
+ {
+ key: 'geekblue',
+ color: '#2F54EB',
+ },
+ {
+ key: 'purple',
+ color: '#722ED1',
+ },
+ ];
+ }
+ return (
+
+
{title}
+
+ {colorList.map(({ key, color }) => (
+
+ onChange && onChange(color)}
+ />
+
+ ))}
+
+
+ );
+};
+
+export default ThemeColor;
diff --git a/src/components/SettingDrawer/ThemeColor.less b/src/components/SettingDrawer/ThemeColor.less
new file mode 100644
index 00000000..4983eb9c
--- /dev/null
+++ b/src/components/SettingDrawer/ThemeColor.less
@@ -0,0 +1,21 @@
+.themeColor {
+ overflow: hidden;
+ margin-top: 24px;
+ .title {
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.65);
+ line-height: 22px;
+ margin-bottom: 12px;
+ }
+ .colorBlock {
+ width: 20px;
+ height: 20px;
+ border-radius: 2px;
+ float: left;
+ cursor: pointer;
+ margin-right: 8px;
+ text-align: center;
+ color: #fff;
+ font-weight: bold;
+ }
+}
diff --git a/src/components/SettingDrawer/index.js b/src/components/SettingDrawer/index.js
new file mode 100644
index 00000000..4996e996
--- /dev/null
+++ b/src/components/SettingDrawer/index.js
@@ -0,0 +1,254 @@
+import React, { PureComponent } from 'react';
+import { Select, message, Drawer, List, Switch, Divider, Icon, Button, Alert, Tooltip } from 'antd';
+import { formatMessage } from 'umi/locale';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { connect } from 'dva';
+import omit from 'omit.js';
+import styles from './index.less';
+import ThemeColor from './ThemeColor';
+import BlockChecbox from './BlockChecbox';
+
+const { Option } = Select;
+
+const Body = ({ children, title, style }) => (
+
+
{title}
+ {children}
+
+);
+
+@connect(({ setting }) => ({ setting }))
+class SettingDrawer extends PureComponent {
+ state = {
+ collapse: false,
+ };
+
+ getLayoutSetting = () => {
+ const {
+ setting: { contentWidth, fixedHeader, layout, autoHideHeader, fixSiderbar },
+ } = this.props;
+ return [
+ {
+ title: formatMessage({ id: 'app.setting.content-width' }),
+ action: (
+ this.changeSetting('contentWidth', value)}
+ style={{ width: 80 }}
+ >
+ {layout === 'sidemenu' ? null : (
+
+ {formatMessage({ id: 'app.setting.content-width.fixed' })}
+
+ )}
+
+ {formatMessage({ id: 'app.setting.content-width.fluid' })}
+
+
+ ),
+ },
+ {
+ title: formatMessage({ id: 'app.setting.fixedheader' }),
+ action: (
+ this.changeSetting('fixedHeader', checked)}
+ />
+ ),
+ },
+ {
+ title: formatMessage({ id: 'app.setting.hideheader' }),
+ disabled: !fixedHeader,
+ disabledReason: formatMessage({ id: 'app.setting.hideheader.hint' }),
+ action: (
+ this.changeSetting('autoHideHeader', checked)}
+ />
+ ),
+ },
+ {
+ title: formatMessage({ id: 'app.setting.fixedsidebar' }),
+ disabled: layout === 'topmenu',
+ disabledReason: formatMessage({ id: 'app.setting.fixedsidebar.hint' }),
+ action: (
+ this.changeSetting('fixSiderbar', checked)}
+ />
+ ),
+ },
+ ];
+ };
+
+ changeSetting = (key, value) => {
+ const { setting } = this.props;
+ const nextState = { ...setting };
+ nextState[key] = value;
+ if (key === 'layout') {
+ nextState.contentWidth = value === 'topmenu' ? 'Fixed' : 'Fluid';
+ } else if (key === 'fixedHeader' && !value) {
+ nextState.autoHideHeader = false;
+ }
+ this.setState(nextState, () => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'setting/changeSetting',
+ payload: this.state,
+ });
+ });
+ };
+
+ togglerContent = () => {
+ const { collapse } = this.state;
+ this.setState({ collapse: !collapse });
+ };
+
+ renderLayoutSettingItem = item => {
+ const action = React.cloneElement(item.action, {
+ disabled: item.disabled,
+ });
+ return (
+
+
+ {item.title}
+
+
+ );
+ };
+
+ render() {
+ const { setting } = this.props;
+ const { navTheme, primaryColor, layout, colorWeak } = setting;
+ const { collapse } = this.state;
+ return (
+
+
+
+ }
+ onHandleClick={this.togglerContent}
+ style={{
+ zIndex: 999,
+ }}
+ >
+
+
+
this.changeSetting('navTheme', value)}
+ />
+
+
+ this.changeSetting('primaryColor', color)}
+ />
+
+
+
+
+ this.changeSetting('layout', value)}
+ />
+
+
+
+
+
+
+
+ this.changeSetting('colorWeak', checked)}
+ />,
+ ]}
+ >
+ {formatMessage({ id: 'app.setting.weakmode' })}
+
+
+
+ message.success(formatMessage({ id: 'app.setting.copyinfo' }))}
+ >
+
+ {formatMessage({ id: 'app.setting.copy' })}
+
+
+
+ {formatMessage({ id: 'app.setting.production.hint' })}{' '}
+
+ src/defaultSettings.js
+
+
+ }
+ />
+
+
+ );
+ }
+}
+
+export default SettingDrawer;
diff --git a/src/components/SettingDrawer/index.less b/src/components/SettingDrawer/index.less
new file mode 100644
index 00000000..af4109be
--- /dev/null
+++ b/src/components/SettingDrawer/index.less
@@ -0,0 +1,74 @@
+@import '~antd/lib/style/themes/default.less';
+
+.content {
+ min-height: 100%;
+ background: #fff;
+ position: relative;
+}
+
+.blockChecbox {
+ display: flex;
+ .item {
+ margin-right: 16px;
+ position: relative;
+ // box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.1);
+ border-radius: @border-radius-base;
+ cursor: pointer;
+ img {
+ width: 48px;
+ }
+ }
+ .selectIcon {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ padding-top: 15px;
+ padding-left: 24px;
+ height: 100%;
+ color: @primary-color;
+ font-size: 14px;
+ font-weight: bold;
+ }
+}
+
+.color_block {
+ width: 38px;
+ height: 22px;
+ margin: 4px;
+ border-radius: 4px;
+ cursor: pointer;
+ margin-right: 12px;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.title {
+ font-size: 14px;
+ color: @heading-color;
+ line-height: 22px;
+ margin-bottom: 12px;
+}
+
+.handle {
+ position: absolute;
+ top: 240px;
+ background: @primary-color;
+ width: 48px;
+ height: 48px;
+ right: 300px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ pointer-events: auto;
+ z-index: 0;
+ text-align: center;
+ font-size: 16px;
+ border-radius: 4px 0 0 4px;
+}
+
+.productionHint {
+ font-size: 12px;
+ margin-top: 16px;
+}
diff --git a/src/components/SiderMenu/BaseMenu.js b/src/components/SiderMenu/BaseMenu.js
new file mode 100644
index 00000000..e819cda3
--- /dev/null
+++ b/src/components/SiderMenu/BaseMenu.js
@@ -0,0 +1,161 @@
+import React, { PureComponent } from 'react';
+import { Menu, Icon } from 'antd';
+import Link from 'umi/link';
+import isEqual from 'lodash/isEqual';
+import memoizeOne from 'memoize-one';
+import { urlToList } from '../_utils/pathTools';
+import { getMenuMatches } from './SiderMenuUtils';
+import { isUrl } from '@/utils/utils';
+import styles from './index.less';
+
+const { SubMenu } = Menu;
+
+// Allow menu.js config icon as string or ReactNode
+// icon: 'setting',
+// icon: 'http://demo.com/icon.png',
+// icon:
,
+const getIcon = icon => {
+ if (typeof icon === 'string' && isUrl(icon)) {
+ return
;
+ }
+ if (typeof icon === 'string') {
+ return
;
+ }
+ return icon;
+};
+
+export default class BaseMenu extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.getSelectedMenuKeys = memoizeOne(this.getSelectedMenuKeys, isEqual);
+ }
+
+ /**
+ * 获得菜单子节点
+ * @memberof SiderMenu
+ */
+ getNavMenuItems = (menusData, parent) => {
+ if (!menusData) {
+ return [];
+ }
+ return menusData
+ .filter(item => item.name && !item.hideInMenu)
+ .map(item => this.getSubMenuOrItem(item, parent))
+ .filter(item => item);
+ };
+
+ // Get the currently selected menu
+ getSelectedMenuKeys = pathname => {
+ const { flatMenuKeys } = this.props;
+ return urlToList(pathname).map(itemPath => getMenuMatches(flatMenuKeys, itemPath).pop());
+ };
+
+ /**
+ * get SubMenu or Item
+ */
+ getSubMenuOrItem = item => {
+ // doc: add hideChildrenInMenu
+ if (item.children && !item.hideChildrenInMenu && item.children.some(child => child.name)) {
+ const { name } = item;
+ return (
+
+ {getIcon(item.icon)}
+ {name}
+
+ ) : (
+ name
+ )
+ }
+ key={item.path}
+ >
+ {this.getNavMenuItems(item.children)}
+
+ );
+ }
+ return
{this.getMenuItemPath(item)} ;
+ };
+
+ /**
+ * 判断是否是http链接.返回 Link 或 a
+ * Judge whether it is http link.return a or Link
+ * @memberof SiderMenu
+ */
+ getMenuItemPath = item => {
+ const { name } = item;
+ const itemPath = this.conversionPath(item.path);
+ const icon = getIcon(item.icon);
+ const { target } = item;
+ // Is it a http link
+ if (/^https?:\/\//.test(itemPath)) {
+ return (
+
+ {icon}
+ {name}
+
+ );
+ }
+ const { location, isMobile, onCollapse } = this.props;
+ return (
+
{
+ onCollapse(true);
+ }
+ : undefined
+ }
+ >
+ {icon}
+
{name}
+
+ );
+ };
+
+ conversionPath = path => {
+ if (path && path.indexOf('http') === 0) {
+ return path;
+ }
+ return `/${path || ''}`.replace(/\/+/g, '/');
+ };
+
+ render() {
+ const {
+ openKeys,
+ theme,
+ mode,
+ location: { pathname },
+ } = this.props;
+ // if pathname can't match, use the nearest parent's key
+ let selectedKeys = this.getSelectedMenuKeys(pathname);
+ if (!selectedKeys.length && openKeys) {
+ selectedKeys = [openKeys[openKeys.length - 1]];
+ }
+ let props = {};
+ if (openKeys) {
+ props = {
+ openKeys,
+ };
+ }
+ const { handleOpenChange, style, menuData } = this.props;
+ return (
+
+ {this.getNavMenuItems(menuData)}
+
+ );
+ }
+}
diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js
index 7b5ed49a..e1b49d00 100644
--- a/src/components/SiderMenu/SiderMenu.js
+++ b/src/components/SiderMenu/SiderMenu.js
@@ -1,202 +1,59 @@
-import React, { PureComponent } from 'react';
-import { Layout, Menu, Icon } from 'antd';
-import pathToRegexp from 'path-to-regexp';
-import { Link } from 'dva/router';
+import React, { PureComponent, Suspense } from 'react';
+import { Layout } from 'antd';
+import classNames from 'classnames';
+import Link from 'umi/link';
import styles from './index.less';
-import { urlToList } from '../_utils/pathTools';
+import PageLoading from '../PageLoading';
+import { getDefaultCollapsedSubMenus } from './SiderMenuUtils';
+const BaseMenu = React.lazy(() => import('./BaseMenu'));
const { Sider } = Layout;
-const { SubMenu } = Menu;
-
-// Allow menu.js config icon as string or ReactNode
-// icon: 'setting',
-// icon: 'http://demo.com/icon.png',
-// icon:
,
-const getIcon = icon => {
- if (typeof icon === 'string' && icon.indexOf('http') === 0) {
- return
;
- }
- if (typeof icon === 'string') {
- return
;
- }
- return icon;
-};
-
-/**
- * Recursively flatten the data
- * [{path:string},{path:string}] => {path,path2}
- * @param menu
- */
-export const getFlatMenuKeys = menu =>
- menu
- .reduce((keys, item) => {
- keys.push(item.path);
- if (item.children) {
- return keys.concat(getFlatMenuKeys(item.children));
- }
- return keys;
- }, []);
-
-/**
- * Find all matched menu keys based on paths
- * @param flatMenuKeys: [/abc, /abc/:id, /abc/:id/info]
- * @param paths: [/abc, /abc/11, /abc/11/info]
- */
-export const getMenuMatchKeys = (flatMenuKeys, paths) =>
- paths
- .reduce((matchKeys, path) => (
- matchKeys.concat(
- flatMenuKeys.filter(item => pathToRegexp(item).test(path))
- )), []);
export default class SiderMenu extends PureComponent {
constructor(props) {
super(props);
- this.menus = props.menuData;
- this.flatMenuKeys = getFlatMenuKeys(props.menuData);
this.state = {
- openKeys: this.getDefaultCollapsedSubMenus(props),
+ openKeys: getDefaultCollapsedSubMenus(props),
};
}
- componentWillReceiveProps(nextProps) {
- if (nextProps.location.pathname !== this.props.location.pathname) {
- this.setState({
- openKeys: this.getDefaultCollapsedSubMenus(nextProps),
- });
+
+ static getDerivedStateFromProps(props, state) {
+ const { pathname } = state;
+ if (props.location.pathname !== pathname) {
+ return {
+ pathname: props.location.pathname,
+ openKeys: getDefaultCollapsedSubMenus(props),
+ };
}
+ return null;
}
- /**
- * Convert pathname to openKeys
- * /list/search/articles = > ['list','/list/search']
- * @param props
- */
- getDefaultCollapsedSubMenus(props) {
- const { location: { pathname } } = props || this.props;
- return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname));
- }
- /**
- * 判断是否是http链接.返回 Link 或 a
- * Judge whether it is http link.return a or Link
- * @memberof SiderMenu
- */
- getMenuItemPath = item => {
- const itemPath = this.conversionPath(item.path);
- const icon = getIcon(item.icon);
- const { target, name } = item;
- // Is it a http link
- if (/^https?:\/\//.test(itemPath)) {
- return (
-
- {icon}
- {name}
-
- );
- }
- return (
-
{
- this.props.onCollapse(true);
- }
- : undefined
- }
- >
- {icon}
-
{name}
-
- );
- };
- /**
- * get SubMenu or Item
- */
- getSubMenuOrItem = item => {
- if (item.children && item.children.some(child => child.name)) {
- const childrenItems = this.getNavMenuItems(item.children);
- // 当无子菜单时就不展示菜单
- if (childrenItems && childrenItems.length > 0) {
- return (
-
- {getIcon(item.icon)}
- {item.name}
-
- ) : (
- item.name
- )
- }
- key={item.path}
- >
- {childrenItems}
-
- );
- }
- return null;
- } else {
- return
{this.getMenuItemPath(item)} ;
- }
- };
- /**
- * 获得菜单子节点
- * @memberof SiderMenu
- */
- getNavMenuItems = menusData => {
- if (!menusData) {
- return [];
- }
- return menusData
- .filter(item => item.name && !item.hideInMenu)
- .map(item => {
- // make dom
- const ItemDom = this.getSubMenuOrItem(item);
- return ItemDom;
- })
- .filter(item => item);
- };
- // Get the currently selected menu
- getSelectedMenuKeys = () => {
- const { location: { pathname } } = this.props;
- return getMenuMatchKeys(this.flatMenuKeys, urlToList(pathname));
- };
- // conversion Path
- // 转化路径
- conversionPath = path => {
- if (path && path.indexOf('http') === 0) {
- return path;
- } else {
- return `/${path || ''}`.replace(/\/+/g, '/');
- }
- };
isMainMenu = key => {
- return this.menus.some(item => key && (item.key === key || item.path === key));
+ const { menuData } = this.props;
+ return menuData.some(item => {
+ if (key) {
+ return item.key === key || item.path === key;
+ }
+ return false;
+ });
};
+
handleOpenChange = openKeys => {
- const lastOpenKey = openKeys[openKeys.length - 1];
const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1;
this.setState({
- openKeys: moreThanOne ? [lastOpenKey] : [...openKeys],
+ openKeys: moreThanOne ? [openKeys.pop()] : [...openKeys],
});
};
+
render() {
- const { logo, collapsed, onCollapse } = this.props;
+ const { logo, collapsed, onCollapse, fixSiderbar, theme } = this.props;
const { openKeys } = this.state;
- // Don't show popup menu when it is been collapsed
- const menuProps = collapsed
- ? {}
- : {
- openKeys,
- };
- // if pathname can't match, use the nearest parent's key
- let selectedKeys = this.getSelectedMenuKeys();
- if (!selectedKeys.length) {
- selectedKeys = [openKeys[openKeys.length - 1]];
- }
+ const defaultProps = collapsed ? {} : { openKeys };
+
+ const siderClassName = classNames(styles.sider, {
+ [styles.fixSiderbar]: fixSiderbar,
+ [styles.light]: theme === 'light',
+ });
return (
-
+
Ant Design Pro
-
- {this.getNavMenuItems(this.menus)}
-
+
}>
+
+
);
}
diff --git a/src/components/SiderMenu/SiderMenu.test.js b/src/components/SiderMenu/SiderMenu.test.js
new file mode 100644
index 00000000..3d280da0
--- /dev/null
+++ b/src/components/SiderMenu/SiderMenu.test.js
@@ -0,0 +1,39 @@
+import { getFlatMenuKeys } from './SiderMenuUtils';
+
+const menu = [
+ {
+ path: '/dashboard',
+ children: [
+ {
+ path: '/dashboard/name',
+ },
+ ],
+ },
+ {
+ path: '/userinfo',
+ children: [
+ {
+ path: '/userinfo/:id',
+ children: [
+ {
+ path: '/userinfo/:id/info',
+ },
+ ],
+ },
+ ],
+ },
+];
+
+const flatMenuKeys = getFlatMenuKeys(menu);
+
+describe('test convert nested menu to flat menu', () => {
+ it('simple menu', () => {
+ expect(flatMenuKeys).toEqual([
+ '/dashboard',
+ '/dashboard/name',
+ '/userinfo',
+ '/userinfo/:id',
+ '/userinfo/:id/info',
+ ]);
+ });
+});
diff --git a/src/components/SiderMenu/SiderMenuUtils.js b/src/components/SiderMenu/SiderMenuUtils.js
new file mode 100644
index 00000000..6722ed7a
--- /dev/null
+++ b/src/components/SiderMenu/SiderMenuUtils.js
@@ -0,0 +1,39 @@
+import pathToRegexp from 'path-to-regexp';
+import { urlToList } from '../_utils/pathTools';
+
+/**
+ * Recursively flatten the data
+ * [{path:string},{path:string}] => {path,path2}
+ * @param menus
+ */
+export const getFlatMenuKeys = menuData => {
+ let keys = [];
+ menuData.forEach(item => {
+ keys.push(item.path);
+ if (item.children) {
+ keys = keys.concat(getFlatMenuKeys(item.children));
+ }
+ });
+ return keys;
+};
+
+export const getMenuMatches = (flatMenuKeys, path) =>
+ flatMenuKeys.filter(item => {
+ if (item) {
+ return pathToRegexp(item).test(path);
+ }
+ return false;
+ });
+/**
+ * 获得菜单子节点
+ * @memberof SiderMenu
+ */
+export const getDefaultCollapsedSubMenus = props => {
+ const {
+ location: { pathname },
+ flatMenuKeys,
+ } = props;
+ return urlToList(pathname)
+ .map(item => getMenuMatches(flatMenuKeys, item)[0])
+ .filter(item => item);
+};
diff --git a/src/components/SiderMenu/SilderMenu.test.js b/src/components/SiderMenu/SilderMenu.test.js
deleted file mode 100644
index 8f01f669..00000000
--- a/src/components/SiderMenu/SilderMenu.test.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { urlToList } from '../_utils/pathTools';
-import { getFlatMenuKeys, getMenuMatchKeys } from './SiderMenu';
-
-const menu = [
- {
- path: '/dashboard',
- children: [
- {
- path: '/dashboard/name',
- },
- ],
- },
- {
- path: '/userinfo',
- children: [
- {
- path: '/userinfo/:id',
- children: [
- {
- path: '/userinfo/:id/info',
- },
- ],
- },
- ],
- },
-];
-
-const flatMenuKeys = getFlatMenuKeys(menu);
-
-describe('test convert nested menu to flat menu', () => {
- it('simple menu', () => {
- expect(flatMenuKeys).toEqual([
- '/dashboard',
- '/dashboard/name',
- '/userinfo',
- '/userinfo/:id',
- '/userinfo/:id/info',
- ]);
- });
-});
-
-describe('test menu match', () => {
- it('simple path', () => {
- expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard'))).toEqual(['/dashboard']);
- });
-
- it('error path', () => {
- expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboardname'))).toEqual([]);
- });
-
- it('Secondary path', () => {
- expect(getMenuMatchKeys(flatMenuKeys, urlToList('/dashboard/name'))).toEqual([
- '/dashboard',
- '/dashboard/name',
- ]);
- });
-
- it('Parameter path', () => {
- expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144'))).toEqual([
- '/userinfo',
- '/userinfo/:id',
- ]);
- });
-
- it('three parameter path', () => {
- expect(getMenuMatchKeys(flatMenuKeys, urlToList('/userinfo/2144/info'))).toEqual([
- '/userinfo',
- '/userinfo/:id',
- '/userinfo/:id/info',
- ]);
- });
-});
diff --git a/src/components/SiderMenu/index.js b/src/components/SiderMenu/index.js
index a0dca978..0be27331 100644
--- a/src/components/SiderMenu/index.js
+++ b/src/components/SiderMenu/index.js
@@ -1,24 +1,26 @@
-import 'rc-drawer-menu/assets/index.css';
import React from 'react';
-import DrawerMenu from 'rc-drawer-menu';
+import { Drawer } from 'antd';
import SiderMenu from './SiderMenu';
+import { getFlatMenuKeys } from './SiderMenuUtils';
-const SiderMenuWrapper = props =>
- props.isMobile ? (
-
{
- props.onCollapse(true);
+const SiderMenuWrapper = React.memo(props => {
+ const { isMobile, menuData, collapsed, onCollapse } = props;
+ const flatMenuKeys = getFlatMenuKeys(menuData);
+ return isMobile ? (
+ onCollapse(true)}
+ style={{
+ padding: 0,
+ height: '100vh',
}}
- width="256px"
>
-
-
+
+
) : (
-
+
);
+});
export default SiderMenuWrapper;
diff --git a/src/components/SiderMenu/index.less b/src/components/SiderMenu/index.less
index 9fa1a91a..7ffba1b1 100644
--- a/src/components/SiderMenu/index.less
+++ b/src/components/SiderMenu/index.less
@@ -1,9 +1,11 @@
@import '~antd/lib/style/themes/default.less';
-@ease-in-out-circ: cubic-bezier(0.78, 0.14, 0.15, 0.86);
+
+@nav-header-height: 64px;
+
.logo {
- height: 64px;
+ height: @nav-header-height;
position: relative;
- line-height: 64px;
+ line-height: @nav-header-height;
padding-left: (@menu-collapsed-width - 32px) / 2;
transition: all 0.3s;
background: #002140;
@@ -19,7 +21,7 @@
vertical-align: middle;
font-size: 20px;
margin: 0 0 0 12px;
- font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-weight: 600;
}
}
@@ -29,14 +31,28 @@
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
position: relative;
z-index: 10;
- &.ligth {
+ &.fixSiderbar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ :global(.ant-menu-root) {
+ overflow-y: auto;
+ height: ~'calc(100vh - @{nav-header-height})';
+ }
+ }
+ &.light {
+ box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.05);
background-color: white;
.logo {
background: white;
+ box-shadow: 1px 1px 0 0 @border-color-split;
h1 {
- color: #002140;
+ color: @primary-color;
}
}
+ :global(.ant-menu-light) {
+ border-right-color: transparent;
+ }
}
}
@@ -46,6 +62,10 @@
}
:global {
+ .top-nav-menu li.ant-menu-item {
+ height: @nav-header-height;
+ line-height: @nav-header-height;
+ }
.drawer .drawer-content {
background: #001529;
}
diff --git a/src/components/StandardTable/index.js b/src/components/StandardTable/index.js
index 281d60e6..04233543 100644
--- a/src/components/StandardTable/index.js
+++ b/src/components/StandardTable/index.js
@@ -24,37 +24,37 @@ class StandardTable extends PureComponent {
};
}
- componentWillReceiveProps(nextProps) {
+ static getDerivedStateFromProps(nextProps) {
// clean state
if (nextProps.selectedRows.length === 0) {
const needTotalList = initTotalList(nextProps.columns);
- this.setState({
+ return {
selectedRowKeys: [],
needTotalList,
- });
+ };
}
+ return null;
}
handleRowSelectChange = (selectedRowKeys, selectedRows) => {
- let needTotalList = [...this.state.needTotalList];
- needTotalList = needTotalList.map(item => {
- return {
- ...item,
- total: selectedRows.reduce((sum, val) => {
- return sum + parseFloat(val[item.dataIndex], 10);
- }, 0),
- };
- });
-
- if (this.props.onSelectRow) {
- this.props.onSelectRow(selectedRows);
+ let { needTotalList } = this.state;
+ needTotalList = needTotalList.map(item => ({
+ ...item,
+ total: selectedRows.reduce((sum, val) => sum + parseFloat(val[item.dataIndex], 10), 0),
+ }));
+ const { onSelectRow } = this.props;
+ if (onSelectRow) {
+ onSelectRow(selectedRows);
}
this.setState({ selectedRowKeys, needTotalList });
};
handleTableChange = (pagination, filters, sorter) => {
- this.props.onChange(pagination, filters, sorter);
+ const { onChange } = this.props;
+ if (onChange) {
+ onChange(pagination, filters, sorter);
+ }
};
cleanSelectedKeys = () => {
@@ -63,7 +63,11 @@ class StandardTable extends PureComponent {
render() {
const { selectedRowKeys, needTotalList } = this.state;
- const { data: { list, pagination }, loading, columns, rowKey } = this.props;
+ const {
+ data: { list, pagination },
+ rowKey,
+ ...rest
+ } = this.props;
const paginationProps = {
showSizeChanger: true,
@@ -88,7 +92,8 @@ class StandardTable extends PureComponent {
已选择
{selectedRowKeys.length} 项
{needTotalList.map(item => (
- {item.title}总计
+ {item.title}
+ 总计
{item.render ? item.render(item.total) : item.total}
@@ -104,13 +109,12 @@ class StandardTable extends PureComponent {
/>
);
diff --git a/src/components/TagSelect/demo/controlled.md b/src/components/TagSelect/demo/controlled.md
new file mode 100644
index 00000000..4e9defa7
--- /dev/null
+++ b/src/components/TagSelect/demo/controlled.md
@@ -0,0 +1,50 @@
+---
+order: 3
+title: 受控模式
+---
+
+结合 `Tag` 的 `TagSelect` 组件,方便的应用于筛选类目的业务场景中。
+
+```jsx
+import { Button } from 'antd';
+import TagSelect from 'ant-design-pro/lib/TagSelect';
+
+class Demo extends React.Component {
+ state = {
+ value: ['cat1'],
+ };
+ handleFormSubmit = value => {
+ this.setState({
+ value,
+ });
+ };
+ checkAll = () => {
+ this.setState({
+ value: ['cat1', 'cat2', 'cat3', 'cat4', 'cat5', 'cat6'],
+ });
+ };
+ render() {
+ return (
+
+
全部
+
+
+ 类目一
+ 类目二
+ 类目三
+ 类目四
+ 类目五
+ 类目六
+
+
+
+ );
+ }
+}
+
+ReactDOM.render(
, mountNode);
+```
diff --git a/src/components/TagSelect/index.d.ts b/src/components/TagSelect/index.d.ts
index b17d34d8..736ca52f 100644
--- a/src/components/TagSelect/index.d.ts
+++ b/src/components/TagSelect/index.d.ts
@@ -6,6 +6,7 @@ export interface ITagSelectProps {
expandable?: boolean;
value?: string[] | number[];
style?: React.CSSProperties;
+ hideCheckAll?: boolean;
}
export default class TagSelect extends React.Component
{
diff --git a/src/components/TagSelect/index.js b/src/components/TagSelect/index.js
index 773b9c84..c36cd672 100644
--- a/src/components/TagSelect/index.js
+++ b/src/components/TagSelect/index.js
@@ -15,14 +15,23 @@ const TagSelectOption = ({ children, checked, onChange, value }) => (
TagSelectOption.isTagSelectOption = true;
class TagSelect extends Component {
- state = {
- expand: false,
- value: this.props.value || this.props.defaultValue || [],
+ static defaultProps = {
+ hideCheckAll: false,
};
- componentWillReceiveProps(nextProps) {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ expand: false,
+ value: props.value || props.defaultValue || [],
+ };
+ }
+
+ static getDerivedStateFromProps(nextProps) {
if ('value' in nextProps && nextProps.value) {
- this.setState({ value: nextProps.value });
+ return { value: nextProps.value };
}
+ return null;
}
onChange = value => {
@@ -53,7 +62,8 @@ class TagSelect extends Component {
}
handleTagChange = (value, checked) => {
- const checkedTags = [...this.state.value];
+ const { value: StateValue } = this.state;
+ const checkedTags = [...StateValue];
const index = checkedTags.indexOf(value);
if (checked && index === -1) {
@@ -65,22 +75,20 @@ class TagSelect extends Component {
};
handleExpand = () => {
+ const { expand } = this.state;
this.setState({
- expand: !this.state.expand,
+ expand: !expand,
});
};
- isTagSelectOption = node => {
- return (
- node &&
- node.type &&
- (node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption')
- );
- };
+ isTagSelectOption = node =>
+ node &&
+ node.type &&
+ (node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption');
render() {
const { value, expand } = this.state;
- const { children, className, style, expandable } = this.props;
+ const { children, hideCheckAll, className, style, expandable } = this.props;
const checkedAll = this.getAllTags().length === value.length;
@@ -90,9 +98,11 @@ class TagSelect extends Component {
});
return (
-
- 全部
-
+ {hideCheckAll ? null : (
+
+ 全部
+
+ )}
{value &&
React.Children.map(children, child => {
if (this.isTagSelectOption(child)) {
diff --git a/src/components/TagSelect/index.md b/src/components/TagSelect/index.md
index 608219cd..25a42b57 100644
--- a/src/components/TagSelect/index.md
+++ b/src/components/TagSelect/index.md
@@ -19,7 +19,7 @@ order: 13
| defaultValue |默认选中的项 |string[] \| number[] | |
| onChange | 标签选择的回调函数 | Function(checkedTags) | |
| expandable | 是否展示 `展开/收起` 按钮 | Boolean | false |
-
+| hideCheckAll | 隐藏 `全部` 按钮 | Boolean | false |
### TagSelectOption
diff --git a/src/components/TopNavHeader/index.js b/src/components/TopNavHeader/index.js
new file mode 100644
index 00000000..ee302f66
--- /dev/null
+++ b/src/components/TopNavHeader/index.js
@@ -0,0 +1,55 @@
+import React, { PureComponent } from 'react';
+import Link from 'umi/link';
+import RightContent from '../GlobalHeader/RightContent';
+import BaseMenu from '../SiderMenu/BaseMenu';
+import { getFlatMenuKeys } from '../SiderMenu/SiderMenuUtils';
+import styles from './index.less';
+
+export default class TopNavHeader extends PureComponent {
+ state = {
+ maxWidth: undefined,
+ };
+
+ static getDerivedStateFromProps(props) {
+ return {
+ maxWidth: (props.contentWidth === 'Fixed' ? 1200 : window.innerWidth) - 280 - 165 - 40,
+ };
+ }
+
+ render() {
+ const { theme, contentWidth, menuData, logo } = this.props;
+ const { maxWidth } = this.state;
+ const flatMenuKeys = getFlatMenuKeys(menuData);
+ return (
+
+
{
+ this.maim = ref;
+ }}
+ className={`${styles.main} ${contentWidth === 'Fixed' ? styles.wide : ''}`}
+ >
+
+
+
+
+
Ant Design Pro
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/TopNavHeader/index.less b/src/components/TopNavHeader/index.less
new file mode 100644
index 00000000..9883019e
--- /dev/null
+++ b/src/components/TopNavHeader/index.less
@@ -0,0 +1,64 @@
+.head {
+ width: 100%;
+ transition: background 0.3s, width 0.2s;
+ height: 64px;
+ box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+ position: relative;
+ :global {
+ .ant-menu-submenu.ant-menu-submenu-horizontal {
+ line-height: 64px;
+ height: 100%;
+ .ant-menu-submenu-title {
+ height: 100%;
+ }
+ }
+ }
+ &.light {
+ background-color: #fff;
+ }
+ .main {
+ display: flex;
+ height: 64px;
+ padding-left: 24px;
+ &.wide {
+ max-width: 1200px;
+ margin: auto;
+ padding-left: 0;
+ }
+ .left {
+ flex: 1;
+ display: flex;
+ }
+ .right {
+ width: 324px;
+ }
+ }
+}
+
+.logo {
+ width: 165px;
+ height: 64px;
+ position: relative;
+ line-height: 64px;
+ transition: all 0.3s;
+ overflow: hidden;
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ height: 32px;
+ }
+ h1 {
+ color: #fff;
+ display: inline-block;
+ vertical-align: top;
+ font-size: 16px;
+ margin: 0 0 0 12px;
+ font-weight: 400;
+ }
+}
+
+.light {
+ h1 {
+ color: #002140;
+ }
+}
diff --git a/src/components/Trend/demo/basic.md b/src/components/Trend/demo/basic.md
index 63c415dc..da771dc2 100644
--- a/src/components/Trend/demo/basic.md
+++ b/src/components/Trend/demo/basic.md
@@ -5,7 +5,7 @@ title: 演示
在数值背后添加一个小图标来标识涨跌情况。
-````jsx
+```jsx
import Trend from 'ant-design-pro/lib/Trend';
ReactDOM.render(
@@ -14,4 +14,4 @@ ReactDOM.render(
11%
, mountNode);
-````
+```
diff --git a/src/components/Trend/demo/reverse.md b/src/components/Trend/demo/reverse.md
index 143ed023..26f73667 100644
--- a/src/components/Trend/demo/reverse.md
+++ b/src/components/Trend/demo/reverse.md
@@ -5,7 +5,7 @@ title: 颜色反转
在数值背后添加一个小图标来标识涨跌情况。
-````jsx
+```jsx
import Trend from 'ant-design-pro/lib/Trend';
ReactDOM.render(
@@ -14,4 +14,4 @@ ReactDOM.render(
11%
, mountNode);
-````
+```
diff --git a/src/components/Trend/index.js b/src/components/Trend/index.js
index 1cdceb2e..c476ef62 100644
--- a/src/components/Trend/index.js
+++ b/src/components/Trend/index.js
@@ -14,7 +14,7 @@ const Trend = ({ colorful = true, reverseColor = false, flag, children, classNam
);
return (
-
{children}
+
{children}
{flag && (
diff --git a/src/components/Trend/index.md b/src/components/Trend/index.md
index eb78b96e..3e3ac07a 100644
--- a/src/components/Trend/index.md
+++ b/src/components/Trend/index.md
@@ -19,4 +19,4 @@ order: 14
|----------|------------------------------------------|-------------|-------|
| colorful | 是否彩色标记 | Boolean | true |
| flag | 上升下降标识:`up|down` | string | - |
-| reverseColor | 颜色反转 | Boolean | true |
+| reverseColor | 颜色反转 | Boolean | false |
diff --git a/src/components/_utils/pathTools.js b/src/components/_utils/pathTools.js
index 7d49400a..bfb94e74 100644
--- a/src/components/_utils/pathTools.js
+++ b/src/components/_utils/pathTools.js
@@ -1,7 +1,6 @@
// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id']
+// eslint-disable-next-line import/prefer-default-export
export function urlToList(url) {
const urllist = url.split('/').filter(i => i);
- return urllist.map((urlItem, index) => {
- return `/${urllist.slice(0, index + 1).join('/')}`;
- });
+ return urllist.map((urlItem, index) => `/${urllist.slice(0, index + 1).join('/')}`);
}
diff --git a/src/components/utils/pathTools.js b/src/components/utils/pathTools.js
deleted file mode 100644
index 7d49400a..00000000
--- a/src/components/utils/pathTools.js
+++ /dev/null
@@ -1,7 +0,0 @@
-// /userinfo/2144/id => ['/userinfo','/useinfo/2144,'/userindo/2144/id']
-export function urlToList(url) {
- const urllist = url.split('/').filter(i => i);
- return urllist.map((urlItem, index) => {
- return `/${urllist.slice(0, index + 1).join('/')}`;
- });
-}
diff --git a/src/components/utils/pathTools.test.js b/src/components/utils/pathTools.test.js
deleted file mode 100644
index 4daf28b4..00000000
--- a/src/components/utils/pathTools.test.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { urlToList } from './pathTools';
-
-describe('test urlToList', () => {
- it('A path', () => {
- expect(urlToList('/userinfo')).toEqual(['/userinfo']);
- });
- it('Secondary path', () => {
- expect(urlToList('/userinfo/2144')).toEqual([
- '/userinfo',
- '/userinfo/2144',
- ]);
- });
- it('Three paths', () => {
- expect(urlToList('/userinfo/2144/addr')).toEqual([
- '/userinfo',
- '/userinfo/2144',
- '/userinfo/2144/addr',
- ]);
- });
-});
diff --git a/src/defaultSettings.js b/src/defaultSettings.js
new file mode 100644
index 00000000..c254c74e
--- /dev/null
+++ b/src/defaultSettings.js
@@ -0,0 +1,9 @@
+module.exports = {
+ navTheme: 'dark', // theme for nav menu
+ primaryColor: '#1890FF', // primary color of ant design
+ layout: 'sidemenu', // nav menu position: sidemenu or topmenu
+ contentWidth: 'Fluid', // layout of content: Fluid or Fixed, only works when layout is topmenu
+ fixedHeader: false, // sticky header
+ autoHideHeader: false, // auto hide header
+ fixSiderbar: false, // sticky siderbar
+};
diff --git a/src/dva.js b/src/dva.js
deleted file mode 100644
index 4447401b..00000000
--- a/src/dva.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { message } from 'antd';
-
-export function config() {
- return {
- onError(err) {
- err.preventDefault();
- message.error(err.message);
- },
- initialState: {
- global: {
- text: 'hi umi + dva',
- },
- },
- };
-}
diff --git a/src/e2e/baseLayout.e2e.js b/src/e2e/baseLayout.e2e.js
new file mode 100644
index 00000000..93574661
--- /dev/null
+++ b/src/e2e/baseLayout.e2e.js
@@ -0,0 +1,34 @@
+import RouterConfig from '../../config/router.config';
+
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+function formatter(data) {
+ return data
+ .reduce((pre, item) => {
+ pre.push(item.path);
+ return pre;
+ }, [])
+ .filter(item => item);
+}
+
+describe('Homepage', async () => {
+ const testPage = path => async () => {
+ await page.goto(`${BASE_URL}${path}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0
+ );
+ expect(haveFooter).toBeTruthy();
+ };
+
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ await page.setCacheEnabled(false);
+ });
+ const routers = formatter(RouterConfig[1].routes);
+ routers.forEach(route => {
+ fit(`test pages ${route}`, testPage(route));
+ });
+});
diff --git a/src/e2e/home.e2e.js b/src/e2e/home.e2e.js
new file mode 100644
index 00000000..0531d5f4
--- /dev/null
+++ b/src/e2e/home.e2e.js
@@ -0,0 +1,15 @@
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+describe('Homepage', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+ it('it should have logo text', async () => {
+ await page.goto(BASE_URL);
+ await page.waitForSelector('h1', {
+ timeout: 5000,
+ });
+ const text = await page.evaluate(() => document.getElementsByTagName('h1')[0].innerText);
+ expect(text).toContain('Ant Design Pro');
+ });
+});
diff --git a/src/e2e/login.e2e.js b/src/e2e/login.e2e.js
new file mode 100644
index 00000000..b991af44
--- /dev/null
+++ b/src/e2e/login.e2e.js
@@ -0,0 +1,34 @@
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+describe('Login', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+
+ beforeEach(async () => {
+ await page.goto(`${BASE_URL}/user/login`, { waitUntil: 'networkidle2' });
+ await page.evaluate(() => window.localStorage.setItem('antd-pro-authority', 'guest'));
+ });
+
+ it('should login with failure', async () => {
+ await page.waitForSelector('#userName', {
+ timeout: 2000,
+ });
+ await page.type('#userName', 'mockuser');
+ await page.type('#password', 'wrong_password');
+ await page.click('button[type="submit"]');
+ await page.waitForSelector('.ant-alert-error'); // should display error
+ });
+
+ it('should login successfully', async () => {
+ await page.waitForSelector('#userName', {
+ timeout: 2000,
+ });
+ await page.type('#userName', 'admin');
+ await page.type('#password', 'ant.design');
+ await page.click('button[type="submit"]');
+ await page.waitForSelector('.ant-layout-sider h1'); // should display error
+ const text = await page.evaluate(() => document.body.innerHTML);
+ expect(text).toContain('Ant Design Pro ');
+ });
+});
diff --git a/src/e2e/topMenu.e2e.js b/src/e2e/topMenu.e2e.js
new file mode 100644
index 00000000..51ff9f35
--- /dev/null
+++ b/src/e2e/topMenu.e2e.js
@@ -0,0 +1,18 @@
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+describe('Homepage', () => {
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+ it('topmenu should have footer', async () => {
+ const params = '/form/basic-form?navTheme=light&layout=topmenu';
+ await page.goto(`${BASE_URL}${params}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0
+ );
+ expect(haveFooter).toBeTruthy();
+ });
+});
diff --git a/src/e2e/userLayout.e2e.js b/src/e2e/userLayout.e2e.js
new file mode 100644
index 00000000..a3f23a68
--- /dev/null
+++ b/src/e2e/userLayout.e2e.js
@@ -0,0 +1,32 @@
+import RouterConfig from '../../config/router.config';
+
+const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;
+
+function formatter(data) {
+ return data
+ .reduce((pre, item) => {
+ pre.push(item.path);
+ return pre;
+ }, [])
+ .filter(item => item);
+}
+
+describe('Homepage', () => {
+ const testPage = path => async () => {
+ await page.goto(`${BASE_URL}${path}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0
+ );
+ expect(haveFooter).toBeTruthy();
+ };
+
+ beforeAll(async () => {
+ jest.setTimeout(1000000);
+ });
+ formatter(RouterConfig[0].routes).forEach(route => {
+ fit(`test pages ${route}`, testPage(route));
+ });
+});
diff --git a/src/global.js b/src/global.js
index e2679cf8..8c215e92 100644
--- a/src/global.js
+++ b/src/global.js
@@ -1,13 +1,39 @@
-// import Rollbar from 'rollbar';
+import { Modal, message } from 'antd';
+import { formatMessage } from 'umi/locale';
-// // Track error by rollbar.com
-// if (location.host === 'preview.pro.ant.design') {
-// Rollbar.init({
-// accessToken: '033ca6d7c0eb4cc1831cf470c2649971',
-// captureUncaught: true,
-// captureUnhandledRejections: true,
-// payload: {
-// environment: 'production',
-// },
-// });
-// }
+// Notify user if offline now
+window.addEventListener('sw.offline', () => {
+ message.warning(formatMessage({ id: 'app.pwa.offline' }));
+});
+
+// Pop up a prompt on the page asking the user if they want to use the latest version
+window.addEventListener('sw.updated', e => {
+ Modal.confirm({
+ title: formatMessage({ id: 'app.pwa.serviceworker.updated' }),
+ content: formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
+ okText: formatMessage({ id: 'app.pwa.serviceworker.updated.ok' }),
+ onOk: async () => {
+ // Check if there is sw whose state is waiting in ServiceWorkerRegistration
+ // https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
+ const worker = e.detail && e.detail.waiting;
+ if (!worker) {
+ return Promise.resolve();
+ }
+ // Send skip-waiting event to waiting SW with MessageChannel
+ await new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = event => {
+ if (event.data.error) {
+ reject(event.data.error);
+ } else {
+ resolve(event.data);
+ }
+ };
+ worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
+ });
+ // Refresh current page to use the updated HTML and other assets after SW has skiped waiting
+ window.location.reload(true);
+ return true;
+ },
+ });
+});
diff --git a/src/global.less b/src/global.less
index 5cfe914a..fccd7e83 100644
--- a/src/global.less
+++ b/src/global.less
@@ -1,11 +1,15 @@
html,
body,
-:global(#root) {
+#root {
height: 100%;
}
-:global(.ant-layout) {
- min-height: 100%;
+.colorWeak {
+ filter: invert(80%);
+}
+
+.ant-layout {
+ min-height: 100vh;
}
canvas {
@@ -23,9 +27,7 @@ body {
margin: 40px 0 !important;
}
-// temp fix for https://github.com/ant-design/ant-design/commit/a1fafb5b727b62cb0be29ce6e9eca8f579d4f8b7
-:global {
- .ant-spin-container {
- overflow: visible !important;
- }
+ul,
+ol {
+ list-style: none;
}
diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js
index ce0a2bb2..2ca98e37 100644
--- a/src/layouts/BasicLayout.js
+++ b/src/layouts/BasicLayout.js
@@ -1,275 +1,244 @@
-import React, { Fragment } from "react";
-import PropTypes from "prop-types";
-import { Layout, Icon, message } from "antd";
-import DocumentTitle from "react-document-title";
-import { connect } from "dva";
-import { routerRedux } from "dva/router";
-import { ContainerQuery } from "react-container-query";
-import classNames from "classnames";
-import pathToRegexp from "path-to-regexp";
-import { enquireScreen, unenquireScreen } from "enquire-js";
-import GlobalHeader from "components/GlobalHeader";
-import GlobalFooter from "components/GlobalFooter";
-import SiderMenu from "components/SiderMenu";
-import Authorized from "utils/Authorized";
-import { getMenuData } from "common/menu";
-import logo from "assets/logo.svg";
+import React, { Suspense } from 'react';
+import { Layout } from 'antd';
+import DocumentTitle from 'react-document-title';
+import isEqual from 'lodash/isEqual';
+import memoizeOne from 'memoize-one';
+import { connect } from 'dva';
+import { ContainerQuery } from 'react-container-query';
+import classNames from 'classnames';
+import pathToRegexp from 'path-to-regexp';
+import Media from 'react-media';
+import { formatMessage } from 'umi/locale';
+import Authorized from '@/utils/Authorized';
+import logo from '../assets/logo.svg';
+import Footer from './Footer';
+import Header from './Header';
+import Context from './MenuContext';
+import Exception403 from '../pages/Exception/403';
+import PageLoading from '@/components/PageLoading';
+import SiderMenu from '@/components/SiderMenu';
-const { Content, Header, Footer } = Layout;
-const { check } = Authorized;
+// lazy load SettingDrawer
+const SettingDrawer = React.lazy(() => import('@/components/SettingDrawer'));
-/**
- * 根据菜单取得重定向地址.
- */
-const redirectData = [];
-const getRedirect = item => {
- if (item && item.children) {
- if (item.children[0] && item.children[0].path) {
- redirectData.push({
- from: `${item.path}`,
- to: `${item.children[0].path}`
- });
- item.children.forEach(children => {
- getRedirect(children);
- });
- }
- }
-};
-getMenuData().forEach(getRedirect);
-
-/**
- * 获取面包屑映射
- * @param {Object} menuData 菜单配置
- * @param {Object} routerData 路由配置
- */
-const getBreadcrumbNameMap = (menuData, routerData) => {
- const result = {};
- const childResult = {};
- for (const i of menuData) {
- if (!routerData[i.path]) {
- result[i.path] = i;
- }
- if (i.children) {
- Object.assign(childResult, getBreadcrumbNameMap(i.children, routerData));
- }
- }
- return Object.assign({}, routerData, result, childResult);
-};
+const { Content } = Layout;
const query = {
- "screen-xs": {
- maxWidth: 575
+ 'screen-xs': {
+ maxWidth: 575,
},
- "screen-sm": {
+ 'screen-sm': {
minWidth: 576,
- maxWidth: 767
+ maxWidth: 767,
},
- "screen-md": {
+ 'screen-md': {
minWidth: 768,
- maxWidth: 991
+ maxWidth: 991,
},
- "screen-lg": {
+ 'screen-lg': {
minWidth: 992,
- maxWidth: 1199
+ maxWidth: 1199,
+ },
+ 'screen-xl': {
+ minWidth: 1200,
+ maxWidth: 1599,
+ },
+ 'screen-xxl': {
+ minWidth: 1600,
},
- "screen-xl": {
- minWidth: 1200
- }
};
-let isMobile;
-enquireScreen(b => {
- isMobile = b;
-});
-
class BasicLayout extends React.PureComponent {
- static childContextTypes = {
- location: PropTypes.object,
- breadcrumbNameMap: PropTypes.object
- };
- state = {
- isMobile
- };
- getChildContext() {
- const { location, routerData } = this.props;
- return {
- location,
- breadcrumbNameMap: getBreadcrumbNameMap(getMenuData(), routerData)
- };
+ constructor(props) {
+ super(props);
+ this.getPageTitle = memoizeOne(this.getPageTitle);
+ this.matchParamsPath = memoizeOne(this.matchParamsPath, isEqual);
}
+
componentDidMount() {
- this.enquireHandler = enquireScreen(mobile => {
- this.setState({
- isMobile: mobile
- });
+ const {
+ dispatch,
+ route: { routes, authority },
+ } = this.props;
+ dispatch({
+ type: 'user/fetchCurrent',
});
- this.props.dispatch({
- type: "user/fetchCurrent"
+ dispatch({
+ type: 'setting/getSetting',
});
- }
- componentWillUnmount() {
- unenquireScreen(this.enquireHandler);
- }
- getPageTitle() {
- const { routerData, location } = this.props;
- const { pathname } = location;
- let title = "Umi Antd Pro";
- let currRouterData = null;
- // match params path
- Object.keys(routerData).forEach(key => {
- if (pathToRegexp(key).test(pathname)) {
- currRouterData = routerData[key];
- }
+ dispatch({
+ type: 'menu/getMenuData',
+ payload: { routes, authority },
});
- if (currRouterData && currRouterData.name) {
- title = `${currRouterData.name} - Ant Design Pro`;
- }
- return title;
}
- getBashRedirect = () => {
- // According to the url parameter to redirect
- // 这里是重定向的,重定向到 url 的 redirect 参数所示地址
- const urlParams = new URL(window.location.href);
- const redirect = urlParams.searchParams.get("redirect");
- // Remove the parameters in the url
- if (redirect) {
- console.log(redirect);
- urlParams.searchParams.delete("redirect");
- window.history.replaceState(null, "redirect", urlParams.href);
- } else {
- const { routerData } = this.props;
- // get the first authorized route path in routerData
- const authorizedPath = Object.keys(routerData).find(
- item => check(routerData[item].authority, item) && item !== "/"
- );
- // this.props.dispatch(routerRedux.push(authorizedPath));
- return authorizedPath;
+
+ componentDidUpdate(preProps) {
+ // After changing to phone mode,
+ // if collapsed is true, you need to click twice to display
+ const { collapsed, isMobile } = this.props;
+ if (isMobile && !preProps.isMobile && !collapsed) {
+ this.handleMenuCollapse(false);
}
- // this.props.dispatch(routerRedux.push(redirect));
+ }
- return redirect;
- };
- handleMenuCollapse = collapsed => {
- this.props.dispatch({
- type: "global/changeLayoutCollapsed",
- payload: collapsed
- });
+ getContext() {
+ const { location, breadcrumbNameMap } = this.props;
+ return {
+ location,
+ breadcrumbNameMap,
+ };
+ }
+
+ /**
+ * 获取面包屑映射
+ * @param {Object} menuData 菜单配置
+ */
+ getBreadcrumbNameMap() {
+ const routerMap = {};
+ const { menuData } = this.props;
+ const flattenMenuData = data => {
+ data.forEach(menuItem => {
+ if (menuItem.children) {
+ flattenMenuData(menuItem.children);
+ }
+ // Reduce memory usage
+ routerMap[menuItem.path] = menuItem;
+ });
+ };
+ flattenMenuData(menuData);
+ return routerMap;
+ }
+
+ matchParamsPath = (pathname, breadcrumbNameMap) => {
+ const pathKey = Object.keys(breadcrumbNameMap).find(key => pathToRegexp(key).test(pathname));
+ return breadcrumbNameMap[pathKey];
};
- handleNoticeClear = type => {
- message.success(`清空了${type}`);
- this.props.dispatch({
- type: "global/clearNotices",
- payload: type
+
+ getPageTitle = (pathname, breadcrumbNameMap) => {
+ const currRouterData = this.matchParamsPath(pathname, breadcrumbNameMap);
+
+ if (!currRouterData) {
+ return 'Ant Design Pro';
+ }
+ const pageName = formatMessage({
+ id: currRouterData.locale || currRouterData.name,
+ defaultMessage: currRouterData.name,
});
+
+ return `${pageName} - Ant Design Pro`;
};
- handleMenuClick = ({ key }) => {
- if (key === "triggerError") {
- this.props.dispatch(routerRedux.push("/Exception/triggerException"));
- return;
- }
- if (key === "logout") {
- this.props.dispatch({
- type: "login/logout"
- });
+
+ getLayoutStyle = () => {
+ const { fixSiderbar, isMobile, collapsed, layout } = this.props;
+ if (fixSiderbar && layout !== 'topmenu' && !isMobile) {
+ return {
+ paddingLeft: collapsed ? '80px' : '256px',
+ };
}
+ return null;
};
- handleNoticeVisibleChange = visible => {
- if (visible) {
- this.props.dispatch({
- type: "global/fetchNotices"
- });
+
+ getContentStyle = () => {
+ const { fixedHeader } = this.props;
+ return {
+ margin: '24px 24px 0',
+ paddingTop: fixedHeader ? 64 : 0,
+ };
+ };
+
+ handleMenuCollapse = collapsed => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'global/changeLayoutCollapsed',
+ payload: collapsed,
+ });
+ };
+
+ renderSettingDrawer = () => {
+ // Do not render SettingDrawer in production
+ // unless it is deployed in preview.pro.ant.design as demo
+ if (process.env.NODE_ENV === 'production' && APP_TYPE !== 'site') {
+ return null;
}
+ return ;
};
+
render() {
const {
- currentUser,
- collapsed,
- fetchingNotices,
- notices,
- location,
- children
+ navTheme,
+ layout: PropsLayout,
+ children,
+ location: { pathname },
+ isMobile,
+ menuData,
+ breadcrumbNameMap,
} = this.props;
- // const bashRedirect = this.getBashRedirect();
- this.getBashRedirect();
+
+ const isTop = PropsLayout === 'topmenu';
+ const routerConfig = this.matchParamsPath(pathname, breadcrumbNameMap);
+
const layout = (
-
-
-
-
- {children}
+ {isTop && !isMobile ? null : (
+
+ )}
+
+
+
+ }
+ >
+ {children}
+
-
- ,
- href: "https://github.com/xiaohuoni/umi-antd-pro",
- blankTarget: true
- },
- {
- key: "Ant Design",
- title: "Ant Design",
- href: "http://ant.design",
- blankTarget: true
- }
- ]}
- copyright={
-
- Copyright 2018
- 蚂蚁金服体验技术部出品
-
- }
- />
-
+
);
-
return (
-
+
+
- {params => {layout}
}
+ {params => (
+
+ {layout}
+
+ )}
+ }>{this.renderSettingDrawer()}
+
);
}
}
-export default connect(({ user, global, loading }) => ({
- currentUser: user.currentUser,
+export default connect(({ global, setting, menu }) => ({
collapsed: global.collapsed,
- fetchingNotices: loading.effects["global/fetchNotices"],
- notices: global.notices
-}))(BasicLayout);
+ layout: setting.layout,
+ menuData: menu.menuData,
+ breadcrumbNameMap: menu.breadcrumbNameMap,
+ ...setting,
+}))(props => (
+
+ {isMobile => }
+
+));
diff --git a/src/layouts/Footer.js b/src/layouts/Footer.js
new file mode 100644
index 00000000..693c8172
--- /dev/null
+++ b/src/layouts/Footer.js
@@ -0,0 +1,37 @@
+import React, { Fragment } from 'react';
+import { Layout, Icon } from 'antd';
+import GlobalFooter from '@/components/GlobalFooter';
+
+const { Footer } = Layout;
+const FooterView = () => (
+
+ ,
+ href: 'https://github.com/ant-design/ant-design-pro',
+ blankTarget: true,
+ },
+ {
+ key: 'Ant Design',
+ title: 'Ant Design',
+ href: 'https://ant.design',
+ blankTarget: true,
+ },
+ ]}
+ copyright={
+
+ Copyright 2018 蚂蚁金服体验技术部出品
+
+ }
+ />
+
+);
+export default FooterView;
diff --git a/src/layouts/Header.js b/src/layouts/Header.js
new file mode 100644
index 00000000..b7e7a759
--- /dev/null
+++ b/src/layouts/Header.js
@@ -0,0 +1,161 @@
+import React, { PureComponent } from 'react';
+import { formatMessage } from 'umi/locale';
+import { Layout, message } from 'antd';
+import Animate from 'rc-animate';
+import { connect } from 'dva';
+import router from 'umi/router';
+import GlobalHeader from '@/components/GlobalHeader';
+import TopNavHeader from '@/components/TopNavHeader';
+import styles from './Header.less';
+
+const { Header } = Layout;
+
+class HeaderView extends PureComponent {
+ state = {
+ visible: true,
+ };
+
+ static getDerivedStateFromProps(props, state) {
+ if (!props.autoHideHeader && !state.visible) {
+ return {
+ visible: true,
+ };
+ }
+ return null;
+ }
+
+ componentDidMount() {
+ document.addEventListener('scroll', this.handScroll, { passive: true });
+ }
+
+ componentWillUnmount() {
+ document.removeEventListener('scroll', this.handScroll);
+ }
+
+ getHeadWidth = () => {
+ const { isMobile, collapsed, setting } = this.props;
+ const { fixedHeader, layout } = setting;
+ if (isMobile || !fixedHeader || layout === 'topmenu') {
+ return '100%';
+ }
+ return collapsed ? 'calc(100% - 80px)' : 'calc(100% - 256px)';
+ };
+
+ handleNoticeClear = type => {
+ message.success(
+ `${formatMessage({ id: 'component.noticeIcon.cleared' })} ${formatMessage({
+ id: `component.globalHeader.${type}`,
+ })}`
+ );
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'global/clearNotices',
+ payload: type,
+ });
+ };
+
+ handleMenuClick = ({ key }) => {
+ const { dispatch } = this.props;
+ if (key === 'userCenter') {
+ router.push('/account/center');
+ return;
+ }
+ if (key === 'triggerError') {
+ router.push('/exception/trigger');
+ return;
+ }
+ if (key === 'userinfo') {
+ router.push('/account/settings/base');
+ return;
+ }
+ if (key === 'logout') {
+ dispatch({
+ type: 'login/logout',
+ });
+ }
+ };
+
+ handleNoticeVisibleChange = visible => {
+ if (visible) {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'global/fetchNotices',
+ });
+ }
+ };
+
+ handScroll = () => {
+ const { autoHideHeader } = this.props;
+ const { visible } = this.state;
+ if (!autoHideHeader) {
+ return;
+ }
+ const scrollTop = document.body.scrollTop + document.documentElement.scrollTop;
+ if (!this.ticking) {
+ this.ticking = true;
+ requestAnimationFrame(() => {
+ if (this.oldScrollTop > scrollTop) {
+ this.setState({
+ visible: true,
+ });
+ }
+ if (scrollTop > 300 && visible) {
+ this.setState({
+ visible: false,
+ });
+ }
+ if (scrollTop < 300 && !visible) {
+ this.setState({
+ visible: true,
+ });
+ }
+ this.oldScrollTop = scrollTop;
+ this.ticking = false;
+ });
+ }
+ };
+
+ render() {
+ const { isMobile, handleMenuCollapse, setting } = this.props;
+ const { navTheme, layout, fixedHeader } = setting;
+ const { visible } = this.state;
+ const isTop = layout === 'topmenu';
+ const width = this.getHeadWidth();
+ const HeaderDom = visible ? (
+
+ {isTop && !isMobile ? (
+
+ ) : (
+
+ )}
+
+ ) : null;
+ return (
+
+ {HeaderDom}
+
+ );
+ }
+}
+
+export default connect(({ user, global, setting, loading }) => ({
+ currentUser: user.currentUser,
+ collapsed: global.collapsed,
+ fetchingNotices: loading.effects['global/fetchNotices'],
+ notices: global.notices,
+ setting,
+}))(HeaderView);
diff --git a/src/layouts/Header.less b/src/layouts/Header.less
new file mode 100644
index 00000000..394bfda1
--- /dev/null
+++ b/src/layouts/Header.less
@@ -0,0 +1,8 @@
+.fixedHeader {
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 100%;
+ z-index: 9;
+ transition: width 0.2s;
+}
diff --git a/src/layouts/MenuContext.js b/src/layouts/MenuContext.js
new file mode 100644
index 00000000..860f1068
--- /dev/null
+++ b/src/layouts/MenuContext.js
@@ -0,0 +1,3 @@
+import { createContext } from 'react';
+
+export default createContext();
diff --git a/src/layouts/PageHeaderLayout.js b/src/layouts/PageHeaderLayout.js
deleted file mode 100644
index 2c615e89..00000000
--- a/src/layouts/PageHeaderLayout.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react';
-import { Link } from 'dva/router';
-import PageHeader from '../components/PageHeader';
-import styles from './PageHeaderLayout.less';
-
-export default ({ children, wrapperClassName, top, ...restProps }) => (
-
- {top}
-
- {children ?
{children}
: null}
-
-);
diff --git a/src/layouts/UserLayout.js b/src/layouts/UserLayout.js
new file mode 100644
index 00000000..16b70ac7
--- /dev/null
+++ b/src/layouts/UserLayout.js
@@ -0,0 +1,72 @@
+import React, { Fragment } from 'react';
+import { formatMessage } from 'umi/locale';
+import Link from 'umi/link';
+import { Icon } from 'antd';
+import GlobalFooter from '@/components/GlobalFooter';
+import SelectLang from '@/components/SelectLang';
+import styles from './UserLayout.less';
+import logo from '../assets/logo.svg';
+
+const links = [
+ {
+ key: 'help',
+ title: formatMessage({ id: 'layout.user.link.help' }),
+ href: '',
+ },
+ {
+ key: 'privacy',
+ title: formatMessage({ id: 'layout.user.link.privacy' }),
+ href: '',
+ },
+ {
+ key: 'terms',
+ title: formatMessage({ id: 'layout.user.link.terms' }),
+ href: '',
+ },
+];
+
+const copyright = (
+
+ Copyright 2018 蚂蚁金服体验技术部出品
+
+);
+
+class UserLayout extends React.PureComponent {
+ // @TODO title
+ // getPageTitle() {
+ // const { routerData, location } = this.props;
+ // const { pathname } = location;
+ // let title = 'Ant Design Pro';
+ // if (routerData[pathname] && routerData[pathname].name) {
+ // title = `${routerData[pathname].name} - Ant Design Pro`;
+ // }
+ // return title;
+ // }
+
+ render() {
+ const { children } = this.props;
+ return (
+ // @TODO
+
+
+
+
+
+
+
+
+
+
Ant Design
+
+
+
Ant Design 是西湖区最具影响力的 Web 设计规范
+
+ {children}
+
+
+
+ );
+ }
+}
+
+export default UserLayout;
diff --git a/src/pages/User/UserLayout.less b/src/layouts/UserLayout.less
similarity index 75%
rename from src/pages/User/UserLayout.less
rename to src/layouts/UserLayout.less
index f01dae08..5eb257a4 100644
--- a/src/pages/User/UserLayout.less
+++ b/src/layouts/UserLayout.less
@@ -1,10 +1,21 @@
@import '~antd/lib/style/themes/default.less';
+
.container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: auto;
- background: #f0f2f5;
+ background: @layout-body-background;
+}
+
+.lang {
+ text-align: right;
+ width: 100%;
+ height: 40px;
+ line-height: 44px;
+ :global(.ant-dropdown-trigger) {
+ margin-right: 24px;
+ }
}
.content {
@@ -21,7 +32,7 @@
}
.content {
- padding: 112px 0 24px 0;
+ padding: 32px 0 24px 0;
}
}
@@ -46,7 +57,7 @@
.title {
font-size: 33px;
color: @heading-color;
- font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;
font-weight: 600;
position: relative;
top: 2px;
diff --git a/src/layouts/index.js b/src/layouts/index.js
index 40492420..eccaa5a7 100644
--- a/src/layouts/index.js
+++ b/src/layouts/index.js
@@ -1,26 +1,16 @@
import BasicLayout from "./BasicLayout";
-import { config } from "utils";
-import { getRouterData } from "common/router";
-import { LocaleProvider } from "antd";
-import zh_CN from "antd/lib/locale-provider/zh_CN";
-import "moment/locale/zh-cn";
-
+import UserLayout from "./UserLayout";
+import config from "@/utils/config";
const { openPages } = config;
export default props => {
- const { children, location } = props;
- const routerData = getRouterData({});
- const newProps = {
- ...props,
- routerData: routerData
- };
+ const { location } = props;
let { pathname } = location;
pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
+
if (openPages && openPages.includes(pathname)) {
- return {children}
;
+ return ;
}
return (
-
-
-
+
);
};
diff --git a/src/locales/en-US.js b/src/locales/en-US.js
new file mode 100644
index 00000000..fcaffceb
--- /dev/null
+++ b/src/locales/en-US.js
@@ -0,0 +1,33 @@
+import analysis from './en-US/analysis';
+import exception from './en-US/exception';
+import form from './en-US/form';
+import globalHeader from './en-US/globalHeader';
+import login from './en-US/login';
+import menu from './en-US/menu';
+import monitor from './en-US/monitor';
+import result from './en-US/result';
+import settingDrawer from './en-US/settingDrawer';
+import settings from './en-US/settings';
+import pwa from './en-US/pwa';
+
+export default {
+ 'navBar.lang': 'Languages',
+ 'layout.user.link.help': 'Help',
+ 'layout.user.link.privacy': 'Privacy',
+ 'layout.user.link.terms': 'Terms',
+ 'app.home.introduce': 'introduce',
+ 'app.forms.basic.title': 'Basic form',
+ 'app.forms.basic.description':
+ 'Form pages are used to collect or verify information to users, and basic forms are common in scenarios where there are fewer data items.',
+ ...analysis,
+ ...exception,
+ ...form,
+ ...globalHeader,
+ ...login,
+ ...menu,
+ ...monitor,
+ ...result,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+};
diff --git a/src/locales/en-US/analysis.js b/src/locales/en-US/analysis.js
new file mode 100644
index 00000000..f3005dab
--- /dev/null
+++ b/src/locales/en-US/analysis.js
@@ -0,0 +1,34 @@
+export default {
+ 'app.analysis.test': 'Gongzhuan No.{no} shop',
+ 'app.analysis.introduce': 'Introduce',
+ 'app.analysis.total-sales': 'Total Sales',
+ 'app.analysis.day-sales': 'Daily Sales',
+ 'app.analysis.visits': 'Visits',
+ 'app.analysis.visits-trend': 'Visits Trend',
+ 'app.analysis.visits-ranking': 'Visits Ranking',
+ 'app.analysis.day-visits': 'Daily Visits',
+ 'app.analysis.week': 'WoW Change',
+ 'app.analysis.day': 'DoD Change',
+ 'app.analysis.payments': 'Payments',
+ 'app.analysis.conversion-rate': 'Conversion Rate',
+ 'app.analysis.operational-effect': 'Operational Effect',
+ 'app.analysis.sales-trend': 'Stores Sales Trend',
+ 'app.analysis.sales-ranking': 'Sales Ranking',
+ 'app.analysis.all-year': 'All Year',
+ 'app.analysis.all-month': 'All Month',
+ 'app.analysis.all-week': 'All Week',
+ 'app.analysis.all-day': 'All day',
+ 'app.analysis.search-users': 'Search Users',
+ 'app.analysis.per-capita-search': 'Per Capita Search',
+ 'app.analysis.online-top-search': 'Online Top Search',
+ 'app.analysis.the-proportion-of-sales': 'The Proportion Of Sales',
+ 'app.analysis.channel.all': 'ALL',
+ 'app.analysis.channel.online': 'Online',
+ 'app.analysis.channel.stores': 'Stores',
+ 'app.analysis.sales': 'Sales',
+ 'app.analysis.traffic': 'Traffic',
+ 'app.analysis.table.rank': 'Rank',
+ 'app.analysis.table.search-keyword': 'Keyword',
+ 'app.analysis.table.users': 'Users',
+ 'app.analysis.table.weekly-range': 'Weekly Range',
+};
diff --git a/src/locales/en-US/exception.js b/src/locales/en-US/exception.js
new file mode 100644
index 00000000..5035552a
--- /dev/null
+++ b/src/locales/en-US/exception.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.exception.back': 'Back to home',
+ 'app.exception.description.403': "Sorry, you don't have access to this page",
+ 'app.exception.description.404': 'Sorry, the page you visited does not exist',
+ 'app.exception.description.500': 'Sorry, the server is reporting an error',
+};
diff --git a/src/locales/en-US/form.js b/src/locales/en-US/form.js
new file mode 100644
index 00000000..29f1f6a9
--- /dev/null
+++ b/src/locales/en-US/form.js
@@ -0,0 +1,38 @@
+export default {
+ 'form.captcha': 'Get Captcha',
+ 'form.captcha.second': 'sec',
+ 'form.optional': ' (optional) ',
+ 'form.submit': 'Submit',
+ 'form.save': 'Save',
+ 'form.email.placeholder': 'Email',
+ 'form.password.placeholder': 'Password',
+ 'form.confirm-password.placeholder': 'Confirm password',
+ 'form.phone-number.placeholder': 'Phone number',
+ 'form.verification-code.placeholder': 'Verification code',
+ 'form.title.label': 'Title',
+ 'form.title.placeholder': 'Give the target a name',
+ 'form.date.label': 'Start and end date',
+ 'form.date.placeholder.start': 'Start date',
+ 'form.date.placeholder.end': 'End date',
+ 'form.goal.label': 'Goal description',
+ 'form.goal.placeholder': 'Please enter your work goals',
+ 'form.standard.label': 'Metrics',
+ 'form.standard.placeholder': 'Please enter a metric',
+ 'form.client.label': 'Client',
+ 'form.client.label.tooltip': 'Target service object',
+ 'form.client.placeholder':
+ 'Please describe your customer service, internal customers directly @ Name / job number',
+ 'form.invites.label': 'Inviting critics',
+ 'form.invites.placeholder': 'Please direct @ Name / job number, you can invite up to 5 people',
+ 'form.weight.label': 'Weight',
+ 'form.weight.placeholder': 'Please enter weight',
+ 'form.public.label': 'Target disclosure',
+ 'form.public.label.help': 'Customers and invitees are shared by default',
+ 'form.public.radio.public': 'Public',
+ 'form.public.radio.partially-public': 'Partially public',
+ 'form.public.radio.private': 'Private',
+ 'form.publicUsers.placeholder': 'Open to',
+ 'form.publicUsers.option.A': 'Colleague A',
+ 'form.publicUsers.option.B': 'Colleague B',
+ 'form.publicUsers.option.C': 'Colleague C',
+};
diff --git a/src/locales/en-US/globalHeader.js b/src/locales/en-US/globalHeader.js
new file mode 100644
index 00000000..4e95bca8
--- /dev/null
+++ b/src/locales/en-US/globalHeader.js
@@ -0,0 +1,16 @@
+export default {
+ 'component.globalHeader.search': 'Search',
+ 'component.globalHeader.search.example1': 'Search example 1',
+ 'component.globalHeader.search.example2': 'Search example 2',
+ 'component.globalHeader.search.example3': 'Search example 3',
+ 'component.globalHeader.help': 'Help',
+ 'component.globalHeader.notification': 'Notification',
+ 'component.globalHeader.notification.empty': 'You have viewed all notifications.',
+ 'component.globalHeader.message': 'Message',
+ 'component.globalHeader.message.empty': 'You have viewed all messsages.',
+ 'component.globalHeader.event': 'Event',
+ 'component.globalHeader.event.empty': 'You have viewed all events.',
+ 'component.noticeIcon.clear': 'Clear',
+ 'component.noticeIcon.cleared': 'Cleared',
+ 'component.noticeIcon.empty': 'No notifications',
+};
diff --git a/src/locales/en-US/login.js b/src/locales/en-US/login.js
new file mode 100644
index 00000000..963b7b59
--- /dev/null
+++ b/src/locales/en-US/login.js
@@ -0,0 +1,36 @@
+export default {
+ 'app.login.message-invalid-credentials': 'Invalid username or password(admin/ant.design)',
+ 'app.login.message-invalid-verification-code': 'Invalid verification code',
+ 'app.login.tab-login-credentials': 'Credentials',
+ 'app.login.tab-login-mobile': 'Mobile number',
+ 'app.login.remember-me': 'Remember me',
+ 'app.login.forgot-password': 'Forgot your password?',
+ 'app.login.sign-in-with': 'Sign in with',
+ 'app.login.signup': 'Sign up',
+ 'app.login.login': 'Login',
+ 'app.register.register': 'Register',
+ 'app.register.get-verification-code': 'Get code',
+ 'app.register.sign-in': 'Already have an account?',
+ 'app.register-result.msg': 'Account:registered at {email}',
+ 'app.register-result.activation-email':
+ 'The activation email has been sent to your email address and is valid for 24 hours. Please log in to the email in time and click on the link in the email to activate the account.',
+ 'app.register-result.back-home': 'Back to home',
+ 'app.register-result.view-mailbox': 'View mailbox',
+ 'validation.email.required': 'Please enter your email!',
+ 'validation.email.wrong-format': 'The email address is in the wrong format!',
+ 'validation.password.required': 'Please enter your password!',
+ 'validation.password.twice': 'The passwords entered twice do not match!',
+ 'validation.password.strength.msg':
+ "Please enter at least 6 characters and don't use passwords that are easy to guess.",
+ 'validation.password.strength.strong': 'Strength: strong',
+ 'validation.password.strength.medium': 'Strength: medium',
+ 'validation.password.strength.short': 'Strength: too short',
+ 'validation.confirm-password.required': 'Please confirm your password!',
+ 'validation.phone-number.required': 'Please enter your phone number!',
+ 'validation.phone-number.wrong-format': 'Malformed phone number!',
+ 'validation.verification-code.required': 'Please enter the verification code!',
+ 'validation.title.required': 'Please enter a title',
+ 'validation.date.required': 'Please select the start and end date',
+ 'validation.goal.required': 'Please enter a description of the goal',
+ 'validation.standard.required': 'Please enter a metric',
+};
diff --git a/src/locales/en-US/menu.js b/src/locales/en-US/menu.js
new file mode 100644
index 00000000..3102a600
--- /dev/null
+++ b/src/locales/en-US/menu.js
@@ -0,0 +1,38 @@
+export default {
+ 'menu.home': 'Home',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': 'Analysis',
+ 'menu.dashboard.monitor': 'Monitor',
+ 'menu.dashboard.workplace': 'Workplace',
+ 'menu.form': 'Form',
+ 'menu.form.basicform': 'Basic Form',
+ 'menu.form.stepform': 'Step Form',
+ 'menu.form.stepform.info': 'Step Form(write transfer information)',
+ 'menu.form.stepform.confirm': 'Step Form(confirm transfer information)',
+ 'menu.form.stepform.result': 'Step Form(finished)',
+ 'menu.form.advancedform': 'Advanced Form',
+ 'menu.list': 'List',
+ 'menu.list.searchtable': 'Search Table',
+ 'menu.list.basiclist': 'Basic List',
+ 'menu.list.cardlist': 'Card List',
+ 'menu.list.searchlist': 'Search List',
+ 'menu.list.searchlist.articles': 'Search List(articles)',
+ 'menu.list.searchlist.projects': 'Search List(projects)',
+ 'menu.list.searchlist.applications': 'Search List(applications)',
+ 'menu.profile': 'Profile',
+ 'menu.profile.basic': 'Basic Profile',
+ 'menu.profile.advanced': 'Advanced Profile',
+ 'menu.result': 'Result',
+ 'menu.result.success': 'Success',
+ 'menu.result.fail': 'Fail',
+ 'menu.exception': 'Exception',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': 'Trigger',
+ 'menu.account': 'Account',
+ 'menu.account.center': 'Account Center',
+ 'menu.account.settings': 'Account Settings',
+ 'menu.account.trigger': 'Trigger Error',
+ 'menu.account.logout': 'Logout',
+};
diff --git a/src/locales/en-US/monitor.js b/src/locales/en-US/monitor.js
new file mode 100644
index 00000000..dcb57055
--- /dev/null
+++ b/src/locales/en-US/monitor.js
@@ -0,0 +1,18 @@
+export default {
+ 'app.monitor.trading-activity': 'Real-Time Trading Activity',
+ 'app.monitor.total-transactions': 'Total transactions today',
+ 'app.monitor.sales-target': 'Sales target completion rate',
+ 'app.monitor.remaining-time': 'Remaining time of activity',
+ 'app.monitor.total-transactions-per-second': 'Total transactions per second',
+ 'app.monitor.activity-forecast': 'Activity forecast',
+ 'app.monitor.efficiency': 'Efficiency',
+ 'app.monitor.ratio': 'Ratio',
+ 'app.monitor.proportion-per-category': 'Proportion Per Category',
+ 'app.monitor.fast-food': 'Fast food',
+ 'app.monitor.western-food': 'Western food',
+ 'app.monitor.hot-pot': 'Hot pot',
+ 'app.monitor.waiting-for-implementation': 'Waiting for implementation',
+ 'app.monitor.popular-searches': 'Popular Searches',
+ 'app.monitor.resource-surplus': 'Resource Surplus',
+ 'app.monitor.fund-surplus': 'Fund Surplus',
+};
diff --git a/src/locales/en-US/pwa.js b/src/locales/en-US/pwa.js
new file mode 100644
index 00000000..ed8d199e
--- /dev/null
+++ b/src/locales/en-US/pwa.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': 'You are offline now',
+ 'app.pwa.serviceworker.updated': 'New content is available',
+ 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+ 'app.pwa.serviceworker.updated.ok': 'Refresh',
+};
diff --git a/src/locales/en-US/result.js b/src/locales/en-US/result.js
new file mode 100644
index 00000000..23de8b7b
--- /dev/null
+++ b/src/locales/en-US/result.js
@@ -0,0 +1,28 @@
+export default {
+ 'app.result.error.title': 'Submission Failed',
+ 'app.result.error.description':
+ 'Please check and modify the following information before resubmitting.',
+ 'app.result.error.hint-title': 'The content you submitted has the following error:',
+ 'app.result.error.hint-text1': 'Your account has been frozen',
+ 'app.result.error.hint-btn1': 'Thaw immediately',
+ 'app.result.error.hint-text2': 'Your account is not yet eligible to apply',
+ 'app.result.error.hint-btn2': 'Upgrade immediately',
+ 'app.result.error.btn-text': 'Return to modify',
+ 'app.result.success.title': 'Submission Success',
+ 'app.result.success.description':
+ 'The submission results page is used to feed back the results of a series of operational tasks. If it is a simple operation, use the Message global prompt feedback. This text area can show a simple supplementary explanation. If there is a similar requirement for displaying “documents”, the following gray area can present more complicated content.',
+ 'app.result.success.operate-title': 'Project Name',
+ 'app.result.success.operate-id': 'Project ID:',
+ 'app.result.success.principal': 'Principal:',
+ 'app.result.success.operate-time': 'Effective time:',
+ 'app.result.success.step1-title': 'Create project',
+ 'app.result.success.step1-operator': 'Qu Lili',
+ 'app.result.success.step2-title': 'Departmental preliminary review',
+ 'app.result.success.step2-operator': 'Zhou Maomao',
+ 'app.result.success.step2-extra': 'Urge',
+ 'app.result.success.step3-title': 'Financial review',
+ 'app.result.success.step4-title': 'Finish',
+ 'app.result.success.btn-return': 'Back to list',
+ 'app.result.success.btn-project': 'View project',
+ 'app.result.success.btn-print': 'Print',
+};
diff --git a/src/locales/en-US/settingDrawer.js b/src/locales/en-US/settingDrawer.js
new file mode 100644
index 00000000..a644905e
--- /dev/null
+++ b/src/locales/en-US/settingDrawer.js
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': 'Page style setting',
+ 'app.setting.pagestyle.dark': 'Dark style',
+ 'app.setting.pagestyle.light': 'Light style',
+ 'app.setting.content-width': 'Content Width',
+ 'app.setting.content-width.fixed': 'Fixed',
+ 'app.setting.content-width.fluid': 'Fluid',
+ 'app.setting.themecolor': 'Theme Color',
+ 'app.setting.themecolor.dust': 'Dust Red',
+ 'app.setting.themecolor.volcano': 'Volcano',
+ 'app.setting.themecolor.sunset': 'Sunset Orange',
+ 'app.setting.themecolor.cyan': 'Cyan',
+ 'app.setting.themecolor.green': 'Polar Green',
+ 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
+ 'app.setting.themecolor.geekblue': 'Geek Glue',
+ 'app.setting.themecolor.purple': 'Golden Purple',
+ 'app.setting.navigationmode': 'Navigation Mode',
+ 'app.setting.sidemenu': 'Side Menu Layout',
+ 'app.setting.topmenu': 'Top Menu Layout',
+ 'app.setting.fixedheader': 'Fixed Header',
+ 'app.setting.fixedsidebar': 'Fixed Sidebar',
+ 'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
+ 'app.setting.hideheader': 'Hidden Header when scrolling',
+ 'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
+ 'app.setting.othersettings': 'Other Settings',
+ 'app.setting.weakmode': 'Weak Mode',
+ 'app.setting.copy': 'Copy Setting',
+ 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
+ 'app.setting.production.hint':
+ 'Setting panel shows in development environment only, please manually modify',
+};
diff --git a/src/locales/en-US/settings.js b/src/locales/en-US/settings.js
new file mode 100644
index 00000000..e0de686d
--- /dev/null
+++ b/src/locales/en-US/settings.js
@@ -0,0 +1,60 @@
+export default {
+ 'app.settings.menuMap.basic': 'Basic Settings',
+ 'app.settings.menuMap.security': 'Security Settings',
+ 'app.settings.menuMap.binding': 'Account Binding',
+ 'app.settings.menuMap.notification': 'New Message Notification',
+ 'app.settings.basic.avatar': 'Avatar',
+ 'app.settings.basic.change-avatar': 'Change avatar',
+ 'app.settings.basic.email': 'Email',
+ 'app.settings.basic.email-message': 'Please input your email!',
+ 'app.settings.basic.nickname': 'Nickname',
+ 'app.settings.basic.nickname-message': 'Please input your Nickname!',
+ 'app.settings.basic.profile': 'Personal profile',
+ 'app.settings.basic.profile-message': 'Please input your personal profile!',
+ 'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
+ 'app.settings.basic.country': 'Country/Region',
+ 'app.settings.basic.country-message': 'Please input your country!',
+ 'app.settings.basic.geographic': 'Province or city',
+ 'app.settings.basic.geographic-message': 'Please input your geographic info!',
+ 'app.settings.basic.address': 'Street Address',
+ 'app.settings.basic.address-message': 'Please input your address!',
+ 'app.settings.basic.phone': 'Phone Number',
+ 'app.settings.basic.phone-message': 'Please input your phone!',
+ 'app.settings.basic.update': 'Update Information',
+ 'app.settings.security.strong': 'Strong',
+ 'app.settings.security.medium': 'Medium',
+ 'app.settings.security.weak': 'Weak',
+ 'app.settings.security.password': 'Account Password',
+ 'app.settings.security.password-description': 'Current password strength:',
+ 'app.settings.security.phone': 'Security Phone',
+ 'app.settings.security.phone-description': 'Bound phone:',
+ 'app.settings.security.question': 'Security Question',
+ 'app.settings.security.question-description':
+ 'The security question is not set, and the security policy can effectively protect the account security',
+ 'app.settings.security.email': 'Backup Email',
+ 'app.settings.security.email-description': 'Bound Email:',
+ 'app.settings.security.mfa': 'MFA Device',
+ 'app.settings.security.mfa-description':
+ 'Unbound MFA device, after binding, can be confirmed twice',
+ 'app.settings.security.modify': 'Modify',
+ 'app.settings.security.set': 'Set',
+ 'app.settings.security.bind': 'Bind',
+ 'app.settings.binding.taobao': 'Binding Taobao',
+ 'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
+ 'app.settings.binding.alipay': 'Binding Alipay',
+ 'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
+ 'app.settings.binding.dingding': 'Binding DingTalk',
+ 'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
+ 'app.settings.binding.bind': 'Bind',
+ 'app.settings.notification.password': 'Account Password',
+ 'app.settings.notification.password-description':
+ 'Messages from other users will be notified in the form of a station letter',
+ 'app.settings.notification.messages': 'System Messages',
+ 'app.settings.notification.messages-description':
+ 'System messages will be notified in the form of a station letter',
+ 'app.settings.notification.todo': 'To-do Notification',
+ 'app.settings.notification.todo-description':
+ 'The to-do list will be notified in the form of a letter from the station',
+ 'app.settings.open': 'Open',
+ 'app.settings.close': 'Close',
+};
diff --git a/src/locales/pt-BR.js b/src/locales/pt-BR.js
new file mode 100644
index 00000000..45df10bb
--- /dev/null
+++ b/src/locales/pt-BR.js
@@ -0,0 +1,33 @@
+import analysis from './pt-BR/analysis';
+import exception from './pt-BR/exception';
+import form from './pt-BR/form';
+import globalHeader from './pt-BR/globalHeader';
+import login from './pt-BR/login';
+import menu from './pt-BR/menu';
+import monitor from './pt-BR/monitor';
+import result from './pt-BR/result';
+import settingDrawer from './pt-BR/settingDrawer';
+import settings from './pt-BR/settings';
+import pwa from './pt-BR/pwa';
+
+export default {
+ 'navBar.lang': 'Idiomas',
+ 'layout.user.link.help': 'ajuda',
+ 'layout.user.link.privacy': 'política de privacidade',
+ 'layout.user.link.terms': 'termos de serviços',
+ 'app.home.introduce': 'introduzir',
+ 'app.forms.basic.title': 'Basic form',
+ 'app.forms.basic.description':
+ 'Form pages are used to collect or verify information to users, and basic forms are common in scenarios where there are fewer data items.',
+ ...analysis,
+ ...exception,
+ ...form,
+ ...globalHeader,
+ ...login,
+ ...menu,
+ ...monitor,
+ ...result,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+};
diff --git a/src/locales/pt-BR/analysis.js b/src/locales/pt-BR/analysis.js
new file mode 100644
index 00000000..96a80adf
--- /dev/null
+++ b/src/locales/pt-BR/analysis.js
@@ -0,0 +1,34 @@
+export default {
+ 'app.analysis.test': 'Gongzhuan No.{no} shop',
+ 'app.analysis.introduce': 'Introduzir',
+ 'app.analysis.total-sales': 'Vendas Totais',
+ 'app.analysis.day-sales': 'Vendas do Dia',
+ 'app.analysis.visits': 'Visitas',
+ 'app.analysis.visits-trend': 'Tendência de Visitas',
+ 'app.analysis.visits-ranking': 'Ranking de Visitas',
+ 'app.analysis.day-visits': 'Visitas do Dia',
+ 'app.analysis.week': 'Taxa Semanal',
+ 'app.analysis.day': 'Taxa Diária',
+ 'app.analysis.payments': 'Pagamentos',
+ 'app.analysis.conversion-rate': 'Taxa de Conversão',
+ 'app.analysis.operational-effect': 'Efeito Operacional',
+ 'app.analysis.sales-trend': 'Tendência de Vendas das Lojas',
+ 'app.analysis.sales-ranking': 'Ranking de Vendas',
+ 'app.analysis.all-year': 'Todo ano',
+ 'app.analysis.all-month': 'Todo mês',
+ 'app.analysis.all-week': 'Toda semana',
+ 'app.analysis.all-day': 'Todo dia',
+ 'app.analysis.search-users': 'Pesquisa de Usuários',
+ 'app.analysis.per-capita-search': 'Busca Per Capta',
+ 'app.analysis.online-top-search': 'Mais Buscadas Online',
+ 'app.analysis.the-proportion-of-sales': 'The Proportion Of Sales',
+ 'app.analysis.channel.all': 'Tudo',
+ 'app.analysis.channel.online': 'Online',
+ 'app.analysis.channel.stores': 'Lojas',
+ 'app.analysis.sales': 'Vendas',
+ 'app.analysis.traffic': 'Tráfego',
+ 'app.analysis.table.rank': 'Rank',
+ 'app.analysis.table.search-keyword': 'Palavra chave',
+ 'app.analysis.table.users': 'Usuários',
+ 'app.analysis.table.weekly-range': 'Faixa Semanal',
+};
diff --git a/src/locales/pt-BR/exception.js b/src/locales/pt-BR/exception.js
new file mode 100644
index 00000000..ff4f1a8d
--- /dev/null
+++ b/src/locales/pt-BR/exception.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.exception.back': 'Voltar para Início',
+ 'app.exception.description.403': 'Desculpe, você não tem acesso a esta página',
+ 'app.exception.description.404': 'Desculpe, a página que você visitou não existe',
+ 'app.exception.description.500': 'Desculpe, o servidor está reportando um erro',
+};
diff --git a/src/locales/pt-BR/form.js b/src/locales/pt-BR/form.js
new file mode 100644
index 00000000..4b7b762c
--- /dev/null
+++ b/src/locales/pt-BR/form.js
@@ -0,0 +1,38 @@
+export default {
+ 'form.captcha': 'Get Captcha',
+ 'form.captcha.second': 'sec',
+ 'form.email.placeholder': 'Email',
+ 'form.password.placeholder': 'Senha',
+ 'form.confirm-password.placeholder': 'Confirme a senha',
+ 'form.phone-number.placeholder': 'Telefone',
+ 'form.verification-code.placeholder': 'Código de verificação',
+ 'form.optional': ' (optional) ',
+ 'form.submit': 'Submit',
+ 'form.save': 'Save',
+ 'form.title.label': 'Title',
+ 'form.title.placeholder': 'Give the target a name',
+ 'form.date.label': 'Start and end date',
+ 'form.date.placeholder.start': 'Start date',
+ 'form.date.placeholder.end': 'End date',
+ 'form.goal.label': 'Goal description',
+ 'form.goal.placeholder': 'Please enter your work goals',
+ 'form.standard.label': 'Metrics',
+ 'form.standard.placeholder': 'Please enter a metric',
+ 'form.client.label': 'Client',
+ 'form.client.label.tooltip': 'Target service object',
+ 'form.client.placeholder':
+ 'Please describe your customer service, internal customers directly @ Name / job number',
+ 'form.invites.label': 'Inviting critics',
+ 'form.invites.placeholder': 'Please direct @ Name / job number, you can invite up to 5 people',
+ 'form.weight.label': 'Weight',
+ 'form.weight.placeholder': 'Please enter weight',
+ 'form.public.label': 'Target disclosure',
+ 'form.public.label.help': 'Customers and invitees are shared by default',
+ 'form.public.radio.public': 'Public',
+ 'form.public.radio.partially-public': 'Partially public',
+ 'form.public.radio.private': 'Private',
+ 'form.publicUsers.placeholder': 'Open to',
+ 'form.publicUsers.option.A': 'Colleague A',
+ 'form.publicUsers.option.B': 'Colleague B',
+ 'form.publicUsers.option.C': 'Colleague C',
+};
diff --git a/src/locales/pt-BR/globalHeader.js b/src/locales/pt-BR/globalHeader.js
new file mode 100644
index 00000000..dbb8c6d9
--- /dev/null
+++ b/src/locales/pt-BR/globalHeader.js
@@ -0,0 +1,16 @@
+export default {
+ 'component.globalHeader.search': 'Busca',
+ 'component.globalHeader.search.example1': 'Exemplo de busca 1',
+ 'component.globalHeader.search.example2': 'Exemplo de busca 2',
+ 'component.globalHeader.search.example3': 'Exemplo de busca 3',
+ 'component.globalHeader.help': 'Ajuda',
+ 'component.globalHeader.notification': 'Notificação',
+ 'component.globalHeader.notification.empty': 'Você visualizou todas as notificações.',
+ 'component.globalHeader.message': 'Mensagem',
+ 'component.globalHeader.message.empty': 'Você visualizou todas as mensagens.',
+ 'component.globalHeader.event': 'Evento',
+ 'component.globalHeader.event.empty': 'Você visualizou todos os eventos.',
+ 'component.noticeIcon.clear': 'Limpar',
+ 'component.noticeIcon.cleared': 'Limpo',
+ 'component.noticeIcon.empty': 'Sem notificações',
+};
diff --git a/src/locales/pt-BR/login.js b/src/locales/pt-BR/login.js
new file mode 100644
index 00000000..447a0777
--- /dev/null
+++ b/src/locales/pt-BR/login.js
@@ -0,0 +1,33 @@
+export default {
+ 'app.login.message-invalid-credentials':
+ 'Nome de usuário ou senha inválidosd(admin/ant.design)',
+ 'app.login.message-invalid-verification-code': 'Código de verificação inválido',
+ 'app.login.tab-login-credentials': 'Credenciais',
+ 'app.login.tab-login-mobile': 'Telefone',
+ 'app.login.remember-me': 'Lembre-me',
+ 'app.login.forgot-password': 'Esqueceu sua senha?',
+ 'app.login.sign-in-with': 'Login com',
+ 'app.login.signup': 'Cadastre-se',
+ 'app.login.login': 'Login',
+ 'app.register.register': 'Cadastro',
+ 'app.register.get-verification-code': 'Recuperar código',
+ 'app.register.sign-in': 'Já tem uma conta?',
+ 'app.register-result.msg': 'Conta:registrada em {email}',
+ 'app.register-result.activation-email':
+ 'Um email de ativação foi enviado para o seu email e é válido por 24 horas. Por favor entre no seu email e clique no link de ativação da conta.',
+ 'app.register-result.back-home': 'Voltar ao Início',
+ 'app.register-result.view-mailbox': 'Visualizar a caixa de email',
+ 'validation.email.required': 'Por favor insira seu email!',
+ 'validation.email.wrong-format': 'O email está errado!',
+ 'validation.password.required': 'Por favor insira sua senha!',
+ 'validation.password.twice': 'As senhas não estão iguais!',
+ 'validation.password.strength.msg':
+ 'Por favor insira pelo menos 6 caracteres e não use senhas fáceis de adivinhar.',
+ 'validation.password.strength.strong': 'Força: forte',
+ 'validation.password.strength.medium': 'Força: média',
+ 'validation.password.strength.short': 'Força: curta',
+ 'validation.confirm-password.required': 'Por favor confirme sua senha!',
+ 'validation.phone-number.required': 'Por favor insira seu telefone!',
+ 'validation.phone-number.wrong-format': 'Formato de telefone errado!',
+ 'validation.verification-code.required': 'Por favor insira seu código de verificação!',
+};
diff --git a/src/locales/pt-BR/menu.js b/src/locales/pt-BR/menu.js
new file mode 100644
index 00000000..257ab0c2
--- /dev/null
+++ b/src/locales/pt-BR/menu.js
@@ -0,0 +1,38 @@
+export default {
+ 'menu.home': 'Início',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': 'Análise',
+ 'menu.dashboard.monitor': 'Monitor',
+ 'menu.dashboard.workplace': 'Ambiente de Trabalho',
+ 'menu.form': 'Formulário',
+ 'menu.form.basicform': 'Formulário Básico',
+ 'menu.form.stepform': 'Formulário Assistido',
+ 'menu.form.stepform.info': 'Formulário Assistido(gravar informações de transferência)',
+ 'menu.form.stepform.confirm': 'Formulário Assistido(confirmar informações de transferência)',
+ 'menu.form.stepform.result': 'Formulário Assistido(finalizado)',
+ 'menu.form.advancedform': 'Formulário Avançado',
+ 'menu.list': 'Lista',
+ 'menu.list.searchtable': 'Tabela de Busca',
+ 'menu.list.basiclist': 'Lista Básica',
+ 'menu.list.cardlist': 'Lista de Card',
+ 'menu.list.searchlist': 'Lista de Busca',
+ 'menu.list.searchlist.articles': 'Lista de Busca(artigos)',
+ 'menu.list.searchlist.projects': 'Lista de Busca(projetos)',
+ 'menu.list.searchlist.applications': 'Lista de Busca(aplicações)',
+ 'menu.profile': 'Perfil',
+ 'menu.profile.basic': 'Perfil Básico',
+ 'menu.profile.advanced': 'Perfil Avançado',
+ 'menu.result': 'Resultado',
+ 'menu.result.success': 'Sucesso',
+ 'menu.result.fail': 'Falha',
+ 'menu.exception': 'Exceção',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': 'Disparar',
+ 'menu.account': 'Conta',
+ 'menu.account.center': 'Central da Conta',
+ 'menu.account.settings': 'Configurar Conta',
+ 'menu.account.trigger': 'Disparar Erro',
+ 'menu.account.logout': 'Sair',
+};
diff --git a/src/locales/pt-BR/monitor.js b/src/locales/pt-BR/monitor.js
new file mode 100644
index 00000000..572a79ed
--- /dev/null
+++ b/src/locales/pt-BR/monitor.js
@@ -0,0 +1,19 @@
+export default {
+ 'app.monitor.trading-activity': 'Real-Time Trading Activity',
+ 'app.monitor.total-transactions': 'Total transactions today',
+ 'app.monitor.sales-target': 'Sales target completion rate',
+ 'app.monitor.remaining-time': 'Remaining time of activity',
+ 'app.monitor.total-transactions-per-second': 'Total transactions per second',
+ 'app.monitor.activity-forecast': 'Activity forecast',
+ 'app.monitor.efficiency': 'Efficiency',
+ 'app.monitor.ratio': 'Ratio',
+ 'app.monitor.proportion-per-category': 'Proportion Per Category',
+ 'app.monitor.fast-food': 'Fast food',
+ 'app.monitor.western-food': 'Western food',
+ 'app.monitor.hot-pot': 'Hot pot',
+ 'app.monitor.waiting-for-implementation': 'Waiting for implementation',
+ 'app.monitor.popular-searches': 'Popular Searches',
+ 'app.monitor.resource-surplus': 'Resource Surplus',
+ 'app.monitor.fund-surplus': 'Fund Surplus',
+ 'app.exception.back': 'Back to home',
+};
diff --git a/src/locales/pt-BR/pwa.js b/src/locales/pt-BR/pwa.js
new file mode 100644
index 00000000..05cc7978
--- /dev/null
+++ b/src/locales/pt-BR/pwa.js
@@ -0,0 +1,7 @@
+export default {
+ 'app.pwa.offline': 'Você está offline agora',
+ 'app.pwa.serviceworker.updated': 'Novo conteúdo está disponível',
+ 'app.pwa.serviceworker.updated.hint':
+ 'Por favor, pressione o botão "Atualizar" para recarregar a página atual',
+ 'app.pwa.serviceworker.updated.ok': 'Atualizar',
+};
diff --git a/src/locales/pt-BR/result.js b/src/locales/pt-BR/result.js
new file mode 100644
index 00000000..2720e072
--- /dev/null
+++ b/src/locales/pt-BR/result.js
@@ -0,0 +1,28 @@
+export default {
+ 'app.result.error.title': 'A Submissão Falhou',
+ 'app.result.error.description':
+ 'Por favor, verifique e modifique as seguintes informações antes de reenviar.',
+ 'app.result.error.hint-title': 'O conteúdo que você enviou tem o seguinte erro:',
+ 'app.result.error.hint-text1': 'Sua conta foi congelada',
+ 'app.result.error.hint-btn1': 'Descongele imediatamente',
+ 'app.result.error.hint-text2': 'Sua conta ainda não está qualificada para se candidatar',
+ 'app.result.error.hint-btn2': 'Atualizar imediatamente',
+ 'app.result.error.btn-text': 'Retornar para modificar',
+ 'app.result.success.title': 'A Submissão foi um Sucesso',
+ 'app.result.success.description':
+ 'A página de resultados de envio é usada para fornecer os resultados de uma série de tarefas operacionais. Se for uma operação simples, use o prompt de feedback de Mensagem global. Esta área de texto pode mostrar uma explicação suplementar simples. Se houver um requisito semelhante para exibir "documentos", a área cinza a seguir pode apresentar um conteúdo mais complicado.',
+ 'app.result.success.operate-title': 'Nome do Projeto',
+ 'app.result.success.operate-id': 'ID do Projeto:',
+ 'app.result.success.principal': 'Principal:',
+ 'app.result.success.operate-time': 'Tempo efetivo:',
+ 'app.result.success.step1-title': 'Criar projeto',
+ 'app.result.success.step1-operator': 'Qu Lili',
+ 'app.result.success.step2-title': 'Revisão preliminar do departamento',
+ 'app.result.success.step2-operator': 'Zhou Maomao',
+ 'app.result.success.step2-extra': 'Urge',
+ 'app.result.success.step3-title': 'Revisão financeira',
+ 'app.result.success.step4-title': 'Terminar',
+ 'app.result.success.btn-return': 'Voltar a lista',
+ 'app.result.success.btn-project': 'Ver projeto',
+ 'app.result.success.btn-print': 'imprimir',
+};
diff --git a/src/locales/pt-BR/settingDrawer.js b/src/locales/pt-BR/settingDrawer.js
new file mode 100644
index 00000000..8a10b57e
--- /dev/null
+++ b/src/locales/pt-BR/settingDrawer.js
@@ -0,0 +1,32 @@
+export default {
+ 'app.setting.pagestyle': 'Configuração de estilo da página',
+ 'app.setting.pagestyle.dark': 'Dark style',
+ 'app.setting.pagestyle.light': 'Light style',
+ 'app.setting.content-width': 'Largura do conteúdo',
+ 'app.setting.content-width.fixed': 'Fixo',
+ 'app.setting.content-width.fluid': 'Fluido',
+ 'app.setting.themecolor': 'Cor do Tema',
+ 'app.setting.themecolor.dust': 'Dust Red',
+ 'app.setting.themecolor.volcano': 'Volcano',
+ 'app.setting.themecolor.sunset': 'Sunset Orange',
+ 'app.setting.themecolor.cyan': 'Cyan',
+ 'app.setting.themecolor.green': 'Polar Green',
+ 'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
+ 'app.setting.themecolor.geekblue': 'Geek Glue',
+ 'app.setting.themecolor.purple': 'Golden Purple',
+ 'app.setting.navigationmode': 'Modo de Navegação',
+ 'app.setting.sidemenu': 'Layout do Menu Lateral',
+ 'app.setting.topmenu': 'Layout do Menu Superior',
+ 'app.setting.fixedheader': 'Cabeçalho fixo',
+ 'app.setting.fixedsidebar': 'Barra lateral fixa',
+ 'app.setting.fixedsidebar.hint': 'Funciona no layout do menu lateral',
+ 'app.setting.hideheader': 'Esconder o cabeçalho quando rolar',
+ 'app.setting.hideheader.hint': 'Funciona quando o esconder cabeçalho está abilitado',
+ 'app.setting.othersettings': 'Outras configurações',
+ 'app.setting.weakmode': 'Weak Mode',
+ 'app.setting.copy': 'Copiar Configuração',
+ 'app.setting.copyinfo':
+ 'copiado com sucesso,por favor trocar o defaultSettings em src/models/setting.js',
+ 'app.setting.production.hint':
+ 'O painel de configuração apenas é exibido no ambiente de desenvolvimento, por favor modifique manualmente o',
+};
diff --git a/src/locales/pt-BR/settings.js b/src/locales/pt-BR/settings.js
new file mode 100644
index 00000000..aad2e387
--- /dev/null
+++ b/src/locales/pt-BR/settings.js
@@ -0,0 +1,60 @@
+export default {
+ 'app.settings.menuMap.basic': 'Configurações Básicas',
+ 'app.settings.menuMap.security': 'Configurações de Segurança',
+ 'app.settings.menuMap.binding': 'Vinculação de Conta',
+ 'app.settings.menuMap.notification': 'Mensagens de Notificação',
+ 'app.settings.basic.avatar': 'Avatar',
+ 'app.settings.basic.change-avatar': 'Alterar avatar',
+ 'app.settings.basic.email': 'Email',
+ 'app.settings.basic.email-message': 'Por favor insira seu email!',
+ 'app.settings.basic.nickname': 'Nome de usuário',
+ 'app.settings.basic.nickname-message': 'Por favor insira seu nome de usuário!',
+ 'app.settings.basic.profile': 'Perfil pessoal',
+ 'app.settings.basic.profile-message': 'Por favor insira seu perfil pessoal!',
+ 'app.settings.basic.profile-placeholder': 'Breve introdução sua',
+ 'app.settings.basic.country': 'País/Região',
+ 'app.settings.basic.country-message': 'Por favor insira país!',
+ 'app.settings.basic.geographic': 'Província, estado ou cidade',
+ 'app.settings.basic.geographic-message': 'Por favor insira suas informações geográficas!',
+ 'app.settings.basic.address': 'Endereço',
+ 'app.settings.basic.address-message': 'Por favor insira seu endereço!',
+ 'app.settings.basic.phone': 'Número de telefone',
+ 'app.settings.basic.phone-message': 'Por favor insira seu número de telefone!',
+ 'app.settings.basic.update': 'Atualizar Informações',
+ 'app.settings.security.strong': 'Forte',
+ 'app.settings.security.medium': 'Média',
+ 'app.settings.security.weak': 'Fraca',
+ 'app.settings.security.password': 'Senha da Conta',
+ 'app.settings.security.password-description': 'Força da senha',
+ 'app.settings.security.phone': 'Telefone de Seguraça',
+ 'app.settings.security.phone-description': 'Telefone vinculado',
+ 'app.settings.security.question': 'Pergunta de Segurança',
+ 'app.settings.security.question-description':
+ 'A pergunta de segurança não está definida e a política de segurança pode proteger efetivamente a segurança da conta',
+ 'app.settings.security.email': 'Email de Backup',
+ 'app.settings.security.email-description': 'Email vinculado',
+ 'app.settings.security.mfa': 'Dispositivo MFA',
+ 'app.settings.security.mfa-description':
+ 'O dispositivo MFA não vinculado, após a vinculação, pode ser confirmado duas vezes',
+ 'app.settings.security.modify': 'Modificar',
+ 'app.settings.security.set': 'Atribuir',
+ 'app.settings.security.bind': 'Vincular',
+ 'app.settings.binding.taobao': 'Vincular Taobao',
+ 'app.settings.binding.taobao-description': 'Atualmente não vinculado à conta Taobao',
+ 'app.settings.binding.alipay': 'Vincular Alipay',
+ 'app.settings.binding.alipay-description': 'Atualmente não vinculado à conta Alipay',
+ 'app.settings.binding.dingding': 'Vincular DingTalk',
+ 'app.settings.binding.dingding-description': 'Atualmente não vinculado à conta DingTalk',
+ 'app.settings.binding.bind': 'Vincular',
+ 'app.settings.notification.password': 'Senha da Conta',
+ 'app.settings.notification.password-description':
+ 'Mensagens de outros usuários serão notificadas na forma de uma estação de letra',
+ 'app.settings.notification.messages': 'Mensagens de Sistema',
+ 'app.settings.notification.messages-description':
+ 'Mensagens de sistema serão notificadas na forma de uma estação de letra',
+ 'app.settings.notification.todo': 'Notificação de To-do',
+ 'app.settings.notification.todo-description':
+ 'A lista de to-do será notificada na forma de uma estação de letra',
+ 'app.settings.open': 'Aberto',
+ 'app.settings.close': 'Fechado',
+};
diff --git a/src/locales/zh-CN.js b/src/locales/zh-CN.js
new file mode 100644
index 00000000..4330d60a
--- /dev/null
+++ b/src/locales/zh-CN.js
@@ -0,0 +1,33 @@
+import analysis from './zh-CN/analysis';
+import exception from './zh-CN/exception';
+import form from './zh-CN/form';
+import globalHeader from './zh-CN/globalHeader';
+import login from './zh-CN/login';
+import menu from './zh-CN/menu';
+import monitor from './zh-CN/monitor';
+import result from './zh-CN/result';
+import settingDrawer from './zh-CN/settingDrawer';
+import settings from './zh-CN/settings';
+import pwa from './zh-CN/pwa';
+
+export default {
+ 'navBar.lang': '语言',
+ 'layout.user.link.help': '帮助',
+ 'layout.user.link.privacy': '隐私',
+ 'layout.user.link.terms': '条款',
+ 'app.home.introduce': '介绍',
+ 'app.forms.basic.title': '基础表单',
+ 'app.forms.basic.description':
+ '表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。',
+ ...analysis,
+ ...exception,
+ ...form,
+ ...globalHeader,
+ ...login,
+ ...menu,
+ ...monitor,
+ ...result,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+};
diff --git a/src/locales/zh-CN/analysis.js b/src/locales/zh-CN/analysis.js
new file mode 100644
index 00000000..8ed17ed8
--- /dev/null
+++ b/src/locales/zh-CN/analysis.js
@@ -0,0 +1,34 @@
+export default {
+ 'app.analysis.test': '工专路 {no} 号店',
+ 'app.analysis.introduce': '指标说明',
+ 'app.analysis.total-sales': '总销售额',
+ 'app.analysis.day-sales': '日销售额',
+ 'app.analysis.visits': '访问量',
+ 'app.analysis.visits-trend': '访问量趋势',
+ 'app.analysis.visits-ranking': '门店访问量排名',
+ 'app.analysis.day-visits': '日访问量',
+ 'app.analysis.week': '周同比',
+ 'app.analysis.day': '日同比',
+ 'app.analysis.payments': '支付笔数',
+ 'app.analysis.conversion-rate': '转化率',
+ 'app.analysis.operational-effect': '运营活动效果',
+ 'app.analysis.sales-trend': '销售趋势',
+ 'app.analysis.sales-ranking': '门店销售额排名',
+ 'app.analysis.all-year': '全年',
+ 'app.analysis.all-month': '本月',
+ 'app.analysis.all-week': '本周',
+ 'app.analysis.all-day': '今日',
+ 'app.analysis.search-users': '搜索用户数',
+ 'app.analysis.per-capita-search': '人均搜索次数',
+ 'app.analysis.online-top-search': '线上热门搜索',
+ 'app.analysis.the-proportion-of-sales': '销售额类别占比',
+ 'app.analysis.channel.all': '全部渠道',
+ 'app.analysis.channel.online': '线上',
+ 'app.analysis.channel.stores': '门店',
+ 'app.analysis.sales': '销售额',
+ 'app.analysis.traffic': '客流量',
+ 'app.analysis.table.rank': '排名',
+ 'app.analysis.table.search-keyword': '搜索关键词',
+ 'app.analysis.table.users': '用户数',
+ 'app.analysis.table.weekly-range': '周涨幅',
+};
diff --git a/src/locales/zh-CN/exception.js b/src/locales/zh-CN/exception.js
new file mode 100644
index 00000000..6f645dae
--- /dev/null
+++ b/src/locales/zh-CN/exception.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.exception.back': '返回首页',
+ 'app.exception.description.403': '抱歉,你无权访问该页面',
+ 'app.exception.description.404': '抱歉,你访问的页面不存在',
+ 'app.exception.description.500': '抱歉,服务器出错了',
+};
diff --git a/src/locales/zh-CN/form.js b/src/locales/zh-CN/form.js
new file mode 100644
index 00000000..6de0406a
--- /dev/null
+++ b/src/locales/zh-CN/form.js
@@ -0,0 +1,37 @@
+export default {
+ 'form.captcha': '获取验证码',
+ 'form.captcha.second': '秒',
+ 'form.optional': '(选填)',
+ 'form.submit': '提交',
+ 'form.save': '保存',
+ 'form.email.placeholder': '邮箱',
+ 'form.password.placeholder': '至少6位密码,区分大小写',
+ 'form.confirm-password.placeholder': '确认密码',
+ 'form.phone-number.placeholder': '位手机号',
+ 'form.verification-code.placeholder': '验证码',
+ 'form.title.label': '标题',
+ 'form.title.placeholder': '给目标起个名字',
+ 'form.date.label': '起止日期',
+ 'form.date.placeholder.start': '开始日期',
+ 'form.date.placeholder.end': '结束日期',
+ 'form.goal.label': '目标描述',
+ 'form.goal.placeholder': '请输入你的阶段性工作目标',
+ 'form.standard.label': '衡量标准',
+ 'form.standard.placeholder': '请输入衡量标准',
+ 'form.client.label': '客户',
+ 'form.client.label.tooltip': '目标的服务对象',
+ 'form.client.placeholder': '请描述你服务的客户,内部客户直接 @姓名/工号',
+ 'form.invites.label': '邀评人',
+ 'form.invites.placeholder': '请直接 @姓名/工号,最多可邀请 5 人',
+ 'form.weight.label': '权重',
+ 'form.weight.placeholder': '请输入',
+ 'form.public.label': '目标公开',
+ 'form.public.label.help': '客户、邀评人默认被分享',
+ 'form.public.radio.public': '公开',
+ 'form.public.radio.partially-public': '部分公开',
+ 'form.public.radio.private': '不公开',
+ 'form.publicUsers.placeholder': '公开给',
+ 'form.publicUsers.option.A': '同事甲',
+ 'form.publicUsers.option.B': '同事乙',
+ 'form.publicUsers.option.C': '同事丙',
+};
diff --git a/src/locales/zh-CN/globalHeader.js b/src/locales/zh-CN/globalHeader.js
new file mode 100644
index 00000000..abd2d2a2
--- /dev/null
+++ b/src/locales/zh-CN/globalHeader.js
@@ -0,0 +1,16 @@
+export default {
+ 'component.globalHeader.search': '站内搜索',
+ 'component.globalHeader.search.example1': '搜索提示一',
+ 'component.globalHeader.search.example2': '搜索提示二',
+ 'component.globalHeader.search.example3': '搜索提示三',
+ 'component.globalHeader.help': '使用文档',
+ 'component.globalHeader.notification': '通知',
+ 'component.globalHeader.notification.empty': '你已查看所有通知',
+ 'component.globalHeader.message': '消息',
+ 'component.globalHeader.message.empty': '您已读完所有消息',
+ 'component.globalHeader.event': '待办',
+ 'component.globalHeader.event.empty': '你已完成所有待办',
+ 'component.noticeIcon.clear': '清空',
+ 'component.noticeIcon.cleared': '清空了',
+ 'component.noticeIcon.empty': '暂无数据',
+};
diff --git a/src/locales/zh-CN/login.js b/src/locales/zh-CN/login.js
new file mode 100644
index 00000000..ddcde450
--- /dev/null
+++ b/src/locales/zh-CN/login.js
@@ -0,0 +1,35 @@
+export default {
+ 'app.login.message-invalid-credentials': '账户或密码错误(admin/ant.design)',
+ 'app.login.message-invalid-verification-code': '验证码错误',
+ 'app.login.tab-login-credentials': '账户密码登录',
+ 'app.login.tab-login-mobile': '手机号登录',
+ 'app.login.remember-me': '自动登录',
+ 'app.login.forgot-password': '忘记密码',
+ 'app.login.sign-in-with': '其他登录方式',
+ 'app.login.signup': '注册账户',
+ 'app.login.login': '登录',
+ 'app.register.register': '注册',
+ 'app.register.get-verification-code': '获取验证码',
+ 'app.register.sign-in': '使用已有账户登录',
+ 'app.register-result.msg': '你的账户:{email} 注册成功',
+ 'app.register-result.activation-email':
+ '激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。',
+ 'app.register-result.back-home': '返回首页',
+ 'app.register-result.view-mailbox': '查看邮箱',
+ 'validation.email.required': '请输入邮箱地址!',
+ 'validation.email.wrong-format': '邮箱地址格式错误!',
+ 'validation.password.required': '请输入密码!',
+ 'validation.password.twice': '两次输入的密码不匹配!',
+ 'validation.password.strength.msg': '请至少输入 6 个字符。请不要使用容易被猜到的密码。',
+ 'validation.password.strength.strong': '强度:强',
+ 'validation.password.strength.medium': '强度:中',
+ 'validation.password.strength.short': '强度:太短',
+ 'validation.confirm-password.required': '请确认密码!',
+ 'validation.phone-number.required': '请输入手机号!',
+ 'validation.phone-number.wrong-format': '手机号格式错误!',
+ 'validation.verification-code.required': '请输入验证码!',
+ 'validation.title.required': '请输入标题',
+ 'validation.date.required': '请选择起止日期',
+ 'validation.goal.required': '请输入目标描述',
+ 'validation.standard.required': '请输入衡量标准',
+};
diff --git a/src/locales/zh-CN/menu.js b/src/locales/zh-CN/menu.js
new file mode 100644
index 00000000..1383b43f
--- /dev/null
+++ b/src/locales/zh-CN/menu.js
@@ -0,0 +1,38 @@
+export default {
+ 'menu.home': '首页',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': '分析页',
+ 'menu.dashboard.monitor': '监控页',
+ 'menu.dashboard.workplace': '工作台',
+ 'menu.form': '表单页',
+ 'menu.form.basicform': '基础表单',
+ 'menu.form.stepform': '分步表单',
+ 'menu.form.stepform.info': '分步表单(填写转账信息)',
+ 'menu.form.stepform.confirm': '分步表单(确认转账信息)',
+ 'menu.form.stepform.result': '分步表单(完成)',
+ 'menu.form.advancedform': '高级表单',
+ 'menu.list': '列表页',
+ 'menu.list.searchtable': '查询表格',
+ 'menu.list.basiclist': '标准列表',
+ 'menu.list.cardlist': '卡片列表',
+ 'menu.list.searchlist': '搜索列表',
+ 'menu.list.searchlist.articles': '搜索列表(文章)',
+ 'menu.list.searchlist.projects': '搜索列表(项目)',
+ 'menu.list.searchlist.applications': '搜索列表(应用)',
+ 'menu.profile': '详情页',
+ 'menu.profile.basic': '基础详情页',
+ 'menu.profile.advanced': '高级详情页',
+ 'menu.result': '结果页',
+ 'menu.result.success': '成功页',
+ 'menu.result.fail': '失败页',
+ 'menu.exception': '异常页',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': '触发错误',
+ 'menu.account': '个人页',
+ 'menu.account.center': '个人中心',
+ 'menu.account.settings': '个人设置',
+ 'menu.account.trigger': '触发报错',
+ 'menu.account.logout': '退出登录',
+};
diff --git a/src/locales/zh-CN/monitor.js b/src/locales/zh-CN/monitor.js
new file mode 100644
index 00000000..3a3e3f0b
--- /dev/null
+++ b/src/locales/zh-CN/monitor.js
@@ -0,0 +1,18 @@
+export default {
+ 'app.monitor.trading-activity': '活动实时交易情况',
+ 'app.monitor.total-transactions': '今日交易总额',
+ 'app.monitor.sales-target': '销售目标完成率',
+ 'app.monitor.remaining-time': '活动剩余时间',
+ 'app.monitor.total-transactions-per-second': '每秒交易总额',
+ 'app.monitor.activity-forecast': '活动情况预测',
+ 'app.monitor.efficiency': '券核效率',
+ 'app.monitor.ratio': '跳出率',
+ 'app.monitor.proportion-per-category': '各品类占比',
+ 'app.monitor.fast-food': '中式快餐',
+ 'app.monitor.western-food': '西餐',
+ 'app.monitor.hot-pot': '火锅',
+ 'app.monitor.waiting-for-implementation': 'Waiting for implementation',
+ 'app.monitor.popular-searches': '热门搜索',
+ 'app.monitor.resource-surplus': '资源剩余',
+ 'app.monitor.fund-surplus': '补贴资金剩余',
+};
diff --git a/src/locales/zh-CN/pwa.js b/src/locales/zh-CN/pwa.js
new file mode 100644
index 00000000..e9504849
--- /dev/null
+++ b/src/locales/zh-CN/pwa.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': '当前处于离线状态',
+ 'app.pwa.serviceworker.updated': '有新内容',
+ 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+ 'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/src/locales/zh-CN/result.js b/src/locales/zh-CN/result.js
new file mode 100644
index 00000000..cba0e1c1
--- /dev/null
+++ b/src/locales/zh-CN/result.js
@@ -0,0 +1,27 @@
+export default {
+ 'app.result.error.title': '提交失败',
+ 'app.result.error.description': '请核对并修改以下信息后,再重新提交。',
+ 'app.result.error.hint-title': '您提交的内容有如下错误:',
+ 'app.result.error.hint-text1': '您的账户已被冻结',
+ 'app.result.error.hint-btn1': '立即解冻',
+ 'app.result.error.hint-text2': '您的账户还不具备申请资格',
+ 'app.result.error.hint-btn2': '立即升级',
+ 'app.result.error.btn-text': '返回修改',
+ 'app.result.success.title': '提交成功',
+ 'app.result.success.description':
+ '提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。',
+ 'app.result.success.operate-title': '项目名称',
+ 'app.result.success.operate-id': '项目 ID:',
+ 'app.result.success.principal': '负责人:',
+ 'app.result.success.operate-time': '生效时间:',
+ 'app.result.success.step1-title': '创建项目',
+ 'app.result.success.step1-operator': '曲丽丽',
+ 'app.result.success.step2-title': '部门初审',
+ 'app.result.success.step2-operator': '周毛毛',
+ 'app.result.success.step2-extra': '催一下',
+ 'app.result.success.step3-title': '财务复核',
+ 'app.result.success.step4-title': '完成',
+ 'app.result.success.btn-return': '返回列表',
+ 'app.result.success.btn-project': '查看项目',
+ 'app.result.success.btn-print': '打印',
+};
diff --git a/src/locales/zh-CN/settingDrawer.js b/src/locales/zh-CN/settingDrawer.js
new file mode 100644
index 00000000..15685a40
--- /dev/null
+++ b/src/locales/zh-CN/settingDrawer.js
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': '整体风格设置',
+ 'app.setting.pagestyle.dark': '暗色菜单风格',
+ 'app.setting.pagestyle.light': '亮色菜单风格',
+ 'app.setting.content-width': '内容区域宽度',
+ 'app.setting.content-width.fixed': '定宽',
+ 'app.setting.content-width.fluid': '流式',
+ 'app.setting.themecolor': '主题色',
+ 'app.setting.themecolor.dust': '薄暮',
+ 'app.setting.themecolor.volcano': '火山',
+ 'app.setting.themecolor.sunset': '日暮',
+ 'app.setting.themecolor.cyan': '明青',
+ 'app.setting.themecolor.green': '极光绿',
+ 'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
+ 'app.setting.themecolor.geekblue': '极客蓝',
+ 'app.setting.themecolor.purple': '酱紫',
+ 'app.setting.navigationmode': '导航模式',
+ 'app.setting.sidemenu': '侧边菜单布局',
+ 'app.setting.topmenu': '顶部菜单布局',
+ 'app.setting.fixedheader': '固定 Header',
+ 'app.setting.fixedsidebar': '固定侧边菜单',
+ 'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
+ 'app.setting.hideheader': '下滑时隐藏 Header',
+ 'app.setting.hideheader.hint': '固定 Header 时可配置',
+ 'app.setting.othersettings': '其他设置',
+ 'app.setting.weakmode': '色弱模式',
+ 'app.setting.copy': '拷贝设置',
+ 'app.setting.copyinfo': '拷贝成功,请到 src/defaultSettings.js 中替换默认配置',
+ 'app.setting.production.hint':
+ '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
+};
diff --git a/src/locales/zh-CN/settings.js b/src/locales/zh-CN/settings.js
new file mode 100644
index 00000000..45542201
--- /dev/null
+++ b/src/locales/zh-CN/settings.js
@@ -0,0 +1,55 @@
+export default {
+ 'app.settings.menuMap.basic': '基本设置',
+ 'app.settings.menuMap.security': '安全设置',
+ 'app.settings.menuMap.binding': '账号绑定',
+ 'app.settings.menuMap.notification': '新消息通知',
+ 'app.settings.basic.avatar': '头像',
+ 'app.settings.basic.change-avatar': '更换头像',
+ 'app.settings.basic.email': '邮箱',
+ 'app.settings.basic.email-message': '请输入您的邮箱!',
+ 'app.settings.basic.nickname': '昵称',
+ 'app.settings.basic.nickname-message': '请输入您的昵称!',
+ 'app.settings.basic.profile': '个人简介',
+ 'app.settings.basic.profile-message': '请输入个人简介!',
+ 'app.settings.basic.profile-placeholder': '个人简介',
+ 'app.settings.basic.country': '国家/地区',
+ 'app.settings.basic.country-message': '请输入您的国家或地区!',
+ 'app.settings.basic.geographic': '所在省市',
+ 'app.settings.basic.geographic-message': '请输入您的所在省市!',
+ 'app.settings.basic.address': '街道地址',
+ 'app.settings.basic.address-message': '请输入您的街道地址!',
+ 'app.settings.basic.phone': '联系电话',
+ 'app.settings.basic.phone-message': '请输入您的联系电话!',
+ 'app.settings.basic.update': '更新基本信息',
+ 'app.settings.security.strong': '强',
+ 'app.settings.security.medium': '中',
+ 'app.settings.security.weak': '弱',
+ 'app.settings.security.password': '账户密码',
+ 'app.settings.security.password-description': '当前密码强度:',
+ 'app.settings.security.phone': '密保手机',
+ 'app.settings.security.phone-description': '已绑定手机:',
+ 'app.settings.security.question': '密保问题',
+ 'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
+ 'app.settings.security.email': '备用邮箱',
+ 'app.settings.security.email-description': '已绑定邮箱:',
+ 'app.settings.security.mfa': 'MFA 设备',
+ 'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
+ 'app.settings.security.modify': '修改',
+ 'app.settings.security.set': '设置',
+ 'app.settings.security.bind': '绑定',
+ 'app.settings.binding.taobao': '绑定淘宝',
+ 'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
+ 'app.settings.binding.alipay': '绑定支付宝',
+ 'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
+ 'app.settings.binding.dingding': '绑定钉钉',
+ 'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
+ 'app.settings.binding.bind': '绑定',
+ 'app.settings.notification.password': '账户密码',
+ 'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
+ 'app.settings.notification.messages': '系统消息',
+ 'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
+ 'app.settings.notification.todo': '账户密码',
+ 'app.settings.notification.todo-description': '账户密码',
+ 'app.settings.open': '开',
+ 'app.settings.close': '关',
+};
diff --git a/src/locales/zh-TW.js b/src/locales/zh-TW.js
new file mode 100644
index 00000000..986820ef
--- /dev/null
+++ b/src/locales/zh-TW.js
@@ -0,0 +1,33 @@
+import analysis from './zh-TW/analysis';
+import exception from './zh-TW/exception';
+import form from './zh-TW/form';
+import globalHeader from './zh-TW/globalHeader';
+import login from './zh-TW/login';
+import menu from './zh-TW/menu';
+import monitor from './zh-TW/monitor';
+import result from './zh-TW/result';
+import settingDrawer from './zh-TW/settingDrawer';
+import settings from './zh-TW/settings';
+import pwa from './zh-TW/pwa';
+
+export default {
+ 'navBar.lang': '語言',
+ 'layout.user.link.help': '幫助',
+ 'layout.user.link.privacy': '隱私',
+ 'layout.user.link.terms': '條款',
+ 'app.home.introduce': '介紹',
+ 'app.forms.basic.title': '基礎表單',
+ 'app.forms.basic.description':
+ '表單頁用於向用戶收集或驗證信息,基礎表單常見於數據項較少的表單場景。',
+ ...analysis,
+ ...exception,
+ ...form,
+ ...globalHeader,
+ ...login,
+ ...menu,
+ ...monitor,
+ ...result,
+ ...settingDrawer,
+ ...settings,
+ ...pwa,
+};
diff --git a/src/locales/zh-TW/analysis.js b/src/locales/zh-TW/analysis.js
new file mode 100644
index 00000000..7b2e37cf
--- /dev/null
+++ b/src/locales/zh-TW/analysis.js
@@ -0,0 +1,34 @@
+export default {
+ 'app.analysis.test': '工專路 {no} 號店',
+ 'app.analysis.introduce': '指標說明',
+ 'app.analysis.total-sales': '總銷售額',
+ 'app.analysis.day-sales': '日銷售額',
+ 'app.analysis.visits': '訪問量',
+ 'app.analysis.visits-trend': '訪問量趨勢',
+ 'app.analysis.visits-ranking': '門店訪問量排名',
+ 'app.analysis.day-visits': '日訪問量',
+ 'app.analysis.week': '周同比',
+ 'app.analysis.day': '日同比',
+ 'app.analysis.payments': '支付筆數',
+ 'app.analysis.conversion-rate': '轉化率',
+ 'app.analysis.operational-effect': '運營活動效果',
+ 'app.analysis.sales-trend': '銷售趨勢',
+ 'app.analysis.sales-ranking': '門店銷售額排名',
+ 'app.analysis.all-year': '全年',
+ 'app.analysis.all-month': '本月',
+ 'app.analysis.all-week': '本周',
+ 'app.analysis.all-day': '今日',
+ 'app.analysis.search-users': '搜索用戶數',
+ 'app.analysis.per-capita-search': '人均搜索次數',
+ 'app.analysis.online-top-search': '線上熱門搜索',
+ 'app.analysis.the-proportion-of-sales': '銷售額類別占比',
+ 'app.analysis.channel.all': '全部渠道',
+ 'app.analysis.channel.online': '線上',
+ 'app.analysis.channel.stores': '門店',
+ 'app.analysis.sales': '銷售額',
+ 'app.analysis.traffic': '客流量',
+ 'app.analysis.table.rank': '排名',
+ 'app.analysis.table.search-keyword': '搜索關鍵詞',
+ 'app.analysis.table.users': '用戶數',
+ 'app.analysis.table.weekly-range': '周漲幅',
+};
diff --git a/src/locales/zh-TW/exception.js b/src/locales/zh-TW/exception.js
new file mode 100644
index 00000000..24a26613
--- /dev/null
+++ b/src/locales/zh-TW/exception.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.exception.back': '返回首頁',
+ 'app.exception.description.403': '抱歉,妳無權訪問該頁面',
+ 'app.exception.description.404': '抱歉,妳訪問的頁面不存在',
+ 'app.exception.description.500': '抱歉,服務器出錯了',
+};
diff --git a/src/locales/zh-TW/form.js b/src/locales/zh-TW/form.js
new file mode 100644
index 00000000..82954dce
--- /dev/null
+++ b/src/locales/zh-TW/form.js
@@ -0,0 +1,37 @@
+export default {
+ 'form.captcha': '獲取驗證碼',
+ 'form.captcha.second': '秒',
+ 'form.optional': '(選填)',
+ 'form.submit': '提交',
+ 'form.save': '保存',
+ 'form.email.placeholder': '郵箱',
+ 'form.password.placeholder': '至少6位密碼,區分大小寫',
+ 'form.confirm-password.placeholder': '確認密碼',
+ 'form.phone-number.placeholder': '位手機號',
+ 'form.verification-code.placeholder': '驗證碼',
+ 'form.title.label': '標題',
+ 'form.title.placeholder': '給目標起個名字',
+ 'form.date.label': '起止日期',
+ 'form.date.placeholder.start': '開始日期',
+ 'form.date.placeholder.end': '結束日期',
+ 'form.goal.label': '目標描述',
+ 'form.goal.placeholder': '請輸入妳的階段性工作目標',
+ 'form.standard.label': '衡量標淮',
+ 'form.standard.placeholder': '請輸入衡量標淮',
+ 'form.client.label': '客戶',
+ 'form.client.label.tooltip': '目標的服務對象',
+ 'form.client.placeholder': '請描述妳服務的客戶,內部客戶直接 @姓名/工號',
+ 'form.invites.label': '邀評人',
+ 'form.invites.placeholder': '請直接 @姓名/工號,最多可邀請 5 人',
+ 'form.weight.label': '權重',
+ 'form.weight.placeholder': '請輸入',
+ 'form.public.label': '目標公開',
+ 'form.public.label.help': '客戶、邀評人默認被分享',
+ 'form.public.radio.public': '公開',
+ 'form.public.radio.partially-public': '部分公開',
+ 'form.public.radio.private': '不公開',
+ 'form.publicUsers.placeholder': '公開給',
+ 'form.publicUsers.option.A': '同事甲',
+ 'form.publicUsers.option.B': '同事乙',
+ 'form.publicUsers.option.C': '同事丙',
+};
diff --git a/src/locales/zh-TW/globalHeader.js b/src/locales/zh-TW/globalHeader.js
new file mode 100644
index 00000000..9166c4c8
--- /dev/null
+++ b/src/locales/zh-TW/globalHeader.js
@@ -0,0 +1,16 @@
+export default {
+ 'component.globalHeader.search': '站內搜索',
+ 'component.globalHeader.search.example1': '搜索提示壹',
+ 'component.globalHeader.search.example2': '搜索提示二',
+ 'component.globalHeader.search.example3': '搜索提示三',
+ 'component.globalHeader.help': '使用文檔',
+ 'component.globalHeader.notification': '通知',
+ 'component.globalHeader.notification.empty': '妳已查看所有通知',
+ 'component.globalHeader.message': '消息',
+ 'component.globalHeader.message.empty': '您已讀完所有消息',
+ 'component.globalHeader.event': '待辦',
+ 'component.globalHeader.event.empty': '妳已完成所有待辦',
+ 'component.noticeIcon.clear': '清空',
+ 'component.noticeIcon.cleared': '清空了',
+ 'component.noticeIcon.empty': '暫無數據',
+};
diff --git a/src/locales/zh-TW/login.js b/src/locales/zh-TW/login.js
new file mode 100644
index 00000000..0442d8e5
--- /dev/null
+++ b/src/locales/zh-TW/login.js
@@ -0,0 +1,35 @@
+export default {
+ 'app.login.message-invalid-credentials': '賬戶或密碼錯誤(admin/ant.design)',
+ 'app.login.message-invalid-verification-code': '驗證碼錯誤',
+ 'app.login.tab-login-credentials': '賬戶密碼登錄',
+ 'app.login.tab-login-mobile': '手機號登錄',
+ 'app.login.remember-me': '自動登錄',
+ 'app.login.forgot-password': '忘記密碼',
+ 'app.login.sign-in-with': '其他登錄方式',
+ 'app.login.signup': '註冊賬戶',
+ 'app.login.login': '登錄',
+ 'app.register.register': '註冊',
+ 'app.register.get-verification-code': '獲取驗證碼',
+ 'app.register.sign-in': '使用已有賬戶登錄',
+ 'app.register-result.msg': '妳的賬戶:{email} 註冊成功',
+ 'app.register-result.activation-email':
+ '激活郵件已發送到妳的郵箱中,郵件有效期為24小時。請及時登錄郵箱,點擊郵件中的鏈接激活帳戶。',
+ 'app.register-result.back-home': '返回首頁',
+ 'app.register-result.view-mailbox': '查看郵箱',
+ 'validation.email.required': '請輸入郵箱地址!',
+ 'validation.email.wrong-format': '郵箱地址格式錯誤!',
+ 'validation.password.required': '請輸入密碼!',
+ 'validation.password.twice': '兩次輸入的密碼不匹配!',
+ 'validation.password.strength.msg': '請至少輸入 6 個字符。請不要使用容易被猜到的密碼。',
+ 'validation.password.strength.strong': '強度:強',
+ 'validation.password.strength.medium': '強度:中',
+ 'validation.password.strength.short': '強度:太短',
+ 'validation.confirm-password.required': '請確認密碼!',
+ 'validation.phone-number.required': '請輸入手機號!',
+ 'validation.phone-number.wrong-format': '手機號格式錯誤!',
+ 'validation.verification-code.required': '請輸入驗證碼!',
+ 'validation.title.required': '請輸入標題',
+ 'validation.date.required': '請選擇起止日期',
+ 'validation.goal.required': '請輸入目標描述',
+ 'validation.standard.required': '請輸入衡量標淮',
+};
diff --git a/src/locales/zh-TW/menu.js b/src/locales/zh-TW/menu.js
new file mode 100644
index 00000000..66831e0b
--- /dev/null
+++ b/src/locales/zh-TW/menu.js
@@ -0,0 +1,38 @@
+export default {
+ 'menu.home': '首頁',
+ 'menu.dashboard': 'Dashboard',
+ 'menu.dashboard.analysis': '分析頁',
+ 'menu.dashboard.monitor': '監控頁',
+ 'menu.dashboard.workplace': '工作臺',
+ 'menu.form': '表單頁',
+ 'menu.form.basicform': '基礎表單',
+ 'menu.form.stepform': '分步表單',
+ 'menu.form.stepform.info': '分步表單(填寫轉賬信息)',
+ 'menu.form.stepform.confirm': '分步表單(確認轉賬信息)',
+ 'menu.form.stepform.result': '分步表單(完成)',
+ 'menu.form.advancedform': '高級表單',
+ 'menu.list': '列表頁',
+ 'menu.list.searchtable': '查詢表格',
+ 'menu.list.basiclist': '標淮列表',
+ 'menu.list.cardlist': '卡片列表',
+ 'menu.list.searchlist': '搜索列表',
+ 'menu.list.searchlist.articles': '搜索列表(文章)',
+ 'menu.list.searchlist.projects': '搜索列表(項目)',
+ 'menu.list.searchlist.applications': '搜索列表(應用)',
+ 'menu.profile': '詳情頁',
+ 'menu.profile.basic': '基礎詳情頁',
+ 'menu.profile.advanced': '高級詳情頁',
+ 'menu.result': '結果頁',
+ 'menu.result.success': '成功頁',
+ 'menu.result.fail': '失敗頁',
+ 'menu.account': '個人頁',
+ 'menu.account.center': '個人中心',
+ 'menu.account.settings': '個人設置',
+ 'menu.account.trigger': '觸發報錯',
+ 'menu.account.logout': '退出登錄',
+ 'menu.exception': '异常页',
+ 'menu.exception.not-permission': '403',
+ 'menu.exception.not-find': '404',
+ 'menu.exception.server-error': '500',
+ 'menu.exception.trigger': '触发错误',
+};
diff --git a/src/locales/zh-TW/monitor.js b/src/locales/zh-TW/monitor.js
new file mode 100644
index 00000000..d19ac054
--- /dev/null
+++ b/src/locales/zh-TW/monitor.js
@@ -0,0 +1,18 @@
+export default {
+ 'app.monitor.trading-activity': '活動實時交易情況',
+ 'app.monitor.total-transactions': '今日交易總額',
+ 'app.monitor.sales-target': '銷售目標完成率',
+ 'app.monitor.remaining-time': '活動剩余時間',
+ 'app.monitor.total-transactions-per-second': '每秒交易總額',
+ 'app.monitor.activity-forecast': '活動情況預測',
+ 'app.monitor.efficiency': '券核效率',
+ 'app.monitor.ratio': '跳出率',
+ 'app.monitor.proportion-per-category': '各品類占比',
+ 'app.monitor.fast-food': '中式快餐',
+ 'app.monitor.western-food': '西餐',
+ 'app.monitor.hot-pot': '火鍋',
+ 'app.monitor.waiting-for-implementation': 'Waiting for implementation',
+ 'app.monitor.popular-searches': '熱門搜索',
+ 'app.monitor.resource-surplus': '資源剩余',
+ 'app.monitor.fund-surplus': '補貼資金剩余',
+};
diff --git a/src/locales/zh-TW/pwa.js b/src/locales/zh-TW/pwa.js
new file mode 100644
index 00000000..108a6e48
--- /dev/null
+++ b/src/locales/zh-TW/pwa.js
@@ -0,0 +1,6 @@
+export default {
+ 'app.pwa.offline': '當前處於離線狀態',
+ 'app.pwa.serviceworker.updated': '有新內容',
+ 'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
+ 'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/src/locales/zh-TW/result.js b/src/locales/zh-TW/result.js
new file mode 100644
index 00000000..a87b96e5
--- /dev/null
+++ b/src/locales/zh-TW/result.js
@@ -0,0 +1,27 @@
+export default {
+ 'app.result.error.title': '提交失敗',
+ 'app.result.error.description': '請核對並修改以下信息後,再重新提交。',
+ 'app.result.error.hint-title': '您提交的內容有如下錯誤:',
+ 'app.result.error.hint-text1': '您的賬戶已被凍結',
+ 'app.result.error.hint-btn1': '立即解凍',
+ 'app.result.error.hint-text2': '您的賬戶還不具備申請資格',
+ 'app.result.error.hint-btn2': '立即升級',
+ 'app.result.error.btn-text': '返回修改',
+ 'app.result.success.title': '提交成功',
+ 'app.result.success.description':
+ '提交結果頁用於反饋壹系列操作任務的處理結果, 如果僅是簡單操作,使用 Message 全局提示反饋即可。 本文字區域可以展示簡單的補充說明,如果有類似展示 “單據”的需求,下面這個灰色區域可以呈現比較復雜的內容。',
+ 'app.result.success.operate-title': '項目名稱',
+ 'app.result.success.operate-id': '項目 ID:',
+ 'app.result.success.principal': '負責人:',
+ 'app.result.success.operate-time': '生效時間:',
+ 'app.result.success.step1-title': '創建項目',
+ 'app.result.success.step1-operator': '曲麗麗',
+ 'app.result.success.step2-title': '部門初審',
+ 'app.result.success.step2-operator': '周毛毛',
+ 'app.result.success.step2-extra': '催壹下',
+ 'app.result.success.step3-title': '財務復核',
+ 'app.result.success.step4-title': '完成',
+ 'app.result.success.btn-return': '返回列表',
+ 'app.result.success.btn-project': '查看項目',
+ 'app.result.success.btn-print': '打印',
+};
diff --git a/src/locales/zh-TW/settingDrawer.js b/src/locales/zh-TW/settingDrawer.js
new file mode 100644
index 00000000..24dc281f
--- /dev/null
+++ b/src/locales/zh-TW/settingDrawer.js
@@ -0,0 +1,31 @@
+export default {
+ 'app.setting.pagestyle': '整體風格設置',
+ 'app.setting.pagestyle.dark': '暗色菜單風格',
+ 'app.setting.pagestyle.light': '亮色菜單風格',
+ 'app.setting.content-width': '內容區域寬度',
+ 'app.setting.content-width.fixed': '定寬',
+ 'app.setting.content-width.fluid': '流式',
+ 'app.setting.themecolor': '主題色',
+ 'app.setting.themecolor.dust': '薄暮',
+ 'app.setting.themecolor.volcano': '火山',
+ 'app.setting.themecolor.sunset': '日暮',
+ 'app.setting.themecolor.cyan': '明青',
+ 'app.setting.themecolor.green': '極光綠',
+ 'app.setting.themecolor.daybreak': '拂曉藍(默認)',
+ 'app.setting.themecolor.geekblue': '極客藍',
+ 'app.setting.themecolor.purple': '醬紫',
+ 'app.setting.navigationmode': '導航模式',
+ 'app.setting.sidemenu': '側邊菜單布局',
+ 'app.setting.topmenu': '頂部菜單布局',
+ 'app.setting.fixedheader': '固定 Header',
+ 'app.setting.fixedsidebar': '固定側邊菜單',
+ 'app.setting.fixedsidebar.hint': '側邊菜單布局時可配置',
+ 'app.setting.hideheader': '下滑時隱藏 Header',
+ 'app.setting.hideheader.hint': '固定 Header 時可配置',
+ 'app.setting.othersettings': '其他設置',
+ 'app.setting.weakmode': '色弱模式',
+ 'app.setting.copy': '拷貝設置',
+ 'app.setting.copyinfo': '拷貝成功,請到 src/defaultSettings.js 中替換默認配置',
+ 'app.setting.production.hint':
+ '配置欄只在開發環境用於預覽,生產環境不會展現,請拷貝後手動修改配置文件',
+};
diff --git a/src/locales/zh-TW/settings.js b/src/locales/zh-TW/settings.js
new file mode 100644
index 00000000..797878bb
--- /dev/null
+++ b/src/locales/zh-TW/settings.js
@@ -0,0 +1,55 @@
+export default {
+ 'app.settings.menuMap.basic': '基本設置',
+ 'app.settings.menuMap.security': '安全設置',
+ 'app.settings.menuMap.binding': '賬號綁定',
+ 'app.settings.menuMap.notification': '新消息通知',
+ 'app.settings.basic.avatar': '頭像',
+ 'app.settings.basic.change-avatar': '更換頭像',
+ 'app.settings.basic.email': '郵箱',
+ 'app.settings.basic.email-message': '請輸入您的郵箱!',
+ 'app.settings.basic.nickname': '昵稱',
+ 'app.settings.basic.nickname-message': '請輸入您的昵稱!',
+ 'app.settings.basic.profile': '個人簡介',
+ 'app.settings.basic.profile-message': '請輸入個人簡介!',
+ 'app.settings.basic.profile-placeholder': '個人簡介',
+ 'app.settings.basic.country': '國家/地區',
+ 'app.settings.basic.country-message': '請輸入您的國家或地區!',
+ 'app.settings.basic.geographic': '所在省市',
+ 'app.settings.basic.geographic-message': '請輸入您的所在省市!',
+ 'app.settings.basic.address': '街道地址',
+ 'app.settings.basic.address-message': '請輸入您的街道地址!',
+ 'app.settings.basic.phone': '聯系電話',
+ 'app.settings.basic.phone-message': '請輸入您的聯系電話!',
+ 'app.settings.basic.update': '更新基本信息',
+ 'app.settings.security.strong': '強',
+ 'app.settings.security.medium': '中',
+ 'app.settings.security.weak': '弱',
+ 'app.settings.security.password': '賬戶密碼',
+ 'app.settings.security.password-description': '當前密碼強度:',
+ 'app.settings.security.phone': '密保手機',
+ 'app.settings.security.phone-description': '已綁定手機:',
+ 'app.settings.security.question': '密保問題',
+ 'app.settings.security.question-description': '未設置密保問題,密保問題可有效保護賬戶安全',
+ 'app.settings.security.email': '備用郵箱',
+ 'app.settings.security.email-description': '已綁定郵箱:',
+ 'app.settings.security.mfa': 'MFA 設備',
+ 'app.settings.security.mfa-description': '未綁定 MFA 設備,綁定後,可以進行二次確認',
+ 'app.settings.security.modify': '修改',
+ 'app.settings.security.set': '設置',
+ 'app.settings.security.bind': '綁定',
+ 'app.settings.binding.taobao': '綁定淘寶',
+ 'app.settings.binding.taobao-description': '當前未綁定淘寶賬號',
+ 'app.settings.binding.alipay': '綁定支付寶',
+ 'app.settings.binding.alipay-description': '當前未綁定支付寶賬號',
+ 'app.settings.binding.dingding': '綁定釘釘',
+ 'app.settings.binding.dingding-description': '當前未綁定釘釘賬號',
+ 'app.settings.binding.bind': '綁定',
+ 'app.settings.notification.password': '賬戶密碼',
+ 'app.settings.notification.password-description': '其他用戶的消息將以站內信的形式通知',
+ 'app.settings.notification.messages': '系統消息',
+ 'app.settings.notification.messages-description': '系統消息將以站內信的形式通知',
+ 'app.settings.notification.todo': '賬戶密碼',
+ 'app.settings.notification.todo-description': '賬戶密碼',
+ 'app.settings.open': '開',
+ 'app.settings.close': '關',
+};
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 00000000..69bce982
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "ant-design-pro",
+ "short_name": "antd-pro",
+ "display": "standalone",
+ "start_url": "./?utm_source=homescreen",
+ "theme_color": "#002140",
+ "background_color": "#001529",
+ "icons": [
+ {
+ "src": "icons/icon-192x192.png",
+ "sizes": "192x192"
+ },{
+ "src": "icons/icon-128x128.png",
+ "sizes": "128x128"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/models/error.js b/src/models/error.js
deleted file mode 100644
index 005c5222..00000000
--- a/src/models/error.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import { query403, query401, query404, query500 } from '../services/error';
-import delay from 'oni-delay';
-export default {
- namespace: 'error',
-
- state: {
- error: '',
- isloading: false,
- },
-
- effects: {
- *query403(_, { call, put }) {
- yield call(query403);
- yield put({
- type: 'trigger',
- payload: '403',
- });
- },
- *query401(_, { call, put }) {
- yield call(query401);
- yield put({
- type: 'trigger',
- payload: '401',
- });
- },
- *query500(_, { call, put }) {
- yield call(query500);
- yield put({
- type: 'trigger',
- payload: '500',
- });
- },
- *query404(_, { call, put }) {
- yield call(query404);
- yield put({
- type: 'trigger',
- payload: '404',
- });
- },
- *throwError() {
- yield delay(0);
- throw new Error('hi error');
- },
- },
-
- reducers: {
- trigger(state, action) {
- return {
- error: action.payload,
- };
- },
- },
-};
diff --git a/src/models/global.js b/src/models/global.js
index db266446..34cff599 100644
--- a/src/models/global.js
+++ b/src/models/global.js
@@ -1,6 +1,4 @@
-import { queryNotices } from '../services/api';
-import { getAuthority } from 'utils/authority';
-import { routerRedux } from 'dva/router';
+import { queryNotices } from '@/services/api';
export default {
namespace: 'global',
@@ -11,15 +9,21 @@ export default {
},
effects: {
- *fetchNotices(_, { call, put }) {
+ *fetchNotices(_, { call, put, select }) {
const data = yield call(queryNotices);
yield put({
type: 'saveNotices',
payload: data,
});
+ const unreadCount = yield select(
+ state => state.global.notices.filter(item => !item.read).length
+ );
yield put({
type: 'user/changeNotifyCount',
- payload: data.length,
+ payload: {
+ totalCount: data.length,
+ unreadCount,
+ },
});
},
*clearNotices({ payload }, { put, select }) {
@@ -28,19 +32,39 @@ export default {
payload,
});
const count = yield select(state => state.global.notices.length);
+ const unreadCount = yield select(
+ state => state.global.notices.filter(item => !item.read).length
+ );
yield put({
type: 'user/changeNotifyCount',
- payload: count,
+ payload: {
+ totalCount: count,
+ unreadCount,
+ },
+ });
+ },
+ *changeNoticeReadState({ payload }, { put, select }) {
+ const notices = yield select(state =>
+ state.global.notices.map(item => {
+ const notice = { ...item };
+ if (notice.id === payload) {
+ notice.read = true;
+ }
+ return notice;
+ })
+ );
+ yield put({
+ type: 'saveNotices',
+ payload: notices,
+ });
+ yield put({
+ type: 'user/changeNotifyCount',
+ payload: {
+ totalCount: notices.length,
+ unreadCount: notices.filter(item => !item.read).length,
+ },
});
},
- *init({payload},{put}){
- const author = getAuthority();
- if(!author){
- yield put(routerRedux.push('/User/Login'))
- }else{
- yield put(routerRedux.push('/Dashboard/Analysis'))
- }
- }
},
reducers: {
@@ -65,17 +89,12 @@ export default {
},
subscriptions: {
- setup({ history,dispatch }) {
+ setup({ history }) {
// Subscribe history(url) change, trigger `load` action if pathname is `/`
return history.listen(({ pathname, search }) => {
if (typeof window.ga !== 'undefined') {
window.ga('send', 'pageview', pathname + search);
}
- if(pathname==='/'){
- dispatch({
- type:"init"
- })
- }
});
},
},
diff --git a/src/models/list.js b/src/models/list.js
index 147eeca6..4758edaa 100644
--- a/src/models/list.js
+++ b/src/models/list.js
@@ -1,4 +1,4 @@
-import { queryFakeList } from '../services/api';
+import { queryFakeList, removeFakeList, addFakeList, updateFakeList } from '@/services/api';
export default {
namespace: 'list',
@@ -22,6 +22,19 @@ export default {
payload: Array.isArray(response) ? response : [],
});
},
+ *submit({ payload }, { call, put }) {
+ let callback;
+ if (payload.id) {
+ callback = Object.keys(payload).length === 1 ? removeFakeList : updateFakeList;
+ } else {
+ callback = addFakeList;
+ }
+ const response = yield call(callback, payload); // post
+ yield put({
+ type: 'queryList',
+ payload: response,
+ });
+ },
},
reducers: {
diff --git a/src/models/login.js b/src/models/login.js
index ae774ab9..82fc4249 100644
--- a/src/models/login.js
+++ b/src/models/login.js
@@ -1,7 +1,9 @@
import { routerRedux } from 'dva/router';
-import { fakeAccountLogin } from 'services/api';
-import { setAuthority } from 'utils/authority';
-import { reloadAuthorized } from 'utils/Authorized';
+import { stringify } from 'qs';
+import { fakeAccountLogin, getFakeCaptcha } from '@/services/api';
+import { setAuthority } from '@/utils/authority';
+import { getPageQuery } from '@/utils/utils';
+import { reloadAuthorized } from '@/utils/Authorized';
export default {
namespace: 'login',
@@ -20,29 +22,47 @@ export default {
// Login successfully
if (response.status === 'ok') {
reloadAuthorized();
- yield put(routerRedux.push('/'));
- }
- },
- *logout(_, { put, select }) {
- try {
- // get location pathname
const urlParams = new URL(window.location.href);
- const pathname = yield select(state => state.routing.location.pathname);
- // add the parameters in the url
- urlParams.searchParams.set('redirect', pathname);
- window.history.replaceState(null, 'login', urlParams.href);
- } finally {
- yield put({
- type: 'changeLoginStatus',
- payload: {
- status: false,
- currentAuthority: '',
- },
- });
- reloadAuthorized();
- yield put(routerRedux.push('/User/Login'));
+ const params = getPageQuery();
+ let { redirect } = params;
+ if (redirect) {
+ const redirectUrlParams = new URL(redirect);
+ if (redirectUrlParams.origin === urlParams.origin) {
+ redirect = redirect.substr(urlParams.origin.length);
+ if (redirect.match(/^\/.*#/)) {
+ redirect = redirect.substr(redirect.indexOf('#') + 1);
+ }
+ } else {
+ window.location.href = redirect;
+ return;
+ }
+ }
+ yield put(routerRedux.replace(redirect || '/'));
}
},
+
+ *getCaptcha({ payload }, { call }) {
+ yield call(getFakeCaptcha, payload);
+ },
+
+ *logout(_, { put }) {
+ yield put({
+ type: 'changeLoginStatus',
+ payload: {
+ status: false,
+ currentAuthority: 'guest',
+ },
+ });
+ reloadAuthorized();
+ yield put(
+ routerRedux.push({
+ pathname: '/user/login',
+ search: stringify({
+ redirect: window.location.href,
+ }),
+ })
+ );
+ },
},
reducers: {
diff --git a/src/models/menu.js b/src/models/menu.js
new file mode 100644
index 00000000..fa975375
--- /dev/null
+++ b/src/models/menu.js
@@ -0,0 +1,126 @@
+import memoizeOne from 'memoize-one';
+import isEqual from 'lodash/isEqual';
+import { formatMessage } from 'umi/locale';
+import Authorized from '@/utils/Authorized';
+import { queryMenu } from '@/services/user';
+
+const { check } = Authorized;
+
+// Conversion router to menu.
+function formatter(data, parentAuthority, parentName) {
+ return data
+ .map(item => {
+ if (!item.name || !item.path) {
+ return null;
+ }
+
+ let locale = 'menu';
+ if (parentName) {
+ locale = `${parentName}.${item.name}`;
+ } else {
+ locale = `menu.${item.name}`;
+ }
+
+ const result = {
+ ...item,
+ name: formatMessage({ id: locale, defaultMessage: item.name }),
+ locale,
+ authority: item.authority || parentAuthority,
+ };
+ if (item.routes) {
+ const children = formatter(item.routes, item.authority, locale);
+ // Reduce memory usage
+ result.children = children;
+ }
+ delete result.routes;
+ return result;
+ })
+ .filter(item => item);
+}
+
+const memoizeOneFormatter = memoizeOne(formatter, isEqual);
+
+/**
+ * get SubMenu or Item
+ */
+const getSubMenu = item => {
+ // doc: add hideChildrenInMenu
+ if (item.children && !item.hideChildrenInMenu && item.children.some(child => child.name)) {
+ return {
+ ...item,
+ children: filterMenuData(item.children), // eslint-disable-line
+ };
+ }
+ return item;
+};
+
+/**
+ * filter menuData
+ */
+const filterMenuData = menuData => {
+ if (!menuData) {
+ return [];
+ }
+ return menuData
+ .filter(item => item.name && !item.hideInMenu)
+ .map(item => {
+ // make dom
+ const ItemDom = getSubMenu(item);
+ const data = check(item.authority, ItemDom);
+ return data;
+ })
+ .filter(item => item);
+};
+/**
+ * 获取面包屑映射
+ * @param {Object} menuData 菜单配置
+ */
+const getBreadcrumbNameMap = menuData => {
+ const routerMap = {};
+
+ const flattenMenuData = data => {
+ data.forEach(menuItem => {
+ if (menuItem.children) {
+ flattenMenuData(menuItem.children);
+ }
+ // Reduce memory usage
+ routerMap[menuItem.path] = menuItem;
+ });
+ };
+ flattenMenuData(menuData);
+ return routerMap;
+};
+
+const memoizeOneGetBreadcrumbNameMap = memoizeOne(getBreadcrumbNameMap, isEqual);
+
+export default {
+ namespace: 'menu',
+
+ state: {
+ menuData: [],
+ breadcrumbNameMap: {},
+ },
+
+ effects: {
+ *getMenuData({ payload }, { put ,call}) {
+ const { routes, authority } = yield call(queryMenu);
+ console.log(routes, authority);
+
+ const menuData = filterMenuData(memoizeOneFormatter(routes, authority));
+ const breadcrumbNameMap = memoizeOneGetBreadcrumbNameMap(menuData);
+ yield put({
+ type: 'save',
+ payload: { menuData, breadcrumbNameMap },
+ });
+ },
+ },
+
+ reducers: {
+ save(state, action) {
+ return {
+ ...state,
+ ...action.payload,
+ };
+ },
+ },
+};
diff --git a/src/models/project.js b/src/models/project.js
index f68bef53..cf894125 100644
--- a/src/models/project.js
+++ b/src/models/project.js
@@ -1,4 +1,4 @@
-import { queryProjectNotice } from '../services/api';
+import { queryProjectNotice } from '@/services/api';
export default {
namespace: 'project',
diff --git a/src/models/setting.js b/src/models/setting.js
new file mode 100644
index 00000000..171da48d
--- /dev/null
+++ b/src/models/setting.js
@@ -0,0 +1,123 @@
+import { message } from 'antd';
+import defaultSettings from '../defaultSettings';
+
+let lessNodesAppended;
+const updateTheme = primaryColor => {
+ // Don't compile less in production!
+ if (APP_TYPE !== 'site') {
+ return;
+ }
+ // Determine if the component is remounted
+ if (!primaryColor) {
+ return;
+ }
+ const hideMessage = message.loading('正在编译主题!', 0);
+ function buildIt() {
+ if (!window.less) {
+ return;
+ }
+ setTimeout(() => {
+ window.less
+ .modifyVars({
+ '@primary-color': primaryColor,
+ })
+ .then(() => {
+ hideMessage();
+ })
+ .catch(() => {
+ message.error('Failed to update theme');
+ hideMessage();
+ });
+ }, 200);
+ }
+ if (!lessNodesAppended) {
+ // insert less.js and color.less
+ const lessStyleNode = document.createElement('link');
+ const lessConfigNode = document.createElement('script');
+ const lessScriptNode = document.createElement('script');
+ lessStyleNode.setAttribute('rel', 'stylesheet/less');
+ lessStyleNode.setAttribute('href', '/color.less');
+ lessConfigNode.innerHTML = `
+ window.less = {
+ async: true,
+ env: 'production',
+ javascriptEnabled: true
+ };
+ `;
+ lessScriptNode.src = 'https://gw.alipayobjects.com/os/lib/less.js/3.8.1/less.min.js';
+ lessScriptNode.async = true;
+ lessScriptNode.onload = () => {
+ buildIt();
+ lessScriptNode.onload = null;
+ };
+ document.body.appendChild(lessStyleNode);
+ document.body.appendChild(lessConfigNode);
+ document.body.appendChild(lessScriptNode);
+ lessNodesAppended = true;
+ } else {
+ buildIt();
+ }
+};
+
+const updateColorWeak = colorWeak => {
+ document.body.className = colorWeak ? 'colorWeak' : '';
+};
+
+export default {
+ namespace: 'setting',
+ state: defaultSettings,
+ reducers: {
+ getSetting(state) {
+ const setting = {};
+ const urlParams = new URL(window.location.href);
+ Object.keys(state).forEach(key => {
+ if (urlParams.searchParams.has(key)) {
+ const value = urlParams.searchParams.get(key);
+ setting[key] = value === '1' ? true : value;
+ }
+ });
+ const { primaryColor, colorWeak } = setting;
+ if (state.primaryColor !== primaryColor) {
+ updateTheme(primaryColor);
+ }
+ updateColorWeak(colorWeak);
+ return {
+ ...state,
+ ...setting,
+ };
+ },
+ changeSetting(state, { payload }) {
+ const urlParams = new URL(window.location.href);
+ Object.keys(defaultSettings).forEach(key => {
+ if (urlParams.searchParams.has(key)) {
+ urlParams.searchParams.delete(key);
+ }
+ });
+ Object.keys(payload).forEach(key => {
+ if (key === 'collapse') {
+ return;
+ }
+ let value = payload[key];
+ if (value === true) {
+ value = 1;
+ }
+ if (defaultSettings[key] !== value) {
+ urlParams.searchParams.set(key, value);
+ }
+ });
+ const { primaryColor, colorWeak, contentWidth } = payload;
+ if (state.primaryColor !== primaryColor) {
+ updateTheme(primaryColor);
+ }
+ if (state.contentWidth !== contentWidth && window.dispatchEvent) {
+ window.dispatchEvent(new Event('resize'));
+ }
+ updateColorWeak(colorWeak);
+ window.history.replaceState(null, 'setting', urlParams.href);
+ return {
+ ...state,
+ ...payload,
+ };
+ },
+ },
+};
diff --git a/src/models/user.js b/src/models/user.js
index b45afc3a..c84ebbcf 100644
--- a/src/models/user.js
+++ b/src/models/user.js
@@ -1,4 +1,4 @@
-import { query as queryUsers, queryCurrent } from '../services/user';
+import { query as queryUsers, queryCurrent } from '@/services/user';
export default {
namespace: 'user',
@@ -35,7 +35,7 @@ export default {
saveCurrentUser(state, action) {
return {
...state,
- currentUser: action.payload,
+ currentUser: action.payload || {},
};
},
changeNotifyCount(state, action) {
@@ -43,7 +43,8 @@ export default {
...state,
currentUser: {
...state.currentUser,
- notifyCount: action.payload,
+ notifyCount: action.payload.totalCount,
+ unreadCount: action.payload.unreadCount,
},
};
},
diff --git a/src/pages/404.js b/src/pages/404.js
index 020b321f..34921c02 100644
--- a/src/pages/404.js
+++ b/src/pages/404.js
@@ -1,3 +1,13 @@
+import React from 'react';
+import Link from 'umi/link';
+import { formatMessage } from 'umi/locale';
+import Exception from '@/components/Exception';
-import noPages from './Exception/404';
-export default noPages;
+export default () => (
+
+);
diff --git a/src/pages/Account/Center/Applications.js b/src/pages/Account/Center/Applications.js
new file mode 100644
index 00000000..20604d25
--- /dev/null
+++ b/src/pages/Account/Center/Applications.js
@@ -0,0 +1,88 @@
+import React, { PureComponent } from 'react';
+import { List, Card, Icon, Dropdown, Menu, Avatar, Tooltip } from 'antd';
+import numeral from 'numeral';
+import { connect } from 'dva';
+import { formatWan } from '@/utils/utils';
+import stylesApplications from '../../List/Search/Applications.less';
+
+@connect(({ list }) => ({
+ list,
+}))
+class Center extends PureComponent {
+ render() {
+ const {
+ list: { list },
+ } = this.props;
+ const itemMenu = (
+
+
+
+ 1st menu item
+
+
+
+
+ 2nd menu item
+
+
+
+
+ 3d menu item
+
+
+
+ );
+ const CardInfo = ({ activeUser, newUser }) => (
+
+ );
+ return (
+ (
+
+
+
+ ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
+ ]}
+ >
+ } title={item.title} />
+
+
+
+
+
+ )}
+ />
+ );
+ }
+}
+
+export default Center;
diff --git a/src/pages/Account/Center/Articles.js b/src/pages/Account/Center/Articles.js
new file mode 100644
index 00000000..9bb5ac30
--- /dev/null
+++ b/src/pages/Account/Center/Articles.js
@@ -0,0 +1,59 @@
+import React, { PureComponent } from 'react';
+import { List, Icon, Tag } from 'antd';
+import { connect } from 'dva';
+import ArticleListContent from '@/components/ArticleListContent';
+import styles from './Articles.less';
+
+@connect(({ list }) => ({
+ list,
+}))
+class Center extends PureComponent {
+ render() {
+ const {
+ list: { list },
+ } = this.props;
+ const IconText = ({ type, text }) => (
+
+
+ {text}
+
+ );
+ return (
+ (
+ ,
+ ,
+ ,
+ ]}
+ >
+
+ {item.title}
+
+ }
+ description={
+
+ Ant Design
+ 设计语言
+ 蚂蚁金服
+
+ }
+ />
+
+
+ )}
+ />
+ );
+ }
+}
+
+export default Center;
diff --git a/src/pages/Account/Center/Articles.less b/src/pages/Account/Center/Articles.less
new file mode 100644
index 00000000..2e51509b
--- /dev/null
+++ b/src/pages/Account/Center/Articles.less
@@ -0,0 +1,12 @@
+@import '~antd/lib/style/themes/default.less';
+
+.articleList {
+ :global {
+ .ant-list-item:first-child {
+ padding-top: 0;
+ }
+ }
+}
+a.listItemMetaTitle {
+ color: @heading-color;
+}
diff --git a/src/pages/Account/Center/Center.less b/src/pages/Account/Center/Center.less
new file mode 100644
index 00000000..37d6a203
--- /dev/null
+++ b/src/pages/Account/Center/Center.less
@@ -0,0 +1,97 @@
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
+
+.avatarHolder {
+ text-align: center;
+ margin-bottom: 24px;
+
+ & > img {
+ width: 104px;
+ height: 104px;
+ margin-bottom: 20px;
+ }
+
+ .name {
+ font-size: 20px;
+ line-height: 28px;
+ font-weight: 500;
+ color: @heading-color;
+ margin-bottom: 4px;
+ }
+}
+
+.detail {
+ p {
+ margin-bottom: 8px;
+ padding-left: 26px;
+ position: relative;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ i {
+ position: absolute;
+ height: 14px;
+ width: 14px;
+ left: 0;
+ top: 4px;
+ background: url(https://gw.alipayobjects.com/zos/rmsportal/pBjWzVAHnOOtAUvZmZfy.svg);
+
+ &.title {
+ background-position: 0 0;
+ }
+
+ &.group {
+ background-position: 0 -22px;
+ }
+
+ &.address {
+ background-position: 0 -44px;
+ }
+ }
+}
+
+.tagsTitle,
+.teamTitle {
+ font-weight: 500;
+ color: @heading-color;
+ margin-bottom: 12px;
+}
+
+.tags {
+ :global {
+ .ant-tag {
+ margin-bottom: 8px;
+ }
+ }
+}
+
+.team {
+ :global {
+ .ant-avatar {
+ margin-right: 12px;
+ }
+ }
+
+ a {
+ display: block;
+ margin-bottom: 24px;
+ color: @text-color;
+ transition: color 0.3s;
+ .textOverflow();
+
+ &:hover {
+ color: @primary-color;
+ }
+ }
+}
+
+.tabsCard {
+ :global {
+ .ant-card-head {
+ padding: 0 16px;
+ }
+ }
+}
diff --git a/src/pages/Account/Center/Projects.js b/src/pages/Account/Center/Projects.js
new file mode 100644
index 00000000..3526bb4e
--- /dev/null
+++ b/src/pages/Account/Center/Projects.js
@@ -0,0 +1,52 @@
+import React, { PureComponent } from 'react';
+import { List, Card } from 'antd';
+import moment from 'moment';
+import { connect } from 'dva';
+import AvatarList from '@/components/AvatarList';
+import stylesProjects from '../../List/Search/Projects.less';
+
+@connect(({ list }) => ({
+ list,
+}))
+class Center extends PureComponent {
+ render() {
+ const {
+ list: { list },
+ } = this.props;
+ return (
+ (
+
+ }
+ >
+ {item.title}} description={item.subDescription} />
+
+
{moment(item.updatedAt).fromNow()}
+
+
+ {item.members.map(member => (
+
+ ))}
+
+
+
+
+
+ )}
+ />
+ );
+ }
+}
+
+export default Center;
diff --git a/src/pages/Account/Center/_layout.js b/src/pages/Account/Center/_layout.js
new file mode 100644
index 00000000..fa81bd0d
--- /dev/null
+++ b/src/pages/Account/Center/_layout.js
@@ -0,0 +1,216 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import Link from 'umi/link';
+import router from 'umi/router';
+import { Card, Row, Col, Icon, Avatar, Tag, Divider, Spin, Input } from 'antd';
+import GridContent from '@/components/PageHeaderWrapper/GridContent';
+import styles from './Center.less';
+
+@connect(({ loading, user, project }) => ({
+ listLoading: loading.effects['list/fetch'],
+ currentUser: user.currentUser,
+ currentUserLoading: loading.effects['user/fetchCurrent'],
+ project,
+ projectLoading: loading.effects['project/fetchNotice'],
+}))
+class Center extends PureComponent {
+ state = {
+ newTags: [],
+ inputVisible: false,
+ inputValue: '',
+ };
+
+ componentDidMount() {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'user/fetchCurrent',
+ });
+ dispatch({
+ type: 'list/fetch',
+ payload: {
+ count: 8,
+ },
+ });
+ dispatch({
+ type: 'project/fetchNotice',
+ });
+ }
+
+ onTabChange = key => {
+ const { match } = this.props;
+ switch (key) {
+ case 'articles':
+ router.push(`${match.url}/articles`);
+ break;
+ case 'applications':
+ router.push(`${match.url}/applications`);
+ break;
+ case 'projects':
+ router.push(`${match.url}/projects`);
+ break;
+ default:
+ break;
+ }
+ };
+
+ showInput = () => {
+ this.setState({ inputVisible: true }, () => this.input.focus());
+ };
+
+ saveInputRef = input => {
+ this.input = input;
+ };
+
+ handleInputChange = e => {
+ this.setState({ inputValue: e.target.value });
+ };
+
+ handleInputConfirm = () => {
+ const { state } = this;
+ const { inputValue } = state;
+ let { newTags } = state;
+ if (inputValue && newTags.filter(tag => tag.label === inputValue).length === 0) {
+ newTags = [...newTags, { key: `new-${newTags.length}`, label: inputValue }];
+ }
+ this.setState({
+ newTags,
+ inputVisible: false,
+ inputValue: '',
+ });
+ };
+
+ render() {
+ const { newTags, inputVisible, inputValue } = this.state;
+ const {
+ listLoading,
+ currentUser,
+ currentUserLoading,
+ project: { notice },
+ projectLoading,
+ match,
+ location,
+ children,
+ } = this.props;
+
+ const operationTabList = [
+ {
+ key: 'articles',
+ tab: (
+
+ 文章 (8)
+
+ ),
+ },
+ {
+ key: 'applications',
+ tab: (
+
+ 应用 (8)
+
+ ),
+ },
+ {
+ key: 'projects',
+ tab: (
+
+ 项目 (8)
+
+ ),
+ },
+ ];
+
+ return (
+
+
+
+
+ {currentUser && Object.keys(currentUser).length ? (
+
+
+
+
{currentUser.name}
+
{currentUser.signature}
+
+
+
+
+ {currentUser.title}
+
+
+
+ {currentUser.group}
+
+
+
+ {currentUser.geographic.province.label}
+ {currentUser.geographic.city.label}
+
+
+
+
+
标签
+ {currentUser.tags.concat(newTags).map(item => (
+
{item.label}
+ ))}
+ {inputVisible && (
+
+ )}
+ {!inputVisible && (
+
+
+
+ )}
+
+
+
+
团队
+
+
+ {notice.map(item => (
+
+
+
+ {item.member}
+
+
+ ))}
+
+
+
+
+ ) : (
+ 'loading...'
+ )}
+
+
+
+
+ {children}
+
+
+
+
+ );
+ }
+}
+
+export default Center;
diff --git a/src/pages/Account/Settings/Base.js b/src/pages/Account/Settings/Base.js
new file mode 100644
index 00000000..e6b956f5
--- /dev/null
+++ b/src/pages/Account/Settings/Base.js
@@ -0,0 +1,192 @@
+import React, { Component, Fragment } from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import { Form, Input, Upload, Select, Button } from 'antd';
+import { connect } from 'dva';
+import styles from './BaseView.less';
+import GeographicView from './Geographic';
+import PhoneView from './Phone';
+// import { getTimeDistance } from '@/utils/utils';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+
+// 头像组件 方便以后独立,增加裁剪之类的功能
+const AvatarView = ({ avatar }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+const validatorGeographic = (rule, value, callback) => {
+ const { province, city } = value;
+ if (!province.key) {
+ callback('Please input your province!');
+ }
+ if (!city.key) {
+ callback('Please input your city!');
+ }
+ callback();
+};
+
+const validatorPhone = (rule, value, callback) => {
+ const values = value.split('-');
+ if (!values[0]) {
+ callback('Please input your area code!');
+ }
+ if (!values[1]) {
+ callback('Please input your phone number!');
+ }
+ callback();
+};
+
+@connect(({ user }) => ({
+ currentUser: user.currentUser,
+}))
+@Form.create()
+class BaseView extends Component {
+ componentDidMount() {
+ this.setBaseInfo();
+ }
+
+ setBaseInfo = () => {
+ const { currentUser, form } = this.props;
+ Object.keys(form.getFieldsValue()).forEach(key => {
+ const obj = {};
+ obj[key] = currentUser[key] || null;
+ form.setFieldsValue(obj);
+ });
+ };
+
+ getAvatarURL() {
+ const { currentUser } = this.props;
+ if (currentUser.avatar) {
+ return currentUser.avatar;
+ }
+ const url = 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png';
+ return url;
+ }
+
+ getViewDom = ref => {
+ this.view = ref;
+ };
+
+ render() {
+ const {
+ form: { getFieldDecorator },
+ } = this.props;
+ return (
+
+ );
+ }
+}
+
+export default BaseView;
diff --git a/src/pages/Account/Settings/BaseView.less b/src/pages/Account/Settings/BaseView.less
new file mode 100644
index 00000000..a0a2ffde
--- /dev/null
+++ b/src/pages/Account/Settings/BaseView.less
@@ -0,0 +1,52 @@
+@import '~antd/lib/style/themes/default.less';
+
+.baseView {
+ display: flex;
+ padding-top: 12px;
+
+ .left {
+ max-width: 448px;
+ min-width: 224px;
+ }
+ .right {
+ flex: 1;
+ padding-left: 104px;
+ .avatar_title {
+ height: 22px;
+ font-size: @font-size-base;
+ color: @heading-color;
+ line-height: 22px;
+ margin-bottom: 8px;
+ }
+ .avatar {
+ width: 144px;
+ height: 144px;
+ margin-bottom: 12px;
+ overflow: hidden;
+ img {
+ width: 100%;
+ }
+ }
+ .button_view {
+ width: 144px;
+ text-align: center;
+ }
+ }
+}
+
+@media screen and (max-width: @screen-xl) {
+ .baseView {
+ flex-direction: column-reverse;
+
+ .right {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ max-width: 448px;
+ .avatar_title {
+ display: none;
+ }
+ }
+ }
+}
diff --git a/src/pages/Account/Settings/Binding.js b/src/pages/Account/Settings/Binding.js
new file mode 100644
index 00000000..29a29890
--- /dev/null
+++ b/src/pages/Account/Settings/Binding.js
@@ -0,0 +1,60 @@
+import React, { Component, Fragment } from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import { Icon, List } from 'antd';
+
+class BindingView extends Component {
+ getData = () => [
+ {
+ title: formatMessage({ id: 'app.settings.binding.taobao' }, {}),
+ description: formatMessage({ id: 'app.settings.binding.taobao-description' }, {}),
+ actions: [
+
+
+ ,
+ ],
+ avatar: ,
+ },
+ {
+ title: formatMessage({ id: 'app.settings.binding.alipay' }, {}),
+ description: formatMessage({ id: 'app.settings.binding.alipay-description' }, {}),
+ actions: [
+
+
+ ,
+ ],
+ avatar: ,
+ },
+ {
+ title: formatMessage({ id: 'app.settings.binding.dingding' }, {}),
+ description: formatMessage({ id: 'app.settings.binding.dingding-description' }, {}),
+ actions: [
+
+
+ ,
+ ],
+ avatar: ,
+ },
+ ];
+
+ render() {
+ return (
+
+ (
+
+
+
+ )}
+ />
+
+ );
+ }
+}
+
+export default BindingView;
diff --git a/src/pages/Account/Settings/Geographic.js b/src/pages/Account/Settings/Geographic.js
new file mode 100644
index 00000000..d33cb138
--- /dev/null
+++ b/src/pages/Account/Settings/Geographic.js
@@ -0,0 +1,128 @@
+import React, { PureComponent } from 'react';
+import { Select, Spin } from 'antd';
+import { connect } from 'dva';
+import styles from './GeographicView.less';
+
+const { Option } = Select;
+
+const nullSlectItem = {
+ label: '',
+ key: '',
+};
+
+@connect(({ geographic }) => {
+ const { province, isLoading, city } = geographic;
+ return {
+ province,
+ city,
+ isLoading,
+ };
+})
+class GeographicView extends PureComponent {
+ componentDidMount = () => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'geographic/fetchProvince',
+ });
+ };
+
+ componentDidUpdate(props) {
+ const { dispatch, value } = this.props;
+
+ if (!props.value && !!value && !!value.province) {
+ dispatch({
+ type: 'geographic/fetchCity',
+ payload: value.province.key,
+ });
+ }
+ }
+
+ getProvinceOption() {
+ const { province } = this.props;
+ return this.getOption(province);
+ }
+
+ getCityOption = () => {
+ const { city } = this.props;
+ return this.getOption(city);
+ };
+
+ getOption = list => {
+ if (!list || list.length < 1) {
+ return (
+
+ 没有找到选项
+
+ );
+ }
+ return list.map(item => (
+
+ {item.name}
+
+ ));
+ };
+
+ selectProvinceItem = item => {
+ const { dispatch, onChange } = this.props;
+ dispatch({
+ type: 'geographic/fetchCity',
+ payload: item.key,
+ });
+ onChange({
+ province: item,
+ city: nullSlectItem,
+ });
+ };
+
+ selectCityItem = item => {
+ const { value, onChange } = this.props;
+ onChange({
+ province: value.province,
+ city: item,
+ });
+ };
+
+ conversionObject() {
+ const { value } = this.props;
+ if (!value) {
+ return {
+ province: nullSlectItem,
+ city: nullSlectItem,
+ };
+ }
+ const { province, city } = value;
+ return {
+ province: province || nullSlectItem,
+ city: city || nullSlectItem,
+ };
+ }
+
+ render() {
+ const { province, city } = this.conversionObject();
+ const { isLoading } = this.props;
+ return (
+
+
+ {this.getProvinceOption()}
+
+
+ {this.getCityOption()}
+
+
+ );
+ }
+}
+
+export default GeographicView;
diff --git a/src/pages/Account/Settings/GeographicView.less b/src/pages/Account/Settings/GeographicView.less
new file mode 100644
index 00000000..22e35a29
--- /dev/null
+++ b/src/pages/Account/Settings/GeographicView.less
@@ -0,0 +1,19 @@
+@import '~antd/lib/style/themes/default.less';
+
+.row {
+ .item {
+ max-width: 220px;
+ width: 50%;
+ }
+ .item:first-child {
+ margin-right: 8px;
+ width: ~'calc(50% - 8px)';
+ }
+}
+
+@media screen and (max-width: @screen-sm) {
+ .item:first-child {
+ margin: 0;
+ margin-bottom: 8px;
+ }
+}
diff --git a/src/pages/Account/Settings/Info.less b/src/pages/Account/Settings/Info.less
new file mode 100644
index 00000000..b7a59fba
--- /dev/null
+++ b/src/pages/Account/Settings/Info.less
@@ -0,0 +1,97 @@
+@import '~antd/lib/style/themes/default.less';
+
+.main {
+ width: 100%;
+ height: 100%;
+ background-color: @body-background;
+ display: flex;
+ padding-top: 16px;
+ padding-bottom: 16px;
+ overflow: auto;
+ .leftmenu {
+ width: 224px;
+ border-right: @border-width-base @border-style-base @border-color-split;
+ :global {
+ .ant-menu-inline {
+ border: none;
+ }
+ .ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
+ font-weight: bold;
+ }
+ }
+ }
+ .right {
+ flex: 1;
+ padding-left: 40px;
+ padding-right: 40px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ .title {
+ font-size: 20px;
+ color: @heading-color;
+ line-height: 28px;
+ font-weight: 500;
+ margin-bottom: 12px;
+ }
+ }
+ :global {
+ .ant-list-split .ant-list-item:last-child {
+ border-bottom: 1px solid #e8e8e8;
+ }
+ .ant-list-item {
+ padding-top: 14px;
+ padding-bottom: 14px;
+ }
+ }
+}
+:global {
+ .ant-list-item-meta {
+ // 账号绑定图标
+ .taobao {
+ color: #ff4000;
+ display: block;
+ font-size: 48px;
+ line-height: 48px;
+ border-radius: @border-radius-base;
+ }
+ .dingding {
+ background-color: #2eabff;
+ color: #fff;
+ font-size: 32px;
+ line-height: 32px;
+ padding: 6px;
+ margin: 2px;
+ border-radius: @border-radius-base;
+ }
+ .alipay {
+ color: #2eabff;
+ font-size: 48px;
+ line-height: 48px;
+ border-radius: @border-radius-base;
+ }
+ }
+
+ // 密码强度
+ font.strong {
+ color: @success-color;
+ }
+ font.medium {
+ color: @warning-color;
+ }
+ font.weak {
+ color: @error-color;
+ }
+}
+
+@media screen and (max-width: @screen-md) {
+ .main {
+ flex-direction: column;
+ .leftmenu {
+ width: 100%;
+ border: none;
+ }
+ .right {
+ padding: 40px;
+ }
+ }
+}
diff --git a/src/pages/Account/Settings/Notification.js b/src/pages/Account/Settings/Notification.js
new file mode 100644
index 00000000..96677bbd
--- /dev/null
+++ b/src/pages/Account/Settings/Notification.js
@@ -0,0 +1,50 @@
+import React, { Component, Fragment } from 'react';
+import { formatMessage } from 'umi/locale';
+import { Switch, List } from 'antd';
+
+class NotificationView extends Component {
+ getData = () => {
+ const Action = (
+
+ );
+ return [
+ {
+ title: formatMessage({ id: 'app.settings.notification.password' }, {}),
+ description: formatMessage({ id: 'app.settings.notification.password-description' }, {}),
+ actions: [Action],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.notification.messages' }, {}),
+ description: formatMessage({ id: 'app.settings.notification.messages-description' }, {}),
+ actions: [Action],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.notification.todo' }, {}),
+ description: formatMessage({ id: 'app.settings.notification.todo-description' }, {}),
+ actions: [Action],
+ },
+ ];
+ };
+
+ render() {
+ return (
+
+ (
+
+
+
+ )}
+ />
+
+ );
+ }
+}
+
+export default NotificationView;
diff --git a/src/pages/Account/Settings/Phone.js b/src/pages/Account/Settings/Phone.js
new file mode 100644
index 00000000..26655276
--- /dev/null
+++ b/src/pages/Account/Settings/Phone.js
@@ -0,0 +1,33 @@
+import React, { Fragment, PureComponent } from 'react';
+import { Input } from 'antd';
+import styles from './PhoneView.less';
+
+class PhoneView extends PureComponent {
+ render() {
+ const { value, onChange } = this.props;
+ let values = ['', ''];
+ if (value) {
+ values = value.split('-');
+ }
+ return (
+
+ {
+ onChange(`${e.target.value}-${values[1]}`);
+ }}
+ />
+ {
+ onChange(`${values[0]}-${e.target.value}`);
+ }}
+ value={values[1]}
+ />
+
+ );
+ }
+}
+
+export default PhoneView;
diff --git a/src/pages/Account/Settings/PhoneView.less b/src/pages/Account/Settings/PhoneView.less
new file mode 100644
index 00000000..5c85970d
--- /dev/null
+++ b/src/pages/Account/Settings/PhoneView.less
@@ -0,0 +1,11 @@
+@import '~antd/lib/style/themes/default.less';
+
+.area_code {
+ max-width: 128px;
+ margin-right: 8px;
+ width: 30%;
+}
+.phone_number {
+ max-width: 312px;
+ width: ~'calc(70% - 8px)';
+}
diff --git a/src/pages/Account/Settings/Security.js b/src/pages/Account/Settings/Security.js
new file mode 100644
index 00000000..0706bd0b
--- /dev/null
+++ b/src/pages/Account/Settings/Security.js
@@ -0,0 +1,102 @@
+import React, { Component, Fragment } from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import { List } from 'antd';
+// import { getTimeDistance } from '@/utils/utils';
+
+const passwordStrength = {
+ strong: (
+
+
+
+ ),
+ medium: (
+
+
+
+ ),
+ weak: (
+
+
+ Weak
+
+ ),
+};
+
+class SecurityView extends Component {
+ getData = () => [
+ {
+ title: formatMessage({ id: 'app.settings.security.password' }, {}),
+ description: (
+
+ {formatMessage({ id: 'app.settings.security.password-description' })}:
+ {passwordStrength.strong}
+
+ ),
+ actions: [
+
+
+ ,
+ ],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.security.phone' }, {}),
+ description: `${formatMessage(
+ { id: 'app.settings.security.phone-description' },
+ {}
+ )}:138****8293`,
+ actions: [
+
+
+ ,
+ ],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.security.question' }, {}),
+ description: formatMessage({ id: 'app.settings.security.question-description' }, {}),
+ actions: [
+
+
+ ,
+ ],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.security.email' }, {}),
+ description: `${formatMessage(
+ { id: 'app.settings.security.email-description' },
+ {}
+ )}:ant***sign.com`,
+ actions: [
+
+
+ ,
+ ],
+ },
+ {
+ title: formatMessage({ id: 'app.settings.security.mfa' }, {}),
+ description: formatMessage({ id: 'app.settings.security.mfa-description' }, {}),
+ actions: [
+
+
+ ,
+ ],
+ },
+ ];
+
+ render() {
+ return (
+
+ (
+
+
+
+ )}
+ />
+
+ );
+ }
+}
+
+export default SecurityView;
diff --git a/src/pages/Account/Settings/_layout.js b/src/pages/Account/Settings/_layout.js
new file mode 100644
index 00000000..1b09211e
--- /dev/null
+++ b/src/pages/Account/Settings/_layout.js
@@ -0,0 +1,127 @@
+import React, { Component } from 'react';
+import { connect } from 'dva';
+import router from 'umi/router';
+import { FormattedMessage } from 'umi/locale';
+import { Menu } from 'antd';
+import GridContent from '@/components/PageHeaderWrapper/GridContent';
+import styles from './Info.less';
+
+const { Item } = Menu;
+
+@connect(({ user }) => ({
+ currentUser: user.currentUser,
+}))
+class Info extends Component {
+ constructor(props) {
+ super(props);
+ const { match, location } = props;
+ const menuMap = {
+ base: ,
+ security: (
+
+ ),
+ binding: (
+
+ ),
+ notification: (
+
+ ),
+ };
+ const key = location.pathname.replace(`${match.path.toLocaleLowerCase()}/`, '');
+ this.state = {
+ mode: 'inline',
+ menuMap,
+ selectKey: menuMap[key] ? key : 'base',
+ };
+ }
+
+ static getDerivedStateFromProps(props, state) {
+ const { match, location } = props;
+ let selectKey = location.pathname.replace(`${match.path.toLocaleLowerCase()}/`, '');
+ selectKey = state.menuMap[selectKey] ? selectKey : 'base';
+ if (selectKey !== state.selectKey) {
+ return { selectKey };
+ }
+ return null;
+ }
+
+ componentDidMount() {
+ window.addEventListener('resize', this.resize);
+ this.resize();
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.resize);
+ }
+
+ getmenu = () => {
+ const { menuMap } = this.state;
+ return Object.keys(menuMap).map(item => - {menuMap[item]}
);
+ };
+
+ getRightTitle = () => {
+ const { selectKey, menuMap } = this.state;
+ return menuMap[selectKey];
+ };
+
+ selectKey = ({ key }) => {
+ console.log(key);
+
+ router.push(`/account/settings/${key}`);
+ this.setState({
+ selectKey: key,
+ });
+ };
+
+ resize = () => {
+ if (!this.main) {
+ return;
+ }
+ requestAnimationFrame(() => {
+ let mode = 'inline';
+ const { offsetWidth } = this.main;
+ if (this.main.offsetWidth < 641 && offsetWidth > 400) {
+ mode = 'horizontal';
+ }
+ if (window.innerWidth < 768 && offsetWidth > 400) {
+ mode = 'horizontal';
+ }
+ this.setState({
+ mode,
+ });
+ });
+ };
+
+ render() {
+ const { children, currentUser } = this.props;
+ if (!currentUser.userid) {
+ return '';
+ }
+ const { mode, selectKey } = this.state;
+ return (
+
+ {
+ this.main = ref;
+ }}
+ >
+
+
+ {this.getmenu()}
+
+
+
+
{this.getRightTitle()}
+ {children}
+
+
+
+ );
+ }
+}
+
+export default Info;
diff --git a/src/pages/Account/Settings/models/geographic.js b/src/pages/Account/Settings/models/geographic.js
new file mode 100644
index 00000000..a501920c
--- /dev/null
+++ b/src/pages/Account/Settings/models/geographic.js
@@ -0,0 +1,65 @@
+import { queryProvince, queryCity } from '@/services/geographic';
+
+export default {
+ namespace: 'geographic',
+
+ state: {
+ province: [],
+ city: [],
+ isLoading: false,
+ },
+
+ effects: {
+ *fetchProvince(_, { call, put }) {
+ yield put({
+ type: 'changeLoading',
+ payload: true,
+ });
+ const response = yield call(queryProvince);
+ yield put({
+ type: 'setProvince',
+ payload: response,
+ });
+ yield put({
+ type: 'changeLoading',
+ payload: false,
+ });
+ },
+ *fetchCity({ payload }, { call, put }) {
+ yield put({
+ type: 'changeLoading',
+ payload: true,
+ });
+ const response = yield call(queryCity, payload);
+ yield put({
+ type: 'setCity',
+ payload: response,
+ });
+ yield put({
+ type: 'changeLoading',
+ payload: false,
+ });
+ },
+ },
+
+ reducers: {
+ setProvince(state, action) {
+ return {
+ ...state,
+ province: action.payload,
+ };
+ },
+ setCity(state, action) {
+ return {
+ ...state,
+ city: action.payload,
+ };
+ },
+ changeLoading(state, action) {
+ return {
+ ...state,
+ isLoading: action.payload,
+ };
+ },
+ },
+};
diff --git a/src/pages/Authorized.js b/src/pages/Authorized.js
new file mode 100644
index 00000000..c29d9610
--- /dev/null
+++ b/src/pages/Authorized.js
@@ -0,0 +1,13 @@
+import React from 'react';
+import RenderAuthorized from '@/components/Authorized';
+import { getAuthority } from '@/utils/authority';
+import Redirect from 'umi/redirect';
+
+const Authority = getAuthority();
+const Authorized = RenderAuthorized(Authority);
+
+export default ({ children }) => (
+ }>
+ {children}
+
+);
diff --git a/src/pages/Dashboard/Analysis.js b/src/pages/Dashboard/Analysis.js
index 4110021f..58261ebb 100644
--- a/src/pages/Dashboard/Analysis.js
+++ b/src/pages/Dashboard/Analysis.js
@@ -1,55 +1,24 @@
-import React, { Component, Fragment } from 'react';
+import React, { Component, Suspense } from 'react';
import { connect } from 'dva';
-import {
- Row,
- Col,
- Icon,
- Card,
- Tabs,
- Table,
- Radio,
- DatePicker,
- Tooltip,
- Menu,
- Dropdown,
-} from 'antd';
-import numeral from 'numeral';
-import {
- ChartCard,
- yuan,
- MiniArea,
- MiniBar,
- MiniProgress,
- Field,
- Bar,
- Pie,
- TimelineChart,
-} from 'components/Charts';
-import Trend from 'components/Trend';
-import NumberInfo from 'components/NumberInfo';
-import { getTimeDistance } from '../../utils/utils';
+import { Row, Col, Icon, Menu, Dropdown } from 'antd';
-import styles from './Analysis.less';
+import GridContent from '@/components/PageHeaderWrapper/GridContent';
+import { getTimeDistance } from '@/utils/utils';
-const { TabPane } = Tabs;
-const { RangePicker } = DatePicker;
+import styles from './Analysis.less';
+import PageLoading from '@/components/PageLoading';
-const rankingListData = [];
-for (let i = 0; i < 7; i += 1) {
- rankingListData.push({
- title: `工专路 ${i} 号店`,
- total: 323234,
- });
-}
-const Yuan = ({ children }) => (
- /* eslint-disable-line react/no-danger */
-);
+const IntroduceRow = React.lazy(() => import('./IntroduceRow'));
+const SalesCard = React.lazy(() => import('./SalesCard'));
+const TopSearch = React.lazy(() => import('./TopSearch'));
+const ProportionSales = React.lazy(() => import('./ProportionSales'));
+const OfflineData = React.lazy(() => import('./OfflineData'));
@connect(({ chart, loading }) => ({
chart,
loading: loading.effects['chart/fetch'],
}))
-export default class Analysis extends Component {
+class Analysis extends Component {
state = {
salesType: 'all',
currentTabKey: '',
@@ -57,8 +26,11 @@ export default class Analysis extends Component {
};
componentDidMount() {
- this.props.dispatch({
- type: 'chart/fetch',
+ const { dispatch } = this.props;
+ this.reqRef = requestAnimationFrame(() => {
+ dispatch({
+ type: 'chart/fetch',
+ });
});
}
@@ -67,45 +39,49 @@ export default class Analysis extends Component {
dispatch({
type: 'chart/clear',
});
+ cancelAnimationFrame(this.reqRef);
+ clearTimeout(this.timeoutId);
}
- handleChangeSalesType = (e) => {
+ handleChangeSalesType = e => {
this.setState({
salesType: e.target.value,
});
};
- handleTabChange = (key) => {
+ handleTabChange = key => {
this.setState({
currentTabKey: key,
});
};
- handleRangePickerChange = (rangePickerValue) => {
+ handleRangePickerChange = rangePickerValue => {
+ const { dispatch } = this.props;
this.setState({
rangePickerValue,
});
- this.props.dispatch({
+ dispatch({
type: 'chart/fetchSalesData',
});
};
- selectDate = (type) => {
+ selectDate = type => {
+ const { dispatch } = this.props;
this.setState({
rangePickerValue: getTimeDistance(type),
});
- this.props.dispatch({
+ dispatch({
type: 'chart/fetchSalesData',
});
};
- isActive(type) {
+ isActive = type => {
const { rangePickerValue } = this.state;
const value = getTimeDistance(type);
if (!rangePickerValue[0] || !rangePickerValue[1]) {
- return;
+ return '';
}
if (
rangePickerValue[0].isSame(value[0], 'day') &&
@@ -113,7 +89,8 @@ export default class Analysis extends Component {
) {
return styles.currentDate;
}
- }
+ return '';
+ };
render() {
const { rangePickerValue, salesType, currentTabKey } = this.state;
@@ -129,12 +106,12 @@ export default class Analysis extends Component {
salesTypeDataOnline,
salesTypeDataOffline,
} = chart;
-
- const salesPieData =
- salesType === 'all'
- ? salesTypeData
- : salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
-
+ let salesPieData;
+ if (salesType === 'all') {
+ salesPieData = salesTypeData;
+ } else {
+ salesPieData = salesType === 'online' ? salesTypeDataOnline : salesTypeDataOffline;
+ }
const menu = (
操作一
@@ -142,7 +119,7 @@ export default class Analysis extends Component {
);
- const iconGroup = (
+ const dropdownGroup = (
@@ -150,344 +127,59 @@ export default class Analysis extends Component {
);
- const salesExtra = (
-
- );
-
- const columns = [
- {
- title: '排名',
- dataIndex: 'index',
- key: 'index',
- },
- {
- title: '搜索关键词',
- dataIndex: 'keyword',
- key: 'keyword',
- render: text => {text} ,
- },
- {
- title: '用户数',
- dataIndex: 'count',
- key: 'count',
- sorter: (a, b) => a.count - b.count,
- className: styles.alignRight,
- },
- {
- title: '周涨幅',
- dataIndex: 'range',
- key: 'range',
- sorter: (a, b) => a.range - b.range,
- render: (text, record) => (
-
- {text}%
-
- ),
- align: 'right',
- },
- ];
-
const activeKey = currentTabKey || (offlineData[0] && offlineData[0].name);
- const CustomTab = ({ data, currentTabKey: currentKey }) => (
-
-
-
-
-
-
-
-
- );
-
- const topColResponsiveProps = {
- xs: 24,
- sm: 12,
- md: 12,
- lg: 12,
- xl: 6,
- style: { marginBottom: 24 },
- };
-
return (
-
-
-
-
-
-
- }
- total={() => 126560 }
- footer={ }
- contentHeight={46}
- >
-
- 周同比12%
-
-
- 日环比11%
-
-
-
-
-
-
-
- }
- total={numeral(8846).format('0,0')}
- footer={ }
- contentHeight={46}
- >
-
-
-
-
-
-
-
- }
- total={numeral(6560).format('0,0')}
- footer={ }
- contentHeight={46}
- >
-
-
-
-
-
-
-
- }
- total="78%"
- footer={
-
-
- 周同比12%
-
-
- 日环比11%
-
-
- }
- contentHeight={46}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
门店销售额排名
-
- {rankingListData.map((item, i) => (
-
- {i + 1}
- {item.title}
- {numeral(item.total).format('0,0')}
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
门店访问量排名
-
- {rankingListData.map((item, i) => (
-
- {i + 1}
- {item.title}
- {numeral(item.total).format('0,0')}
-
- ))}
-
-
-
-
-
-
-
-
-
+
+ }>
+
+
+
+
+
-
-
-
-
- 搜索用户数
-
-
-
-
- }
- gap={8}
- total={numeral(12321).format('0,0')}
- status="up"
- subTotal={17.1}
- />
-
-
-
-
-
-
-
-
record.index}
- size="small"
- columns={columns}
- dataSource={searchData}
- pagination={{
- style: { marginBottom: 0 },
- pageSize: 5,
- }}
+
+
-
+
-
- {iconGroup}
-
-
- 全部渠道
- 线上
- 门店
-
-
-
- }
- style={{ marginTop: 24, minHeight: 509 }}
- >
- 销售额
- {salesPieData.reduce((pre, now) => now.y + pre, 0)}
- }
- data={salesPieData}
- valueFormat={value => {value} }
- height={248}
- lineWidth={4}
+
+
-
+
-
-
-
- {offlineData.map(shop => (
- } key={shop.name}>
-
-
-
-
- ))}
-
-
-
+
+
+
+
);
}
}
+
+export default Analysis;
diff --git a/src/pages/Dashboard/Analysis.less b/src/pages/Dashboard/Analysis.less
index 38682c30..d9b437cb 100644
--- a/src/pages/Dashboard/Analysis.less
+++ b/src/pages/Dashboard/Analysis.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.iconGroup {
i {
@@ -20,30 +20,36 @@
li {
.clearfix();
margin-top: 16px;
+ display: flex;
+ align-items: center;
span {
color: @text-color;
font-size: 14px;
line-height: 22px;
}
- span:first-child {
+ .rankingItemNumber {
background-color: @background-color-base;
border-radius: 20px;
display: inline-block;
font-size: 12px;
font-weight: 600;
- margin-right: 24px;
+ margin-right: 16px;
height: 20px;
line-height: 20px;
width: 20px;
text-align: center;
+ margin-top: 1.5px;
+ &.active {
+ background-color: #314659;
+ color: #fff;
+ }
}
- span.active {
- //background-color: @primary-color;
- background-color: #314659;
- color: #fff;
- }
- span:last-child {
- float: right;
+ .rankingItemTitle {
+ flex: 1;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ margin-right: 8px;
}
}
}
@@ -86,17 +92,20 @@
.ant-card-head {
position: relative;
}
+ .ant-card-head-title {
+ align-items: normal;
+ }
}
}
.salesCardExtra {
- height: 68px;
+ height: inherit;
}
.salesTypeRadio {
position: absolute;
- left: 24px;
- bottom: 15px;
+ right: 54px;
+ bottom: 12px;
}
.offlineCard {
@@ -119,10 +128,9 @@
position: relative;
right: 6px;
}
- }
-
- :global(.ant-tabs-tab-active) h4 {
- color: @primary-color;
+ .ant-tabs-tab-active h4 {
+ color: @primary-color;
+ }
}
}
diff --git a/src/pages/Dashboard/IntroduceRow.js b/src/pages/Dashboard/IntroduceRow.js
new file mode 100755
index 00000000..7262826f
--- /dev/null
+++ b/src/pages/Dashboard/IntroduceRow.js
@@ -0,0 +1,144 @@
+import React, { memo } from 'react';
+import { Row, Col, Icon, Tooltip } from 'antd';
+import { FormattedMessage } from 'umi/locale';
+import styles from './Analysis.less';
+import { ChartCard, MiniArea, MiniBar, MiniProgress, Field } from '@/components/Charts';
+import Trend from '@/components/Trend';
+import numeral from 'numeral';
+import Yuan from '@/utils/Yuan';
+
+const topColResponsiveProps = {
+ xs: 24,
+ sm: 12,
+ md: 12,
+ lg: 12,
+ xl: 6,
+ style: { marginBottom: 24 },
+};
+
+const IntroduceRow = memo(({ loading, visitData }) => (
+
+
+ }
+ action={
+ }
+ >
+
+
+ }
+ loading={loading}
+ total={() => 126560 }
+ footer={
+ }
+ value={`¥${numeral(12423).format('0,0')}`}
+ />
+ }
+ contentHeight={46}
+ >
+
+
+ 12%
+
+
+
+ 11%
+
+
+
+
+
+ }
+ action={
+ }
+ >
+
+
+ }
+ total={numeral(8846).format('0,0')}
+ footer={
+ }
+ value={numeral(1234).format('0,0')}
+ />
+ }
+ contentHeight={46}
+ >
+
+
+
+
+ }
+ action={
+ }
+ >
+
+
+ }
+ total={numeral(6560).format('0,0')}
+ footer={
+
+ }
+ value="60%"
+ />
+ }
+ contentHeight={46}
+ >
+
+
+
+
+
+ }
+ action={
+ }
+ >
+
+
+ }
+ total="78%"
+ footer={
+
+
+
+ 12%
+
+
+
+ 11%
+
+
+ }
+ contentHeight={46}
+ >
+
+
+
+
+));
+
+export default IntroduceRow;
diff --git a/src/pages/Dashboard/Monitor.js b/src/pages/Dashboard/Monitor.js
index 0c9593ab..62909174 100644
--- a/src/pages/Dashboard/Monitor.js
+++ b/src/pages/Dashboard/Monitor.js
@@ -1,23 +1,36 @@
-import React, { PureComponent, Fragment } from 'react';
+import React, { PureComponent } from 'react';
import { connect } from 'dva';
+import { formatMessage, FormattedMessage } from 'umi/locale';
import { Row, Col, Card, Tooltip } from 'antd';
+import { Pie, WaterWave, Gauge, TagCloud } from '@/components/Charts';
+import NumberInfo from '@/components/NumberInfo';
+import CountDown from '@/components/CountDown';
+import ActiveChart from '@/components/ActiveChart';
import numeral from 'numeral';
-import { Pie, WaterWave, Gauge, TagCloud } from 'components/Charts';
-import NumberInfo from 'components/NumberInfo';
-import CountDown from 'components/CountDown';
-import ActiveChart from 'components/ActiveChart';
+import GridContent from '@/components/PageHeaderWrapper/GridContent';
+
+import Authorized from '@/utils/Authorized';
import styles from './Monitor.less';
+const { Secured } = Authorized;
const targetTime = new Date().getTime() + 3900000;
+// use permission as a parameter
+const havePermissionAsync = new Promise(resolve => {
+ // Call resolve on behalf of passed
+ setTimeout(() => resolve(), 300);
+});
+
+@Secured(havePermissionAsync)
@connect(({ monitor, loading }) => ({
monitor,
loading: loading.models.monitor,
}))
-export default class Monitor extends PureComponent {
+class Monitor extends PureComponent {
componentDidMount() {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'monitor/fetchTags',
});
}
@@ -27,34 +40,75 @@ export default class Monitor extends PureComponent {
const { tags } = monitor;
return (
-
+
-
+
+ }
+ bordered={false}
+ >
+ }
suffix="元"
total={numeral(124543233).format('0,0')}
/>
-
+
+ }
+ total="92%"
+ />
- } />
+
+ }
+ total={ }
+ />
+ }
suffix="元"
total={numeral(234).format('0,0')}
/>
-
+
+ }
+ >
-
+
+ }
+ style={{ marginBottom: 24 }}
+ bordered={false}
+ >
}
style={{ marginBottom: 24 }}
bodyStyle={{ textAlign: 'center' }}
bordered={false}
>
{
- switch (parseInt(val, 10)) {
- case 20:
- return '差';
- case 40:
- return '中';
- case 60:
- return '良';
- case 80:
- return '优';
- default:
- return '';
- }
- }}
- title="跳出率"
+ title={formatMessage({ id: 'app.monitor.ratio', defaultMessage: 'Ratio' })}
height={180}
percent={87}
/>
@@ -96,14 +145,25 @@ export default class Monitor extends PureComponent {
-
-
+
+
+ }
+ bordered={false}
+ className={styles.pieCard}
+ >
+ }
total="28%"
height={128}
lineWidth={2}
@@ -114,7 +174,12 @@ export default class Monitor extends PureComponent {
animate={false}
color="#5DDECF"
percent={22}
- subTitle="西餐"
+ subTitle={
+
+ }
total="22%"
height={128}
lineWidth={2}
@@ -125,7 +190,9 @@ export default class Monitor extends PureComponent {
animate={false}
color="#2FC25B"
percent={32}
- subTitle="火锅"
+ subTitle={
+
+ }
total="32%"
height={128}
lineWidth={2}
@@ -134,9 +201,14 @@ export default class Monitor extends PureComponent {
-
+
+ }
loading={loading}
bordered={false}
bodyStyle={{ overflow: 'hidden' }}
@@ -144,17 +216,30 @@ export default class Monitor extends PureComponent {
-
+
+ }
bodyStyle={{ textAlign: 'center', fontSize: 0 }}
bordered={false}
>
-
+
+ }
+ percent={34}
+ />
-
+
);
}
}
+
+export default Monitor;
diff --git a/src/pages/Dashboard/Monitor.less b/src/pages/Dashboard/Monitor.less
index e52dd30c..ca41f33e 100644
--- a/src/pages/Dashboard/Monitor.less
+++ b/src/pages/Dashboard/Monitor.less
@@ -1,9 +1,9 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.mapChart {
padding-top: 24px;
- height: 457px;
+ height: 452px;
text-align: center;
img {
display: inline-block;
@@ -13,7 +13,7 @@
}
.pieCard :global(.pie-stat) {
- font-size: 24px!important;
+ font-size: 24px !important;
}
@media screen and (max-width: @screen-lg) {
diff --git a/src/pages/Dashboard/OfflineData.js b/src/pages/Dashboard/OfflineData.js
new file mode 100755
index 00000000..f7d06ef0
--- /dev/null
+++ b/src/pages/Dashboard/OfflineData.js
@@ -0,0 +1,65 @@
+import React, { memo } from 'react';
+import { Card, Tabs, Row, Col } from 'antd';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import styles from './Analysis.less';
+import { TimelineChart, Pie } from '@/components/Charts';
+import NumberInfo from '@/components/NumberInfo';
+
+const CustomTab = ({ data, currentTabKey: currentKey }) => (
+
+
+
+ }
+ gap={2}
+ total={`${data.cvr * 100}%`}
+ theme={currentKey !== data.name && 'light'}
+ />
+
+
+
+
+
+);
+
+const { TabPane } = Tabs;
+
+const OfflineData = memo(
+ ({ activeKey, loading, offlineData, offlineChartData, handleTabChange }) => (
+
+
+ {offlineData.map(shop => (
+ } key={shop.name}>
+
+
+
+
+ ))}
+
+
+ )
+);
+
+export default OfflineData;
diff --git a/src/pages/Dashboard/ProportionSales.js b/src/pages/Dashboard/ProportionSales.js
new file mode 100755
index 00000000..6343b71c
--- /dev/null
+++ b/src/pages/Dashboard/ProportionSales.js
@@ -0,0 +1,63 @@
+import React, { memo } from 'react';
+import { Card, Radio } from 'antd';
+import { FormattedMessage } from 'umi/locale';
+import styles from './Analysis.less';
+import { Pie } from '@/components/Charts';
+import Yuan from '@/utils/Yuan';
+
+const ProportionSales = memo(
+ ({ dropdownGroup, salesType, loading, salesPieData, handleChangeSalesType }) => (
+
+ }
+ bodyStyle={{ padding: 24 }}
+ extra={
+
+ {dropdownGroup}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ style={{ marginTop: 24 }}
+ >
+
+
+
+
+
}
+ total={() =>
{salesPieData.reduce((pre, now) => now.y + pre, 0)} }
+ data={salesPieData}
+ valueFormat={value =>
{value} }
+ height={248}
+ lineWidth={4}
+ />
+
+
+ )
+);
+
+export default ProportionSales;
diff --git a/src/pages/Dashboard/SalesCard.js b/src/pages/Dashboard/SalesCard.js
new file mode 100755
index 00000000..3ab57775
--- /dev/null
+++ b/src/pages/Dashboard/SalesCard.js
@@ -0,0 +1,150 @@
+import React, { memo } from 'react';
+import { Row, Col, Card, Tabs, DatePicker } from 'antd';
+import { FormattedMessage, formatMessage } from 'umi/locale';
+import numeral from 'numeral';
+import styles from './Analysis.less';
+import { Bar } from '@/components/Charts';
+
+const { RangePicker } = DatePicker;
+const { TabPane } = Tabs;
+
+const rankingListData = [];
+for (let i = 0; i < 7; i += 1) {
+ rankingListData.push({
+ title: formatMessage({ id: 'app.analysis.test' }, { no: i }),
+ total: 323234,
+ });
+}
+
+const SalesCard = memo(
+ ({ rangePickerValue, salesData, isActive, handleRangePickerChange, loading, selectDate }) => (
+
+
+ }
+ size="large"
+ tabBarStyle={{ marginBottom: 24 }}
+ >
+ }
+ key="sales"
+ >
+
+
+
+
+ }
+ data={salesData}
+ />
+
+
+
+
+
+
+
+
+ {rankingListData.map((item, i) => (
+
+
+ {i + 1}
+
+
+ {item.title}
+
+
+ {numeral(item.total).format('0,0')}
+
+
+ ))}
+
+
+
+
+
+ }
+ key="views"
+ >
+
+
+
+
+ }
+ data={salesData}
+ />
+
+
+
+
+
+
+
+
+ {rankingListData.map((item, i) => (
+
+
+ {i + 1}
+
+
+ {item.title}
+
+ {numeral(item.total).format('0,0')}
+
+ ))}
+
+
+
+
+
+
+
+
+ )
+);
+
+export default SalesCard;
diff --git a/src/pages/Dashboard/TopSearch.js b/src/pages/Dashboard/TopSearch.js
new file mode 100755
index 00000000..4e75ea7d
--- /dev/null
+++ b/src/pages/Dashboard/TopSearch.js
@@ -0,0 +1,111 @@
+import React, { memo } from 'react';
+import { Row, Col, Table, Tooltip, Card, Icon } from 'antd';
+import { FormattedMessage } from 'umi/locale';
+import Trend from '@/components/Trend';
+import numeral from 'numeral';
+import styles from './Analysis.less';
+import NumberInfo from '@/components/NumberInfo';
+import { MiniArea } from '@/components/Charts';
+
+const columns = [
+ {
+ title: ,
+ dataIndex: 'index',
+ key: 'index',
+ },
+ {
+ title: (
+
+ ),
+ dataIndex: 'keyword',
+ key: 'keyword',
+ render: text => {text} ,
+ },
+ {
+ title: ,
+ dataIndex: 'count',
+ key: 'count',
+ sorter: (a, b) => a.count - b.count,
+ className: styles.alignRight,
+ },
+ {
+ title: ,
+ dataIndex: 'range',
+ key: 'range',
+ sorter: (a, b) => a.range - b.range,
+ render: (text, record) => (
+
+ {text}%
+
+ ),
+ align: 'right',
+ },
+];
+
+const TopSearch = memo(({ loading, visitData2, searchData, dropdownGroup }) => (
+
+ }
+ extra={dropdownGroup}
+ style={{ marginTop: 24 }}
+ >
+
+
+
+
+ }
+ >
+
+
+
+ }
+ gap={8}
+ total={numeral(12321).format('0,0')}
+ status="up"
+ subTotal={17.1}
+ />
+
+
+
+
+
+ }
+ >
+
+
+
+ }
+ total={2.7}
+ status="down"
+ subTotal={26.2}
+ gap={8}
+ />
+
+
+
+ record.index}
+ size="small"
+ columns={columns}
+ dataSource={searchData}
+ pagination={{
+ style: { marginBottom: 0 },
+ pageSize: 5,
+ }}
+ />
+
+));
+
+export default TopSearch;
diff --git a/src/pages/Dashboard/Workplace.js b/src/pages/Dashboard/Workplace.js
index e596118a..e9e44d45 100644
--- a/src/pages/Dashboard/Workplace.js
+++ b/src/pages/Dashboard/Workplace.js
@@ -1,12 +1,12 @@
import React, { PureComponent } from 'react';
import moment from 'moment';
import { connect } from 'dva';
-import { Link } from 'dva/router';
+import Link from 'umi/link';
import { Row, Col, Card, List, Avatar } from 'antd';
-import { Radar } from 'components/Charts';
-import EditableLinkGroup from 'components/EditableLinkGroup';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import { Radar } from '@/components/Charts';
+import EditableLinkGroup from '@/components/EditableLinkGroup';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './Workplace.less';
@@ -37,49 +37,21 @@ const links = [
},
];
-const members = [
- {
- id: 'members-1',
- title: '科学搬砖组',
- logo: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
- link: '',
- },
- {
- id: 'members-2',
- title: '程序员日常',
- logo: 'https://gw.alipayobjects.com/zos/rmsportal/cnrhVkzwxjPwAaCfPbdc.png',
- link: '',
- },
- {
- id: 'members-3',
- title: '设计天团',
- logo: 'https://gw.alipayobjects.com/zos/rmsportal/gaOngJwsRYRaVAuXXcmB.png',
- link: '',
- },
- {
- id: 'members-4',
- title: '中二少女团',
- logo: 'https://gw.alipayobjects.com/zos/rmsportal/ubnKSIfAJTxIgXOKlciN.png',
- link: '',
- },
- {
- id: 'members-5',
- title: '骗你学计算机',
- logo: 'https://gw.alipayobjects.com/zos/rmsportal/WhxKECPNujWoWEFNdnJE.png',
- link: '',
- },
-];
-
-@connect(({ project, activities, chart, loading }) => ({
+@connect(({ user, project, activities, chart, loading }) => ({
+ currentUser: user.currentUser,
project,
activities,
chart,
+ currentUserLoading: loading.effects['user/fetchCurrent'],
projectLoading: loading.effects['project/fetchNotice'],
activitiesLoading: loading.effects['activities/fetchList'],
}))
-export default class Workplace extends PureComponent {
+class Workplace extends PureComponent {
componentDidMount() {
const { dispatch } = this.props;
+ dispatch({
+ type: 'user/fetchCurrent',
+ });
dispatch({
type: 'project/fetchNotice',
});
@@ -102,10 +74,14 @@ export default class Workplace extends PureComponent {
const {
activities: { list },
} = this.props;
- return list.map((item) => {
- const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => {
+ return list.map(item => {
+ const events = item.template.split(/@\{([^{}]*)\}/gi).map(key => {
if (item[key]) {
- return {item[key].name} ;
+ return (
+
+ {item[key].name}
+
+ );
}
return key;
});
@@ -133,23 +109,32 @@ export default class Workplace extends PureComponent {
render() {
const {
+ currentUser,
+ currentUserLoading,
project: { notice },
projectLoading,
activitiesLoading,
chart: { radarData },
} = this.props;
- const pageHeaderContent = (
-
-
-
-
早安,曲丽丽,祝你开心每一天!
-
交互专家 | 蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED
+ const pageHeaderContent =
+ currentUser && Object.keys(currentUser).length ? (
+
+
+
+
+ 早安,
+ {currentUser.name}
+ ,祝你开心每一天!
+
+
+ {currentUser.title} |{currentUser.group}
+
+
-
- );
+ ) : null;
const extraContent = (
@@ -159,7 +144,9 @@ export default class Workplace extends PureComponent {
团队内排名
-
8 / 24
+
+ 8 / 24
+
项目访问
@@ -169,7 +156,8 @@ export default class Workplace extends PureComponent {
);
return (
-
@@ -184,31 +172,29 @@ export default class Workplace extends PureComponent {
loading={projectLoading}
bodyStyle={{ padding: 0 }}
>
- {
- notice.map(item => (
-
-
-
-
- {item.title}
-
- )}
- description={item.description}
- />
-
- {item.member || ''}
- {item.updatedAt && (
-
- {moment(item.updatedAt).fromNow()}
-
- )}
-
-
-
- ))
- }
+ {notice.map(item => (
+
+
+
+
+ {item.title}
+
+ }
+ description={item.description}
+ />
+
+ {item.member || ''}
+ {item.updatedAt && (
+
+ {moment(item.updatedAt).fromNow()}
+
+ )}
+
+
+
+ ))}
-
- {this.renderActivities()}
-
+ {this.renderActivities()}
@@ -231,11 +215,7 @@ export default class Workplace extends PureComponent {
bordered={false}
bodyStyle={{ padding: 0 }}
>
- {}}
- links={links}
- linkElement={Link}
- />
+ {}} links={links} linkElement={Link} />
- {
- members.map(item => (
-
-
-
- {item.title}
-
-
- ))
- }
+ {notice.map(item => (
+
+
+
+ {item.member}
+
+
+ ))}
-
+
);
}
}
+
+export default Workplace;
diff --git a/src/pages/Dashboard/Workplace.less b/src/pages/Dashboard/Workplace.less
index 4c875292..03801711 100644
--- a/src/pages/Dashboard/Workplace.less
+++ b/src/pages/Dashboard/Workplace.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.activitiesList {
padding: 0 24px 8px 24px;
@@ -86,24 +86,18 @@
a {
display: block;
margin: 12px 0;
- line-height: 24px;
height: 24px;
+ color: @text-color;
+ transition: all 0.3s;
.textOverflow();
.member {
font-size: @font-size-base;
- color: @text-color;
line-height: 24px;
- max-width: 100px;
vertical-align: top;
margin-left: 12px;
- transition: all .3s;
- display: inline-block;
- .textOverflow();
}
&:hover {
- span {
- color: @primary-color;
- }
+ color: @primary-color;
}
}
}
@@ -164,7 +158,7 @@
color: @disabled-color;
}
-@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) {
+@media screen and (max-width: @screen-xl) and (min-width: @screen-lg) {
.activeCard {
margin-bottom: 24px;
}
diff --git a/src/models/activities.js b/src/pages/Dashboard/models/activities.js
similarity index 89%
rename from src/models/activities.js
rename to src/pages/Dashboard/models/activities.js
index 76a7d502..4e0a11e2 100644
--- a/src/models/activities.js
+++ b/src/pages/Dashboard/models/activities.js
@@ -1,4 +1,4 @@
-import { queryActivities } from '../services/api';
+import { queryActivities } from '@/services/api';
export default {
namespace: 'activities',
diff --git a/src/models/chart.js b/src/pages/Dashboard/models/chart.js
similarity index 95%
rename from src/models/chart.js
rename to src/pages/Dashboard/models/chart.js
index 17bd3061..8dfe4a93 100644
--- a/src/models/chart.js
+++ b/src/pages/Dashboard/models/chart.js
@@ -1,4 +1,4 @@
-import { fakeChartData } from '../services/api';
+import { fakeChartData } from '@/services/api';
export default {
namespace: 'chart',
diff --git a/src/models/monitor.js b/src/pages/Dashboard/models/monitor.js
similarity index 89%
rename from src/models/monitor.js
rename to src/pages/Dashboard/models/monitor.js
index a5f96f82..e3e832ff 100644
--- a/src/models/monitor.js
+++ b/src/pages/Dashboard/models/monitor.js
@@ -1,4 +1,4 @@
-import { queryTags } from '../services/api';
+import { queryTags } from '@/services/api';
export default {
namespace: 'monitor',
diff --git a/src/pages/Exception/403.js b/src/pages/Exception/403.js
index c6d86fe0..35e4ca3a 100644
--- a/src/pages/Exception/403.js
+++ b/src/pages/Exception/403.js
@@ -1,7 +1,15 @@
import React from 'react';
-import { Link } from 'dva/router';
-import Exception from 'components/Exception';
+import { formatMessage } from 'umi/locale';
+import Link from 'umi/link';
+import Exception from '@/components/Exception';
-export default () => (
-
+const Exception403 = () => (
+
);
+
+export default Exception403;
diff --git a/src/pages/Exception/404.js b/src/pages/Exception/404.js
index 0a3d8766..84c986c5 100644
--- a/src/pages/Exception/404.js
+++ b/src/pages/Exception/404.js
@@ -1,7 +1,15 @@
import React from 'react';
-import { Link } from 'dva/router';
-import Exception from 'components/Exception';
+import { formatMessage } from 'umi/locale';
+import Link from 'umi/link';
+import Exception from '@/components/Exception';
-export default () => (
-
+const Exception404 = () => (
+
);
+
+export default Exception404;
diff --git a/src/pages/Exception/500.js b/src/pages/Exception/500.js
index 40f659cb..9d96f212 100644
--- a/src/pages/Exception/500.js
+++ b/src/pages/Exception/500.js
@@ -1,7 +1,15 @@
import React from 'react';
-import { Link } from 'dva/router';
-import Exception from 'components/Exception';
+import { formatMessage } from 'umi/locale';
+import Link from 'umi/link';
+import Exception from '@/components/Exception';
-export default () => (
-
+const Exception500 = () => (
+
);
+
+export default Exception500;
diff --git a/src/pages/Exception/models/error.js b/src/pages/Exception/models/error.js
new file mode 100644
index 00000000..1bfd9392
--- /dev/null
+++ b/src/pages/Exception/models/error.js
@@ -0,0 +1,28 @@
+import queryError from '@/services/error';
+
+export default {
+ namespace: 'error',
+
+ state: {
+ error: '',
+ isloading: false,
+ },
+
+ effects: {
+ *query({ payload }, { call, put }) {
+ yield call(queryError, payload.code);
+ yield put({
+ type: 'trigger',
+ payload: payload.code,
+ });
+ },
+ },
+
+ reducers: {
+ trigger(state, action) {
+ return {
+ error: action.payload,
+ };
+ },
+ },
+};
diff --git a/src/pages/Exception/style.less b/src/pages/Exception/style.less
index 44fccccf..91ec7dcf 100644
--- a/src/pages/Exception/style.less
+++ b/src/pages/Exception/style.less
@@ -1,5 +1,5 @@
.trigger {
- background: "red";
+ background: 'red';
:global(.ant-btn) {
margin-right: 8px;
margin-bottom: 12px;
diff --git a/src/pages/Exception/triggerException.js b/src/pages/Exception/triggerException.js
index 5e22992c..15ace510 100644
--- a/src/pages/Exception/triggerException.js
+++ b/src/pages/Exception/triggerException.js
@@ -6,68 +6,45 @@ import styles from './style.less';
@connect(state => ({
isloading: state.error.isloading,
}))
-export default class TriggerException extends PureComponent {
- state={
+class TriggerException extends PureComponent {
+ state = {
isloading: false,
- }
- trigger401 = () => {
- this.setState({
- isloading: true,
- });
- this.props.dispatch({
- type: 'error/query401',
- });
};
- trigger403 = () => {
- this.setState({
- isloading: true,
- });
- this.props.dispatch({
- type: 'error/query403',
- });
- };
- trigger500 = () => {
- this.setState({
- isloading: true,
- });
- this.props.dispatch({
- type: 'error/query500',
- });
- };
- trigger404 = () => {
+
+ triggerError = code => {
this.setState({
isloading: true,
});
- this.props.dispatch({
- type: 'error/query404',
- });
- };
- triggerError = () => {
- this.props.dispatch({
- type: 'error/throwError',
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'error/query',
+ payload: {
+ code,
+ },
});
};
+
render() {
+ const { isloading } = this.state;
return (
-
-
+
+ this.triggerError(401)}>
触发401
-
+ this.triggerError(403)}>
触发403
-
+ this.triggerError(500)}>
触发500
-
+ this.triggerError(404)}>
触发404
-
- 触发 error
-
);
}
}
+
+export default TriggerException;
diff --git a/src/pages/Forms/AdvancedForm.js b/src/pages/Form/AdvancedForm.js
similarity index 71%
rename from src/pages/Forms/AdvancedForm.js
rename to src/pages/Form/AdvancedForm.js
index 5b557646..fbd39689 100644
--- a/src/pages/Forms/AdvancedForm.js
+++ b/src/pages/Form/AdvancedForm.js
@@ -1,8 +1,20 @@
import React, { PureComponent } from 'react';
-import { Card, Button, Form, Icon, Col, Row, DatePicker, TimePicker, Input, Select, Popover } from 'antd';
+import {
+ Card,
+ Button,
+ Form,
+ Icon,
+ Col,
+ Row,
+ DatePicker,
+ TimePicker,
+ Input,
+ Select,
+ Popover,
+} from 'antd';
import { connect } from 'dva';
-import FooterToolbar from 'components/FooterToolbar';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import FooterToolbar from '@/components/FooterToolbar';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import TableForm from './TableForm';
import styles from './style.less';
@@ -24,95 +36,125 @@ const fieldLabels = {
type2: '任务类型',
};
-const tableData = [{
- key: '1',
- workId: '00001',
- name: 'John Brown',
- department: 'New York No. 1 Lake Park',
-}, {
- key: '2',
- workId: '00002',
- name: 'Jim Green',
- department: 'London No. 1 Lake Park',
-}, {
- key: '3',
- workId: '00003',
- name: 'Joe Black',
- department: 'Sidney No. 1 Lake Park',
-}];
+const tableData = [
+ {
+ key: '1',
+ workId: '00001',
+ name: 'John Brown',
+ department: 'New York No. 1 Lake Park',
+ },
+ {
+ key: '2',
+ workId: '00002',
+ name: 'Jim Green',
+ department: 'London No. 1 Lake Park',
+ },
+ {
+ key: '3',
+ workId: '00003',
+ name: 'Joe Black',
+ department: 'Sidney No. 1 Lake Park',
+ },
+];
+@connect(({ loading }) => ({
+ submitting: loading.effects['form/submitAdvancedForm'],
+}))
+@Form.create()
class AdvancedForm extends PureComponent {
state = {
width: '100%',
};
+
componentDidMount() {
- window.addEventListener('resize', this.resizeFooterToolbar);
+ window.addEventListener('resize', this.resizeFooterToolbar, { passive: true });
}
+
componentWillUnmount() {
window.removeEventListener('resize', this.resizeFooterToolbar);
}
- resizeFooterToolbar = () => {
- const sider = document.querySelectorAll('.ant-layout-sider')[0];
- const width = `calc(100% - ${sider.style.width})`;
- if (this.state.width !== width) {
- this.setState({ width });
+
+ getErrorInfo = () => {
+ const {
+ form: { getFieldsError },
+ } = this.props;
+ const errors = getFieldsError();
+ const errorCount = Object.keys(errors).filter(key => errors[key]).length;
+ if (!errors || errorCount === 0) {
+ return null;
}
- }
- render() {
- const { form, dispatch, submitting } = this.props;
- const { getFieldDecorator, validateFieldsAndScroll, getFieldsError } = form;
- const validate = () => {
- validateFieldsAndScroll((error, values) => {
- if (!error) {
- // submit the values
- dispatch({
- type: 'form/submitAdvancedForm',
- payload: values,
- });
- }
- });
+ const scrollToField = fieldKey => {
+ const labelNode = document.querySelector(`label[for="${fieldKey}"]`);
+ if (labelNode) {
+ labelNode.scrollIntoView(true);
+ }
};
- const errors = getFieldsError();
- const getErrorInfo = () => {
- const errorCount = Object.keys(errors).filter(key => errors[key]).length;
- if (!errors || errorCount === 0) {
+ const errorList = Object.keys(errors).map(key => {
+ if (!errors[key]) {
return null;
}
- const scrollToField = (fieldKey) => {
- const labelNode = document.querySelector(`label[for="${fieldKey}"]`);
- if (labelNode) {
- labelNode.scrollIntoView(true);
- }
- };
- const errorList = Object.keys(errors).map((key) => {
- if (!errors[key]) {
- return null;
- }
- return (
- scrollToField(key)}>
-
- {errors[key][0]}
- {fieldLabels[key]}
-
- );
- });
return (
-
- trigger.parentNode}
- >
-
-
- {errorCount}
-
+ scrollToField(key)}>
+
+ {errors[key][0]}
+ {fieldLabels[key]}
+
);
- };
+ });
return (
-
+ trigger.parentNode}
+ >
+
+
+ {errorCount}
+
+ );
+ };
+
+ resizeFooterToolbar = () => {
+ requestAnimationFrame(() => {
+ const sider = document.querySelectorAll('.ant-layout-sider')[0];
+ if (sider) {
+ const width = `calc(100% - ${sider.style.width})`;
+ const { width: stateWidth } = this.state;
+ if (stateWidth !== width) {
+ this.setState({ width });
+ }
+ }
+ });
+ };
+
+ validate = () => {
+ const {
+ form: { validateFieldsAndScroll },
+ dispatch,
+ } = this.props;
+ validateFieldsAndScroll((error, values) => {
+ if (!error) {
+ // submit the values
+ dispatch({
+ type: 'form/submitAdvancedForm',
+ payload: values,
+ });
+ }
+ });
+ };
+
+ render() {
+ const {
+ form: { getFieldDecorator },
+ submitting,
+ } = this.props;
+ const { width } = this.state;
+
+ return (
+
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入仓库名称' }],
- })(
-
- )}
+ })( )}
@@ -200,18 +240,14 @@ class AdvancedForm extends PureComponent {
{getFieldDecorator('name2', {
rules: [{ required: true, message: '请输入' }],
- })(
-
- )}
+ })( )}
{getFieldDecorator('url2', {
rules: [{ required: true, message: '请选择' }],
- })(
-
- )}
+ })( )}
@@ -273,18 +309,15 @@ class AdvancedForm extends PureComponent {
initialValue: tableData,
})( )}
-
- {getErrorInfo()}
-
+
+ {this.getErrorInfo()}
+
提交
-
+
);
}
}
-export default connect(({ global, loading }) => ({
- collapsed: global.collapsed,
- submitting: loading.effects['form/submitAdvancedForm'],
-}))(Form.create()(AdvancedForm));
+export default AdvancedForm;
diff --git a/src/pages/Form/BasicForm.js b/src/pages/Form/BasicForm.js
new file mode 100644
index 00000000..55833150
--- /dev/null
+++ b/src/pages/Form/BasicForm.js
@@ -0,0 +1,247 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import {
+ Form,
+ Input,
+ DatePicker,
+ Select,
+ Button,
+ Card,
+ InputNumber,
+ Radio,
+ Icon,
+ Tooltip,
+} from 'antd';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+import styles from './style.less';
+
+const FormItem = Form.Item;
+const { Option } = Select;
+const { RangePicker } = DatePicker;
+const { TextArea } = Input;
+
+@connect(({ loading }) => ({
+ submitting: loading.effects['form/submitRegularForm'],
+}))
+@Form.create()
+class BasicForms extends PureComponent {
+ handleSubmit = e => {
+ const { dispatch, form } = this.props;
+ e.preventDefault();
+ form.validateFieldsAndScroll((err, values) => {
+ if (!err) {
+ dispatch({
+ type: 'form/submitRegularForm',
+ payload: values,
+ });
+ }
+ });
+ };
+
+ render() {
+ const { submitting } = this.props;
+ const {
+ form: { getFieldDecorator, getFieldValue },
+ } = this.props;
+
+ const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 7 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 12 },
+ md: { span: 10 },
+ },
+ };
+
+ const submitFormLayout = {
+ wrapperCol: {
+ xs: { span: 24, offset: 0 },
+ sm: { span: 10, offset: 7 },
+ },
+ };
+
+ return (
+ }
+ content={ }
+ >
+
+
+
+
+ );
+ }
+}
+
+export default BasicForms;
diff --git a/src/pages/Forms/StepForm/_layout.js b/src/pages/Form/StepForm/_layout.js
similarity index 79%
rename from src/pages/Forms/StepForm/_layout.js
rename to src/pages/Form/StepForm/_layout.js
index 41b931fa..98902a0a 100644
--- a/src/pages/Forms/StepForm/_layout.js
+++ b/src/pages/Form/StepForm/_layout.js
@@ -1,7 +1,6 @@
import React, { PureComponent, Fragment } from 'react';
import { Card, Steps } from 'antd';
-import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
-// import NotFound from '../../Exception/404';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from '../style.less';
const { Step } = Steps;
@@ -12,21 +11,23 @@ export default class StepForm extends PureComponent {
const { pathname } = location;
const pathList = pathname.split('/');
switch (pathList[pathList.length - 1]) {
- case 'Step1':
+ case 'info':
return 0;
- case 'Step2':
+ case 'confirm':
return 1;
- case 'Step3':
+ case 'result':
return 2;
default:
return 0;
}
}
+
render() {
- const { children } = this.props;
+ const { location, children } = this.props;
return (
-
@@ -39,7 +40,7 @@ export default class StepForm extends PureComponent {
{children}
-
+
);
}
}
diff --git a/src/pages/Forms/StepForm/Step2.js b/src/pages/Form/StepForm/confirm.js
similarity index 62%
rename from src/pages/Forms/StepForm/Step2.js
rename to src/pages/Form/StepForm/confirm.js
index 6cbde098..8457dd18 100644
--- a/src/pages/Forms/StepForm/Step2.js
+++ b/src/pages/Form/StepForm/confirm.js
@@ -1,8 +1,8 @@
import React from 'react';
import { connect } from 'dva';
import { Form, Input, Button, Alert, Divider } from 'antd';
-import { routerRedux } from 'dva/router';
-import { digitUppercase } from '../../../utils/utils';
+import router from 'umi/router';
+import { digitUppercase } from '@/utils/utils';
import styles from './style.less';
const formItemLayout = {
@@ -14,15 +14,19 @@ const formItemLayout = {
},
};
+@connect(({ form, loading }) => ({
+ submitting: loading.effects['form/submitStepForm'],
+ data: form.step,
+}))
@Form.create()
class Step2 extends React.PureComponent {
render() {
const { form, data, dispatch, submitting } = this.props;
const { getFieldDecorator, validateFields } = form;
const onPrev = () => {
- dispatch(routerRedux.push('/Forms/StepForm'));
+ router.push('/form/stepform/info');
};
- const onValidateForm = (e) => {
+ const onValidateForm = e => {
e.preventDefault();
validateFields((err, values) => {
if (!err) {
@@ -44,55 +48,39 @@ class Step2 extends React.PureComponent {
message="确认转账后,资金将直接打入对方账户,无法退回。"
style={{ marginBottom: 24 }}
/>
-
+
{data.payAccount}
-
+
{data.receiverAccount}
-
+
{data.receiverName}
-
+
{data.amount}
({digitUppercase(data.amount)})
-
+
{getFieldDecorator('password', {
initialValue: '123456',
- rules: [{
- required: true, message: '需要支付密码才能进行支付',
- }],
- })(
-
- )}
+ rules: [
+ {
+ required: true,
+ message: '需要支付密码才能进行支付',
+ },
+ ],
+ })( )}
@@ -108,7 +96,4 @@ class Step2 extends React.PureComponent {
}
}
-export default connect(({ form, loading }) => ({
- submitting: loading.effects['form/submitStepForm'],
- data: form.step,
-}))(Step2);
+export default Step2;
diff --git a/src/pages/Forms/StepForm/Step1.js b/src/pages/Form/StepForm/info.js
similarity index 64%
rename from src/pages/Forms/StepForm/Step1.js
rename to src/pages/Form/StepForm/info.js
index 30cc4ce9..27d33c38 100644
--- a/src/pages/Forms/StepForm/Step1.js
+++ b/src/pages/Form/StepForm/info.js
@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { connect } from 'dva';
import { Form, Input, Button, Select, Divider } from 'antd';
-import { routerRedux } from 'dva/router';
+import router from 'umi/router';
import styles from './style.less';
const { Option } = Select;
@@ -15,6 +15,9 @@ const formItemLayout = {
},
};
+@connect(({ form }) => ({
+ data: form.step,
+}))
@Form.create()
class Step1 extends React.PureComponent {
render() {
@@ -27,17 +30,14 @@ class Step1 extends React.PureComponent {
type: 'form/saveStepFormData',
payload: values,
});
- dispatch(routerRedux.push('/Forms/StepForm/Step2'));
+ router.push('/form/stepform/confirm');
}
});
};
return (
-
+
{getFieldDecorator('payAccount', {
initialValue: data.payAccount,
rules: [{ required: true, message: '请选择付款账户' }],
@@ -47,10 +47,7 @@ class Step1 extends React.PureComponent {
)}
-
+
支付宝
@@ -62,40 +59,34 @@ class Step1 extends React.PureComponent {
{ required: true, message: '请输入收款人账户' },
{ type: 'email', message: '账户名应为邮箱格式' },
],
- })(
-
- )}
+ })( )}
-
+
{getFieldDecorator('receiverName', {
initialValue: data.receiverName,
rules: [{ required: true, message: '请输入收款人姓名' }],
- })(
-
- )}
+ })( )}
-
+
{getFieldDecorator('amount', {
initialValue: data.amount,
rules: [
{ required: true, message: '请输入转账金额' },
- { pattern: /^(\d+)((?:\.\d+)?)$/, message: '请输入合法金额数字' },
+ {
+ pattern: /^(\d+)((?:\.\d+)?)$/,
+ message: '请输入合法金额数字',
+ },
],
- })(
-
- )}
+ })( )}
@@ -108,15 +99,17 @@ class Step1 extends React.PureComponent {
说明
转账到支付宝账户
-
如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。
+
+ 如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。
+
转账到银行卡
-
如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。
+
+ 如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。
+
);
}
}
-export default connect(({ form }) => ({
- data: form.step,
-}))(Step1);
+export default Step1;
diff --git a/src/pages/Forms/StepForm/Step3.js b/src/pages/Form/StepForm/result.js
similarity index 87%
rename from src/pages/Forms/StepForm/Step3.js
rename to src/pages/Form/StepForm/result.js
index eb8afeac..da479807 100644
--- a/src/pages/Forms/StepForm/Step3.js
+++ b/src/pages/Form/StepForm/result.js
@@ -1,15 +1,18 @@
import React, { Fragment } from 'react';
import { connect } from 'dva';
import { Button, Row, Col } from 'antd';
-import { routerRedux } from 'dva/router';
-import Result from 'components/Result';
+import router from 'umi/router';
+import Result from '@/components/Result';
import styles from './style.less';
+@connect(({ form }) => ({
+ data: form.step,
+}))
class Step3 extends React.PureComponent {
render() {
- const { dispatch, data } = this.props;
+ const { data } = this.props;
const onFinish = () => {
- dispatch(routerRedux.push('/Forms/StepForm'));
+ router.push('/form/stepform/info');
};
const information = (
@@ -68,6 +71,4 @@ class Step3 extends React.PureComponent {
}
}
-export default connect(({ form }) => ({
- data: form.step,
-}))(Step3);
+export default Step3;
diff --git a/src/pages/Forms/StepForm/style.less b/src/pages/Form/StepForm/style.less
similarity index 92%
rename from src/pages/Forms/StepForm/style.less
rename to src/pages/Form/StepForm/style.less
index 0320e113..713dd54c 100644
--- a/src/pages/Forms/StepForm/style.less
+++ b/src/pages/Form/StepForm/style.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.stepForm {
margin: 40px auto 0;
@@ -67,7 +67,7 @@
}
.money {
- font-family: "Helvetica Neue", sans-serif;
+ font-family: 'Helvetica Neue', sans-serif;
font-weight: 500;
font-size: 20px;
line-height: 14px;
diff --git a/src/pages/Form/TableForm.js b/src/pages/Form/TableForm.js
new file mode 100644
index 00000000..ae2723e7
--- /dev/null
+++ b/src/pages/Form/TableForm.js
@@ -0,0 +1,261 @@
+import React, { PureComponent, Fragment } from 'react';
+import { Table, Button, Input, message, Popconfirm, Divider } from 'antd';
+import isEqual from 'lodash/isEqual';
+import styles from './style.less';
+
+class TableForm extends PureComponent {
+ index = 0;
+
+ cacheOriginData = {};
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ data: props.value,
+ loading: false,
+ /* eslint-disable-next-line react/no-unused-state */
+ value: props.value,
+ };
+ }
+
+ static getDerivedStateFromProps(nextProps, preState) {
+ if (isEqual(nextProps.value, preState.value)) {
+ return null;
+ }
+ return {
+ data: nextProps.value,
+ value: nextProps.value,
+ };
+ }
+
+ getRowByKey(key, newData) {
+ const { data } = this.state;
+ return (newData || data).filter(item => item.key === key)[0];
+ }
+
+ toggleEditable = (e, key) => {
+ e.preventDefault();
+ const { data } = this.state;
+ const newData = data.map(item => ({ ...item }));
+ const target = this.getRowByKey(key, newData);
+ if (target) {
+ // 进入编辑状态时保存原始数据
+ if (!target.editable) {
+ this.cacheOriginData[key] = { ...target };
+ }
+ target.editable = !target.editable;
+ this.setState({ data: newData });
+ }
+ };
+
+ newMember = () => {
+ const { data } = this.state;
+ const newData = data.map(item => ({ ...item }));
+ newData.push({
+ key: `NEW_TEMP_ID_${this.index}`,
+ workId: '',
+ name: '',
+ department: '',
+ editable: true,
+ isNew: true,
+ });
+ this.index += 1;
+ this.setState({ data: newData });
+ };
+
+ remove(key) {
+ const { data } = this.state;
+ const { onChange } = this.props;
+ const newData = data.filter(item => item.key !== key);
+ this.setState({ data: newData });
+ onChange(newData);
+ }
+
+ handleKeyPress(e, key) {
+ if (e.key === 'Enter') {
+ this.saveRow(e, key);
+ }
+ }
+
+ handleFieldChange(e, fieldName, key) {
+ const { data } = this.state;
+ const newData = data.map(item => ({ ...item }));
+ const target = this.getRowByKey(key, newData);
+ if (target) {
+ target[fieldName] = e.target.value;
+ this.setState({ data: newData });
+ }
+ }
+
+ saveRow(e, key) {
+ e.persist();
+ this.setState({
+ loading: true,
+ });
+ setTimeout(() => {
+ if (this.clickedCancel) {
+ this.clickedCancel = false;
+ return;
+ }
+ const target = this.getRowByKey(key) || {};
+ if (!target.workId || !target.name || !target.department) {
+ message.error('请填写完整成员信息。');
+ e.target.focus();
+ this.setState({
+ loading: false,
+ });
+ return;
+ }
+ delete target.isNew;
+ this.toggleEditable(e, key);
+ const { data } = this.state;
+ const { onChange } = this.props;
+ onChange(data);
+ this.setState({
+ loading: false,
+ });
+ }, 500);
+ }
+
+ cancel(e, key) {
+ this.clickedCancel = true;
+ e.preventDefault();
+ const { data } = this.state;
+ const newData = data.map(item => ({ ...item }));
+ const target = this.getRowByKey(key, newData);
+ if (this.cacheOriginData[key]) {
+ Object.assign(target, this.cacheOriginData[key]);
+ delete this.cacheOriginData[key];
+ }
+ target.editable = false;
+ this.setState({ data: newData });
+ this.clickedCancel = false;
+ }
+
+ render() {
+ const columns = [
+ {
+ title: '成员姓名',
+ dataIndex: 'name',
+ key: 'name',
+ width: '20%',
+ render: (text, record) => {
+ if (record.editable) {
+ return (
+
this.handleFieldChange(e, 'name', record.key)}
+ onKeyPress={e => this.handleKeyPress(e, record.key)}
+ placeholder="成员姓名"
+ />
+ );
+ }
+ return text;
+ },
+ },
+ {
+ title: '工号',
+ dataIndex: 'workId',
+ key: 'workId',
+ width: '20%',
+ render: (text, record) => {
+ if (record.editable) {
+ return (
+
this.handleFieldChange(e, 'workId', record.key)}
+ onKeyPress={e => this.handleKeyPress(e, record.key)}
+ placeholder="工号"
+ />
+ );
+ }
+ return text;
+ },
+ },
+ {
+ title: '所属部门',
+ dataIndex: 'department',
+ key: 'department',
+ width: '40%',
+ render: (text, record) => {
+ if (record.editable) {
+ return (
+
this.handleFieldChange(e, 'department', record.key)}
+ onKeyPress={e => this.handleKeyPress(e, record.key)}
+ placeholder="所属部门"
+ />
+ );
+ }
+ return text;
+ },
+ },
+ {
+ title: '操作',
+ key: 'action',
+ render: (text, record) => {
+ const { loading } = this.state;
+ if (!!record.editable && loading) {
+ return null;
+ }
+ if (record.editable) {
+ if (record.isNew) {
+ return (
+
+ this.saveRow(e, record.key)}>添加
+
+ this.remove(record.key)}>
+ 删除
+
+
+ );
+ }
+ return (
+
+ this.saveRow(e, record.key)}>保存
+
+ this.cancel(e, record.key)}>取消
+
+ );
+ }
+ return (
+
+ this.toggleEditable(e, record.key)}>编辑
+
+ this.remove(record.key)}>
+ 删除
+
+
+ );
+ },
+ },
+ ];
+
+ const { loading, data } = this.state;
+
+ return (
+
+ (record.editable ? styles.editable : '')}
+ />
+
+ 新增成员
+
+
+ );
+ }
+}
+
+export default TableForm;
diff --git a/src/models/form.js b/src/pages/Form/models/form.js
similarity index 89%
rename from src/models/form.js
rename to src/pages/Form/models/form.js
index d849f802..21a2158b 100644
--- a/src/models/form.js
+++ b/src/pages/Form/models/form.js
@@ -1,6 +1,6 @@
import { routerRedux } from 'dva/router';
import { message } from 'antd';
-import { fakeSubmitForm } from '../services/api';
+import { fakeSubmitForm } from '@/services/api';
export default {
namespace: 'form',
@@ -25,7 +25,7 @@ export default {
type: 'saveStepFormData',
payload,
});
- yield put(routerRedux.push('/Forms/StepForm/Step3'));
+ yield put(routerRedux.push('/form/stepform/result'));
},
*submitAdvancedForm({ payload }, { call }) {
yield call(fakeSubmitForm, payload);
diff --git a/src/pages/Forms/style.less b/src/pages/Form/style.less
similarity index 94%
rename from src/pages/Forms/style.less
rename to src/pages/Form/style.less
index 64187e93..6b12d3c3 100644
--- a/src/pages/Forms/style.less
+++ b/src/pages/Form/style.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.card {
margin-bottom: 24px;
@@ -40,7 +40,7 @@
border-bottom: 1px solid @border-color-split;
padding: 8px 16px;
cursor: pointer;
- transition: all .3s;
+ transition: all 0.3s;
&:hover {
background: @primary-1;
}
diff --git a/src/pages/Forms/BasicForm.js b/src/pages/Forms/BasicForm.js
deleted file mode 100644
index bb1b3355..00000000
--- a/src/pages/Forms/BasicForm.js
+++ /dev/null
@@ -1,188 +0,0 @@
-import React, { PureComponent } from 'react';
-import { connect } from 'dva';
-import {
- Form, Input, DatePicker, Select, Button, Card, InputNumber, Radio, Icon, Tooltip,
-} from 'antd';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-import styles from './style.less';
-
-const FormItem = Form.Item;
-const { Option } = Select;
-const { RangePicker } = DatePicker;
-const { TextArea } = Input;
-
-@connect(({ loading }) => ({
- submitting: loading.effects['form/submitRegularForm'],
-}))
-@Form.create()
-export default class BasicForms extends PureComponent {
- handleSubmit = (e) => {
- e.preventDefault();
- this.props.form.validateFieldsAndScroll((err, values) => {
- if (!err) {
- this.props.dispatch({
- type: 'form/submitRegularForm',
- payload: values,
- });
- }
- });
- }
- render() {
- const { submitting } = this.props;
- const { getFieldDecorator, getFieldValue } = this.props.form;
-
- const formItemLayout = {
- labelCol: {
- xs: { span: 24 },
- sm: { span: 7 },
- },
- wrapperCol: {
- xs: { span: 24 },
- sm: { span: 12 },
- md: { span: 10 },
- },
- };
-
- const submitFormLayout = {
- wrapperCol: {
- xs: { span: 24, offset: 0 },
- sm: { span: 10, offset: 7 },
- },
- };
-
- return (
-
-
-
-
- {getFieldDecorator('title', {
- rules: [{
- required: true, message: '请输入标题',
- }],
- })(
-
- )}
-
-
- {getFieldDecorator('date', {
- rules: [{
- required: true, message: '请选择起止日期',
- }],
- })(
-
- )}
-
-
- {getFieldDecorator('goal', {
- rules: [{
- required: true, message: '请输入目标描述',
- }],
- })(
-
- )}
-
-
- {getFieldDecorator('standard', {
- rules: [{
- required: true, message: '请输入衡量标准',
- }],
- })(
-
- )}
-
-
- 客户
-
- (选填)
-
-
-
-
-
- }
- >
- {getFieldDecorator('client')(
-
- )}
-
- 邀评人(选填) }
- >
- {getFieldDecorator('invites')(
-
- )}
-
- 权重(选填) }
- >
- {getFieldDecorator('weight')(
-
- )}
- %
-
-
-
- {getFieldDecorator('public', {
- initialValue: '1',
- })(
-
- 公开
- 部分公开
- 不公开
-
- )}
-
- {getFieldDecorator('publicUsers')(
-
- 同事甲
- 同事乙
- 同事丙
-
- )}
-
-
-
-
-
- 提交
-
- 保存
-
-
-
-
- );
- }
-}
diff --git a/src/pages/Forms/TableForm.js b/src/pages/Forms/TableForm.js
deleted file mode 100644
index 0afd7d1a..00000000
--- a/src/pages/Forms/TableForm.js
+++ /dev/null
@@ -1,227 +0,0 @@
-import React, { PureComponent, Fragment } from 'react';
-import { Table, Button, Input, message, Popconfirm, Divider } from 'antd';
-import styles from './style.less';
-
-export default class TableForm extends PureComponent {
- constructor(props) {
- super(props);
-
- this.state = {
- data: props.value,
- loading: false,
- };
- }
- UNSAFE_componentWillReceiveProps(nextProps) {
- if ('value' in nextProps) {
- this.setState({
- data: nextProps.value,
- });
- }
- }
- getRowByKey(key, newData) {
- return (newData || this.state.data).filter(item => item.key === key)[0];
- }
- index = 0;
- cacheOriginData = {};
- toggleEditable=(e, key) => {
- e.preventDefault();
- const newData = this.state.data.map(item => ({ ...item }));
- const target = this.getRowByKey(key, newData);
- if (target) {
- // 进入编辑状态时保存原始数据
- if (!target.editable) {
- this.cacheOriginData[key] = { ...target };
- }
- target.editable = !target.editable;
- this.setState({ data: newData });
- }
- }
- remove(key) {
- const newData = this.state.data.filter(item => item.key !== key);
- this.setState({ data: newData });
- this.props.onChange(newData);
- }
- newMember = () => {
- const newData = this.state.data.map(item => ({ ...item }));
- newData.push({
- key: `NEW_TEMP_ID_${this.index}`,
- workId: '',
- name: '',
- department: '',
- editable: true,
- isNew: true,
- });
- this.index += 1;
- this.setState({ data: newData });
- }
- handleKeyPress(e, key) {
- if (e.key === 'Enter') {
- this.saveRow(e, key);
- }
- }
- handleFieldChange(e, fieldName, key) {
- const newData = this.state.data.map(item => ({ ...item }));
- const target = this.getRowByKey(key, newData);
- if (target) {
- target[fieldName] = e.target.value;
- this.setState({ data: newData });
- }
- }
- saveRow(e, key) {
- e.persist();
- this.setState({
- loading: true,
- });
- setTimeout(() => {
- if (this.clickedCancel) {
- this.clickedCancel = false;
- return;
- }
- const target = this.getRowByKey(key) || {};
- if (!target.workId || !target.name || !target.department) {
- message.error('请填写完整成员信息。');
- e.target.focus();
- this.setState({
- loading: false,
- });
- return;
- }
- delete target.isNew;
- this.toggleEditable(e, key);
- this.props.onChange(this.state.data);
- this.setState({
- loading: false,
- });
- }, 500);
- }
- cancel(e, key) {
- this.clickedCancel = true;
- e.preventDefault();
- const newData = this.state.data.map(item => ({ ...item }));
- const target = this.getRowByKey(key, newData);
- if (this.cacheOriginData[key]) {
- Object.assign(target, this.cacheOriginData[key]);
- target.editable = false;
- delete this.cacheOriginData[key];
- }
- this.setState({ data: newData });
- this.clickedCancel = false;
- }
- render() {
- const columns = [{
- title: '成员姓名',
- dataIndex: 'name',
- key: 'name',
- width: '20%',
- render: (text, record) => {
- if (record.editable) {
- return (
- this.handleFieldChange(e, 'name', record.key)}
- onKeyPress={e => this.handleKeyPress(e, record.key)}
- placeholder="成员姓名"
- />
- );
- }
- return text;
- },
- }, {
- title: '工号',
- dataIndex: 'workId',
- key: 'workId',
- width: '20%',
- render: (text, record) => {
- if (record.editable) {
- return (
- this.handleFieldChange(e, 'workId', record.key)}
- onKeyPress={e => this.handleKeyPress(e, record.key)}
- placeholder="工号"
- />
- );
- }
- return text;
- },
- }, {
- title: '所属部门',
- dataIndex: 'department',
- key: 'department',
- width: '40%',
- render: (text, record) => {
- if (record.editable) {
- return (
- this.handleFieldChange(e, 'department', record.key)}
- onKeyPress={e => this.handleKeyPress(e, record.key)}
- placeholder="所属部门"
- />
- );
- }
- return text;
- },
- }, {
- title: '操作',
- key: 'action',
- render: (text, record) => {
- if (!!record.editable && this.state.loading) {
- return null;
- }
- if (record.editable) {
- if (record.isNew) {
- return (
-
- this.saveRow(e, record.key)}>添加
-
- this.remove(record.key)}>
- 删除
-
-
- );
- }
- return (
-
- this.saveRow(e, record.key)}>保存
-
- this.cancel(e, record.key)}>取消
-
- );
- }
- return (
-
- this.toggleEditable(e, record.key)}>编辑
-
- this.remove(record.key)}>
- 删除
-
-
- );
- },
- }];
-
- return (
-
- {
- return record.editable ? styles.editable : '';
- }}
- />
-
- 新增成员
-
-
- );
- }
-}
diff --git a/src/pages/List/BasicList.js b/src/pages/List/BasicList.js
index 0478cdfb..b52a2999 100644
--- a/src/pages/List/BasicList.js
+++ b/src/pages/List/BasicList.js
@@ -1,23 +1,53 @@
import React, { PureComponent } from 'react';
+import { findDOMNode } from 'react-dom';
import moment from 'moment';
import { connect } from 'dva';
-import { List, Card, Row, Col, Radio, Input, Progress, Button, Icon, Dropdown, Menu, Avatar } from 'antd';
+import {
+ List,
+ Card,
+ Row,
+ Col,
+ Radio,
+ Input,
+ Progress,
+ Button,
+ Icon,
+ Dropdown,
+ Menu,
+ Avatar,
+ Modal,
+ Form,
+ DatePicker,
+ Select,
+} from 'antd';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+import Result from '@/components/Result';
import styles from './BasicList.less';
+const FormItem = Form.Item;
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
-const { Search } = Input;
+const SelectOption = Select.Option;
+const { Search, TextArea } = Input;
@connect(({ list, loading }) => ({
list,
loading: loading.models.list,
}))
-export default class BasicList extends PureComponent {
+@Form.create()
+class BasicList extends PureComponent {
+ state = { visible: false, done: false };
+
+ formLayout = {
+ labelCol: { span: 7 },
+ wrapperCol: { span: 13 },
+ };
+
componentDidMount() {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'list/fetch',
payload: {
count: 5,
@@ -25,8 +55,88 @@ export default class BasicList extends PureComponent {
});
}
+ showModal = () => {
+ this.setState({
+ visible: true,
+ current: undefined,
+ });
+ };
+
+ showEditModal = item => {
+ this.setState({
+ visible: true,
+ current: item,
+ });
+ };
+
+ handleDone = () => {
+ setTimeout(() => this.addBtn.blur(), 0);
+ this.setState({
+ done: false,
+ visible: false,
+ });
+ };
+
+ handleCancel = () => {
+ setTimeout(() => this.addBtn.blur(), 0);
+ this.setState({
+ visible: false,
+ });
+ };
+
+ handleSubmit = e => {
+ e.preventDefault();
+ const { dispatch, form } = this.props;
+ const { current } = this.state;
+ const id = current ? current.id : '';
+
+ setTimeout(() => this.addBtn.blur(), 0);
+ form.validateFields((err, fieldsValue) => {
+ if (err) return;
+ this.setState({
+ done: true,
+ });
+ dispatch({
+ type: 'list/submit',
+ payload: { id, ...fieldsValue },
+ });
+ });
+ };
+
+ deleteItem = id => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'list/submit',
+ payload: { id },
+ });
+ };
+
render() {
- const { list: { list }, loading } = this.props;
+ const {
+ list: { list },
+ loading,
+ } = this.props;
+ const {
+ form: { getFieldDecorator },
+ } = this.props;
+ const { visible, done, current = {} } = this.state;
+
+ const editAndDelete = (key, currentItem) => {
+ if (key === 'edit') this.showEditModal(currentItem);
+ else if (key === 'delete') {
+ Modal.confirm({
+ title: '删除任务',
+ content: '确定删除该任务吗?',
+ okText: '确认',
+ cancelText: '取消',
+ onOk: () => this.deleteItem(currentItem.id),
+ });
+ }
+ };
+
+ const modalFooter = done
+ ? { footer: null, onCancel: this.handleDone }
+ : { okText: '保存', onOk: this.handleSubmit, onCancel: this.handleCancel };
const Info = ({ title, value, bordered }) => (
@@ -43,11 +153,7 @@ export default class BasicList extends PureComponent {
进行中
等待中
- ({})}
- />
+ ({})} />
);
@@ -74,27 +180,80 @@ export default class BasicList extends PureComponent {
);
- const menu = (
-
-
- 编辑
-
-
- 删除
-
-
- );
-
- const MoreBtn = () => (
-
+ const MoreBtn = props => (
+ editAndDelete(key, props.current)}>
+ 编辑
+ 删除
+
+ }
+ >
更多
);
+ const getModalContent = () => {
+ if (done) {
+ return (
+
+ 知道了
+
+ }
+ className={styles.formResult}
+ />
+ );
+ }
+ return (
+
+
+ {getFieldDecorator('title', {
+ rules: [{ required: true, message: '请输入任务名称' }],
+ initialValue: current.title,
+ })( )}
+
+
+ {getFieldDecorator('createdAt', {
+ rules: [{ required: true, message: '请选择开始时间' }],
+ initialValue: current.createdAt ? moment(current.createdAt) : null,
+ })(
+
+ )}
+
+
+ {getFieldDecorator('owner', {
+ rules: [{ required: true, message: '请选择任务负责人' }],
+ initialValue: current.owner,
+ })(
+
+ 付晓晓
+ 周毛毛
+
+ )}
+
+
+ {getFieldDecorator('subDescription', {
+ rules: [{ message: '请输入至少五个字符的产品描述!', min: 5 }],
+ initialValue: current.subDescription,
+ })( )}
+
+
+ );
+ };
return (
-
+
@@ -118,7 +277,17 @@ export default class BasicList extends PureComponent {
bodyStyle={{ padding: '0 32px 40px 32px' }}
extra={extraContent}
>
-
+ {
+ /* eslint-disable */
+ this.addBtn = findDOMNode(component);
+ /* eslint-enable */
+ }}
+ >
添加
(
编辑, ]}
+ actions={[
+ {
+ e.preventDefault();
+ this.showEditModal(item);
+ }}
+ >
+ 编辑
+ ,
+ ,
+ ]}
>
}
@@ -142,7 +321,20 @@ export default class BasicList extends PureComponent {
/>
-
+
+ {getModalContent()}
+
+
);
}
}
+
+export default BasicList;
diff --git a/src/pages/List/BasicList.less b/src/pages/List/BasicList.less
index 39457dc9..f086c501 100644
--- a/src/pages/List/BasicList.less
+++ b/src/pages/List/BasicList.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.standardList {
:global {
@@ -174,3 +174,22 @@
}
}
}
+
+.standardListForm {
+ :global {
+ .ant-form-item {
+ margin-bottom: 12px;
+ &:last-child {
+ padding-top: 4px;
+ margin-bottom: 32px;
+ }
+ }
+ }
+}
+
+.formResult {
+ width: 100%;
+ [class^='title'] {
+ margin-bottom: 8px;
+ }
+}
diff --git a/src/pages/List/CardList.js b/src/pages/List/CardList.js
index cd507865..03f799a1 100644
--- a/src/pages/List/CardList.js
+++ b/src/pages/List/CardList.js
@@ -2,8 +2,8 @@ import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { Card, Button, Icon, List } from 'antd';
-import Ellipsis from 'components/Ellipsis';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import Ellipsis from '@/components/Ellipsis';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './CardList.less';
@@ -11,9 +11,10 @@ import styles from './CardList.less';
list,
loading: loading.models.list,
}))
-export default class CardList extends PureComponent {
+class CardList extends PureComponent {
componentDidMount() {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'list/fetch',
payload: {
count: 8,
@@ -22,7 +23,10 @@ export default class CardList extends PureComponent {
}
render() {
- const { list: { list }, loading } = this.props;
+ const {
+ list: { list },
+ loading,
+ } = this.props;
const content = (
@@ -32,13 +36,16 @@ export default class CardList extends PureComponent {
@@ -46,34 +53,36 @@ export default class CardList extends PureComponent {
const extraContent = (
-
+
);
return (
-
+
(item ? (
-
- 操作一, 操作二 ]}>
- }
- title={{item.title} }
- description={(
- {item.description}
- )}
- />
-
-
+ renderItem={item =>
+ item ? (
+
+ 操作一, 操作二 ]}>
+ }
+ title={{item.title} }
+ description={
+
+ {item.description}
+
+ }
+ />
+
+
) : (
@@ -81,10 +90,12 @@ export default class CardList extends PureComponent {
)
- )}
+ }
/>
-
+
);
}
}
+
+export default CardList;
diff --git a/src/pages/List/CardList.less b/src/pages/List/CardList.less
index c4c2e573..3d6feacc 100644
--- a/src/pages/List/CardList.less
+++ b/src/pages/List/CardList.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.cardList {
margin-bottom: -24px;
@@ -10,6 +10,8 @@
margin-bottom: 12px;
& > a {
color: @heading-color;
+ display: inline-block;
+ max-width: 100%;
}
}
.ant-card-actions {
diff --git a/src/pages/List/Search/Applications.js b/src/pages/List/Search/Applications.js
index 368a3e58..bd28fb9e 100644
--- a/src/pages/List/Search/Applications.js
+++ b/src/pages/List/Search/Applications.js
@@ -2,36 +2,38 @@ import React, { PureComponent } from 'react';
import numeral from 'numeral';
import { connect } from 'dva';
import { Row, Col, Form, Card, Select, Icon, Avatar, List, Tooltip, Dropdown, Menu } from 'antd';
+import TagSelect from '@/components/TagSelect';
+import StandardFormRow from '@/components/StandardFormRow';
-import TagSelect from 'components/TagSelect';
-import StandardFormRow from 'components/StandardFormRow';
+import { formatWan } from '@/utils/utils';
import styles from './Applications.less';
const { Option } = Select;
const FormItem = Form.Item;
-const formatWan = (val) => {
- const v = val * 1;
- if (!v || isNaN(v)) return '';
-
- let result = val;
- if (val > 10000) {
- result = Math.floor(val / 10000);
- result = {result}万 ;
- }
- return result;
-};
-
-/* eslint react/no-array-index-key: 0 */
-@Form.create()
@connect(({ list, loading }) => ({
list,
loading: loading.models.list,
}))
-export default class FilterCardList extends PureComponent {
+@Form.create({
+ onValuesChange({ dispatch }, changedValues, allValues) {
+ // 表单项变化时请求数据
+ // eslint-disable-next-line
+ console.log(changedValues, allValues);
+ // 模拟查询表单生效
+ dispatch({
+ type: 'list/fetch',
+ payload: {
+ count: 8,
+ },
+ });
+ },
+})
+class FilterCardList extends PureComponent {
componentDidMount() {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'list/fetch',
payload: {
count: 8,
@@ -39,26 +41,12 @@ export default class FilterCardList extends PureComponent {
});
}
- handleFormSubmit = () => {
- const { form, dispatch } = this.props;
- // setTimeout 用于保证获取表单值是在所有表单字段更新完毕的时候
- setTimeout(() => {
- form.validateFields((err) => {
- if (!err) {
- // eslint-disable-next-line
- dispatch({
- type: 'list/fetch',
- payload: {
- count: 8,
- },
- });
- }
- });
- }, 0);
- }
-
render() {
- const { list: { list }, loading, form } = this.props;
+ const {
+ list: { list },
+ loading,
+ form,
+ } = this.props;
const { getFieldDecorator } = form;
const CardInfo = ({ activeUser, newUser }) => (
@@ -84,13 +72,19 @@ export default class FilterCardList extends PureComponent {
const itemMenu = (
- 1st menu item
+
+ 1st menu item
+
- 2nd menu item
+
+ 2nd menu item
+
- 3d menu item
+
+ 3d menu item
+
);
@@ -102,7 +96,7 @@ export default class FilterCardList extends PureComponent {
{getFieldDecorator('category')(
-
+
类目一
类目二
类目三
@@ -119,39 +113,21 @@ export default class FilterCardList extends PureComponent {
)}
-
+
-
+
{getFieldDecorator('author', {})(
-
+
王昭君
)}
-
+
{getFieldDecorator('rate', {})(
-
+
优秀
普通
@@ -174,16 +150,21 @@ export default class FilterCardList extends PureComponent {
hoverable
bodyStyle={{ paddingBottom: 20 }}
actions={[
- ,
- ,
- ,
- ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
]}
>
- }
- title={item.title}
- />
+ } title={item.title} />
({
list,
loading: loading.models.list,
}))
-export default class SearchList extends Component {
+@Form.create({
+ onValuesChange({ dispatch }, changedValues, allValues) {
+ // 表单项变化时请求数据
+ // eslint-disable-next-line
+ console.log(changedValues, allValues);
+ // 模拟查询表单生效
+ dispatch({
+ type: 'list/fetch',
+ payload: {
+ count: 5,
+ },
+ });
+ },
+})
+class SearchList extends Component {
componentDidMount() {
- this.fetchMore();
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'list/fetch',
+ payload: {
+ count: 5,
+ },
+ });
}
setOwner = () => {
@@ -27,19 +46,24 @@ export default class SearchList extends Component {
form.setFieldsValue({
owner: ['wzj'],
});
- }
+ };
fetchMore = () => {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'list/appendFetch',
payload: {
count: pageSize,
},
});
- }
+ };
render() {
- const { form, list: { list }, loading } = this.props;
+ const {
+ form,
+ list: { list },
+ loading,
+ } = this.props;
const { getFieldDecorator } = form;
const owners = [
@@ -72,16 +96,6 @@ export default class SearchList extends Component {
);
- const ListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
-
-
{content}
-
-
{owner} 发布在
{href}
-
{moment(updatedAt).format('YYYY-MM-DD HH:mm')}
-
-
- );
-
const formItemLayout = {
wrapperCol: {
xs: { span: 24 },
@@ -90,13 +104,20 @@ export default class SearchList extends Component {
},
};
- const loadMore = list.length > 0 ? (
-
-
- {loading ? 加载中... : '加载更多'}
-
-
- ) : null;
+ const loadMore =
+ list.length > 0 ? (
+
+
+ {loading ? (
+
+ 加载中...
+
+ ) : (
+ '加载更多'
+ )}
+
+
+ ) : null;
return (
@@ -105,7 +126,7 @@ export default class SearchList extends Component {
{getFieldDecorator('category')(
-
+
类目一
类目二
类目三
@@ -122,10 +143,7 @@ export default class SearchList extends Component {
)}
-
+
@@ -137,51 +155,35 @@ export default class SearchList extends Component {
style={{ maxWidth: 286, width: '100%' }}
placeholder="选择 owner"
>
- {
- owners.map(owner =>
- {owner.name}
- )
- }
+ {owners.map(owner => (
+
+ {owner.name}
+
+ ))}
)}
- 只看自己的
+
+ 只看自己的
+
-
+
-
+
{getFieldDecorator('user', {})(
-
+
李三
)}
-
+
{getFieldDecorator('rate', {})(
-
+
优秀
)}
@@ -214,9 +216,11 @@ export default class SearchList extends Component {
extra={
}
>
{item.title}
- )}
+ title={
+
+ {item.title}
+
+ }
description={
Ant Design
@@ -225,7 +229,7 @@ export default class SearchList extends Component {
}
/>
-
+
)}
/>
@@ -234,3 +238,5 @@ export default class SearchList extends Component {
);
}
}
+
+export default SearchList;
diff --git a/src/pages/List/Search/Articles.less b/src/pages/List/Search/Articles.less
index 4017ed64..3ec95015 100644
--- a/src/pages/List/Search/Articles.less
+++ b/src/pages/List/Search/Articles.less
@@ -1,30 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "~utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
-.listContent {
- .description {
- line-height: 22px;
- max-width: 720px;
- }
- .extra {
- color: @text-color-secondary;
- margin-top: 16px;
- line-height: 22px;
- & > :global(.ant-avatar) {
- vertical-align: top;
- margin-right: 8px;
- width: 20px;
- height: 20px;
- position: relative;
- top: 1px;
- }
- & > em {
- color: @disabled-color;
- font-style: normal;
- margin-left: 16px;
- }
- }
-}
a.listItemMetaTitle {
color: @heading-color;
}
@@ -41,15 +16,6 @@ a.listItemMetaTitle {
display: block;
margin-left: 0;
}
- .listContent {
- .extra {
- & > em {
- display: block;
- margin-left: 0;
- margin-top: 8px;
- }
- }
- }
}
@media screen and (max-width: @screen-md) {
.selfTrigger {
diff --git a/src/pages/List/Search/Projects.js b/src/pages/List/Search/Projects.js
index d285dea3..430ca420 100644
--- a/src/pages/List/Search/Projects.js
+++ b/src/pages/List/Search/Projects.js
@@ -3,10 +3,10 @@ import moment from 'moment';
import { connect } from 'dva';
import { Row, Col, Form, Card, Select, List } from 'antd';
-import TagSelect from 'components/TagSelect';
-import AvatarList from 'components/AvatarList';
-import Ellipsis from 'components/Ellipsis';
-import StandardFormRow from 'components/StandardFormRow';
+import TagSelect from '@/components/TagSelect';
+import AvatarList from '@/components/AvatarList';
+import Ellipsis from '@/components/Ellipsis';
+import StandardFormRow from '@/components/StandardFormRow';
import styles from './Projects.less';
@@ -14,14 +14,29 @@ const { Option } = Select;
const FormItem = Form.Item;
/* eslint react/no-array-index-key: 0 */
-@Form.create()
+
@connect(({ list, loading }) => ({
list,
loading: loading.models.list,
}))
-export default class CoverCardList extends PureComponent {
+@Form.create({
+ onValuesChange({ dispatch }, changedValues, allValues) {
+ // 表单项变化时请求数据
+ // eslint-disable-next-line
+ console.log(changedValues, allValues);
+ // 模拟查询表单生效
+ dispatch({
+ type: 'list/fetch',
+ payload: {
+ count: 8,
+ },
+ });
+ },
+})
+class CoverCardList extends PureComponent {
componentDidMount() {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'list/fetch',
payload: {
count: 8,
@@ -29,26 +44,12 @@ export default class CoverCardList extends PureComponent {
});
}
- handleFormSubmit = () => {
- const { form, dispatch } = this.props;
- // setTimeout 用于保证获取表单值是在所有表单字段更新完毕的时候
- setTimeout(() => {
- form.validateFields((err) => {
- if (!err) {
- // eslint-disable-next-line
- dispatch({
- type: 'list/fetch',
- payload: {
- count: 8,
- },
- });
- }
- });
- }, 0);
- };
-
render() {
- const { list: { list = [] }, loading, form } = this.props;
+ const {
+ list: { list = [] },
+ loading,
+ form,
+ } = this.props;
const { getFieldDecorator } = form;
const cardList = list ? (
@@ -62,10 +63,10 @@ export default class CoverCardList extends PureComponent {
}
+ cover={ }
>
{item.title}}
+ title={{item.title} }
description={{item.subDescription} }
/>
@@ -102,7 +103,7 @@ export default class CoverCardList extends PureComponent {
{getFieldDecorator('category')(
-
+
类目一
类目二
类目三
@@ -124,11 +125,7 @@ export default class CoverCardList extends PureComponent {
{getFieldDecorator('author', {})(
-
+
王昭君
)}
@@ -137,11 +134,7 @@ export default class CoverCardList extends PureComponent {
{getFieldDecorator('rate', {})(
-
+
优秀
普通
@@ -157,3 +150,5 @@ export default class CoverCardList extends PureComponent {
);
}
}
+
+export default CoverCardList;
diff --git a/src/pages/List/Search/Projects.less b/src/pages/List/Search/Projects.less
index cf69c5d7..43b8d726 100644
--- a/src/pages/List/Search/Projects.less
+++ b/src/pages/List/Search/Projects.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "~utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.coverCardList {
margin-bottom: -24px;
@@ -10,6 +10,8 @@
margin-bottom: 4px;
& > a {
color: @heading-color;
+ display: inline-block;
+ max-width: 100%;
}
}
.ant-card-meta-description {
diff --git a/src/pages/List/Search/_layout.js b/src/pages/List/Search/_layout.js
index 7da9f42c..d420af59 100644
--- a/src/pages/List/Search/_layout.js
+++ b/src/pages/List/Search/_layout.js
@@ -1,42 +1,46 @@
import React, { Component } from 'react';
-import { routerRedux } from 'dva/router';
+import router from 'umi/router';
import { connect } from 'dva';
import { Input } from 'antd';
-import PageHeaderLayout from '../../../layouts/PageHeaderLayout';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
@connect()
-export default class SearchList extends Component {
+class SearchList extends Component {
handleTabChange = key => {
- const { dispatch, match } = this.props;
-
+ const { match } = this.props;
switch (key) {
- case 'Search/Articles':
- dispatch(routerRedux.push(`${match.url}/Search/Articles`));
+ case 'articles':
+ router.push(`${match.url}/articles`);
break;
- case 'Search/Applications':
- dispatch(routerRedux.push(`${match.url}/Search/Applications`));
+ case 'applications':
+ router.push(`${match.url}/applications`);
break;
- case 'Search/Projects':
- dispatch(routerRedux.push(`${match.url}/Search/Projects`));
+ case 'projects':
+ router.push(`${match.url}/projects`);
break;
default:
break;
}
};
+ handleFormSubmit = value => {
+ // eslint-disable-next-line
+ console.log(value);
+ };
+
render() {
const tabList = [
{
- key: 'Search/Articles',
+ key: 'articles',
tab: '文章',
},
{
- key: 'Search/Applications',
- tab: '应用',
+ key: 'projects',
+ tab: '项目',
},
{
- key: 'Search/Projects',
- tab: '项目',
+ key: 'applications',
+ tab: '应用',
},
];
@@ -52,10 +56,10 @@ export default class SearchList extends Component {
);
- const { match, location,children } = this.props;
+ const { match, children, location } = this.props;
return (
-
{children}
-
+ {/*
+ {routes.map(item => (
+
+ ))}
+ */}
+
);
}
}
+
+export default SearchList;
diff --git a/src/pages/List/TableList.js b/src/pages/List/TableList.js
index 24d20ea9..1853dab6 100644
--- a/src/pages/List/TableList.js
+++ b/src/pages/List/TableList.js
@@ -1,79 +1,44 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import moment from 'moment';
-import { Row, Col, Card, Form, Input, Select, Icon, Button, Dropdown, Menu, InputNumber, DatePicker, Modal, message, Badge, Divider } from 'antd';
-import StandardTable from 'components/StandardTable';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import {
+ Row,
+ Col,
+ Card,
+ Form,
+ Input,
+ Select,
+ Icon,
+ Button,
+ Dropdown,
+ Menu,
+ InputNumber,
+ DatePicker,
+ Modal,
+ message,
+ Badge,
+ Divider,
+ Steps,
+ Radio,
+} from 'antd';
+import StandardTable from '@/components/StandardTable';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './TableList.less';
const FormItem = Form.Item;
+const { Step } = Steps;
+const { TextArea } = Input;
const { Option } = Select;
-const getValue = obj => Object.keys(obj).map(key => obj[key]).join(',');
+const RadioGroup = Radio.Group;
+const getValue = obj =>
+ Object.keys(obj)
+ .map(key => obj[key])
+ .join(',');
const statusMap = ['default', 'processing', 'success', 'error'];
const status = ['关闭', '运行中', '已上线', '异常'];
-const columns = [
- {
- title: '规则编号',
- dataIndex: 'no',
- },
- {
- title: '描述',
- dataIndex: 'description',
- },
- {
- title: '服务调用次数',
- dataIndex: 'callNo',
- sorter: true,
- align: 'right',
- render: val => `${val} 万`,
- // mark to display a total number
- needTotal: true,
- },
- {
- title: '状态',
- dataIndex: 'status',
- filters: [
- {
- text: status[0],
- value: 0,
- },
- {
- text: status[1],
- value: 1,
- },
- {
- text: status[2],
- value: 2,
- },
- {
- text: status[3],
- value: 3,
- },
- ],
- render(val) {
- return ;
- },
- },
- {
- title: '更新时间',
- dataIndex: 'updatedAt',
- sorter: true,
- render: val => {moment(val).format('YYYY-MM-DD HH:mm:ss')} ,
- },
- {
- title: '操作',
- render: () => (
-
- 配置
-
- 订阅警报
-
- ),
- },
-];
-
-const CreateForm = Form.create()((props) => {
+
+const CreateForm = Form.create()(props => {
const { modalVisible, form, handleAdd, handleModalVisible } = props;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
@@ -84,39 +49,299 @@ const CreateForm = Form.create()((props) => {
};
return (
handleModalVisible()}
>
-
+
{form.getFieldDecorator('desc', {
- rules: [{ required: true, message: 'Please input some description...' }],
- })(
-
- )}
+ rules: [{ required: true, message: '请输入至少五个字符的规则描述!', min: 5 }],
+ })( )}
);
});
+@Form.create()
+class UpdateForm extends PureComponent {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ formVals: {
+ name: props.values.name,
+ desc: props.values.desc,
+ key: props.values.key,
+ target: '0',
+ template: '0',
+ type: '1',
+ time: '',
+ frequency: 'month',
+ },
+ currentStep: 0,
+ };
+
+ this.formLayout = {
+ labelCol: { span: 7 },
+ wrapperCol: { span: 13 },
+ };
+ }
+
+ handleNext = currentStep => {
+ const { form, handleUpdate } = this.props;
+ const { formVals: oldValue } = this.state;
+ form.validateFields((err, fieldsValue) => {
+ if (err) return;
+ const formVals = { ...oldValue, ...fieldsValue };
+ this.setState(
+ {
+ formVals,
+ },
+ () => {
+ if (currentStep < 2) {
+ this.forward();
+ } else {
+ handleUpdate(formVals);
+ }
+ }
+ );
+ });
+ };
+
+ backward = () => {
+ const { currentStep } = this.state;
+ this.setState({
+ currentStep: currentStep - 1,
+ });
+ };
+
+ forward = () => {
+ const { currentStep } = this.state;
+ this.setState({
+ currentStep: currentStep + 1,
+ });
+ };
+
+ renderContent = (currentStep, formVals) => {
+ const { form } = this.props;
+ if (currentStep === 1) {
+ return [
+
+ {form.getFieldDecorator('target', {
+ initialValue: formVals.target,
+ })(
+
+ 表一
+ 表二
+
+ )}
+ ,
+
+ {form.getFieldDecorator('template', {
+ initialValue: formVals.template,
+ })(
+
+ 规则模板一
+ 规则模板二
+
+ )}
+ ,
+
+ {form.getFieldDecorator('type', {
+ initialValue: formVals.type,
+ })(
+
+ 强
+ 弱
+
+ )}
+ ,
+ ];
+ }
+ if (currentStep === 2) {
+ return [
+
+ {form.getFieldDecorator('time', {
+ rules: [{ required: true, message: '请选择开始时间!' }],
+ })(
+
+ )}
+ ,
+
+ {form.getFieldDecorator('frequency', {
+ initialValue: formVals.frequency,
+ })(
+
+ 月
+ 周
+
+ )}
+ ,
+ ];
+ }
+ return [
+
+ {form.getFieldDecorator('name', {
+ rules: [{ required: true, message: '请输入规则名称!' }],
+ initialValue: formVals.name,
+ })( )}
+ ,
+
+ {form.getFieldDecorator('desc', {
+ rules: [{ required: true, message: '请输入至少五个字符的规则描述!', min: 5 }],
+ initialValue: formVals.desc,
+ })( )}
+ ,
+ ];
+ };
+
+ renderFooter = currentStep => {
+ const { handleUpdateModalVisible } = this.props;
+ if (currentStep === 1) {
+ return [
+
+ 上一步
+ ,
+ handleUpdateModalVisible()}>
+ 取消
+ ,
+ this.handleNext(currentStep)}>
+ 下一步
+ ,
+ ];
+ }
+ if (currentStep === 2) {
+ return [
+
+ 上一步
+ ,
+ handleUpdateModalVisible()}>
+ 取消
+ ,
+ this.handleNext(currentStep)}>
+ 完成
+ ,
+ ];
+ }
+ return [
+ handleUpdateModalVisible()}>
+ 取消
+ ,
+ this.handleNext(currentStep)}>
+ 下一步
+ ,
+ ];
+ };
+
+ render() {
+ const { updateModalVisible, handleUpdateModalVisible } = this.props;
+ const { currentStep, formVals } = this.state;
+
+ return (
+ handleUpdateModalVisible()}
+ >
+
+
+
+
+
+ {this.renderContent(currentStep, formVals)}
+
+ );
+ }
+}
+
+/* eslint react/no-multi-comp:0 */
@connect(({ rule, loading }) => ({
rule,
loading: loading.models.rule,
}))
@Form.create()
-export default class TableList extends PureComponent {
+class TableList extends PureComponent {
state = {
modalVisible: false,
+ updateModalVisible: false,
expandForm: false,
selectedRows: [],
formValues: {},
+ stepFormValues: {},
};
+ columns = [
+ {
+ title: '规则名称',
+ dataIndex: 'name',
+ },
+ {
+ title: '描述',
+ dataIndex: 'desc',
+ },
+ {
+ title: '服务调用次数',
+ dataIndex: 'callNo',
+ sorter: true,
+ align: 'right',
+ render: val => `${val} 万`,
+ // mark to display a total number
+ needTotal: true,
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ filters: [
+ {
+ text: status[0],
+ value: 0,
+ },
+ {
+ text: status[1],
+ value: 1,
+ },
+ {
+ text: status[2],
+ value: 2,
+ },
+ {
+ text: status[3],
+ value: 3,
+ },
+ ],
+ render(val) {
+ return ;
+ },
+ },
+ {
+ title: '上次调度时间',
+ dataIndex: 'updatedAt',
+ sorter: true,
+ render: val => {moment(val).format('YYYY-MM-DD HH:mm:ss')} ,
+ },
+ {
+ title: '操作',
+ render: (text, record) => (
+
+ this.handleUpdateModalVisible(true, record)}>配置
+
+ 订阅警报
+
+ ),
+ },
+ ];
+
componentDidMount() {
const { dispatch } = this.props;
dispatch({
@@ -148,7 +373,7 @@ export default class TableList extends PureComponent {
type: 'rule/fetch',
payload: params,
});
- }
+ };
handleFormReset = () => {
const { form, dispatch } = this.props;
@@ -160,26 +385,26 @@ export default class TableList extends PureComponent {
type: 'rule/fetch',
payload: {},
});
- }
+ };
toggleForm = () => {
+ const { expandForm } = this.state;
this.setState({
- expandForm: !this.state.expandForm,
+ expandForm: !expandForm,
});
- }
+ };
- handleMenuClick = (e) => {
+ handleMenuClick = e => {
const { dispatch } = this.props;
const { selectedRows } = this.state;
if (!selectedRows) return;
-
switch (e.key) {
case 'remove':
dispatch({
type: 'rule/remove',
payload: {
- no: selectedRows.map(row => row.no).join(','),
+ key: selectedRows.map(row => row.key),
},
callback: () => {
this.setState({
@@ -191,15 +416,15 @@ export default class TableList extends PureComponent {
default:
break;
}
- }
+ };
- handleSelectRows = (rows) => {
+ handleSelectRows = rows => {
this.setState({
selectedRows: rows,
});
- }
+ };
- handleSearch = (e) => {
+ handleSearch = e => {
e.preventDefault();
const { dispatch, form } = this.props;
@@ -221,38 +446,59 @@ export default class TableList extends PureComponent {
payload: values,
});
});
- }
+ };
- handleModalVisible = (flag) => {
+ handleModalVisible = flag => {
this.setState({
modalVisible: !!flag,
});
- }
+ };
+
+ handleUpdateModalVisible = (flag, record) => {
+ this.setState({
+ updateModalVisible: !!flag,
+ stepFormValues: record || {},
+ });
+ };
- handleAdd = (fields) => {
- this.props.dispatch({
+ handleAdd = fields => {
+ const { dispatch } = this.props;
+ dispatch({
type: 'rule/add',
payload: {
- description: fields.desc,
+ desc: fields.desc,
},
});
message.success('添加成功');
- this.setState({
- modalVisible: false,
+ this.handleModalVisible();
+ };
+
+ handleUpdate = fields => {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'rule/update',
+ payload: {
+ name: fields.name,
+ desc: fields.desc,
+ key: fields.key,
+ },
});
- }
+
+ message.success('配置成功');
+ this.handleUpdateModalVisible();
+ };
renderSimpleForm() {
- const { getFieldDecorator } = this.props.form;
+ const {
+ form: { getFieldDecorator },
+ } = this.props;
return (
-
- {getFieldDecorator('no')(
-
- )}
+
+ {getFieldDecorator('name')( )}
@@ -267,8 +513,12 @@ export default class TableList extends PureComponent {
- 查询
- 重置
+
+ 查询
+
+
+ 重置
+
展开
@@ -280,15 +530,15 @@ export default class TableList extends PureComponent {
}
renderAdvancedForm() {
- const { getFieldDecorator } = this.props.form;
+ const {
+ form: { getFieldDecorator },
+ } = this.props;
return (
-
- {getFieldDecorator('no')(
-
- )}
+
+ {getFieldDecorator('name')( )}
@@ -303,9 +553,7 @@ export default class TableList extends PureComponent {
- {getFieldDecorator('number')(
-
- )}
+ {getFieldDecorator('number')( )}
@@ -339,26 +587,33 @@ export default class TableList extends PureComponent {
);
}
renderForm() {
- return this.state.expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
+ const { expandForm } = this.state;
+ return expandForm ? this.renderAdvancedForm() : this.renderSimpleForm();
}
render() {
- const { rule: { data }, loading } = this.props;
- const { selectedRows, modalVisible } = this.state;
-
+ const {
+ rule: { data },
+ loading,
+ } = this.props;
+ const { selectedRows, modalVisible, updateModalVisible, stepFormValues } = this.state;
const menu = (
删除
@@ -370,46 +625,51 @@ export default class TableList extends PureComponent {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
};
-
+ const updateMethods = {
+ handleUpdateModalVisible: this.handleUpdateModalVisible,
+ handleUpdate: this.handleUpdate,
+ };
return (
-
+
-
- {this.renderForm()}
-
+
{this.renderForm()}
this.handleModalVisible(true)}>
新建
- {
- selectedRows.length > 0 && (
-
- 批量操作
-
-
- 更多操作
-
-
-
- )
- }
+ {selectedRows.length > 0 && (
+
+ 批量操作
+
+
+ 更多操作
+
+
+
+ )}
-
-
+
+ {stepFormValues && Object.keys(stepFormValues).length ? (
+
+ ) : null}
+
);
}
}
+
+export default TableList;
diff --git a/src/pages/List/TableList.less b/src/pages/List/TableList.less
index 94f58fc0..792757df 100644
--- a/src/pages/List/TableList.less
+++ b/src/pages/List/TableList.less
@@ -1,5 +1,5 @@
-@import "~antd/lib/style/themes/default.less";
-@import "../../utils/utils.less";
+@import '~antd/lib/style/themes/default.less';
+@import '~@/utils/utils.less';
.tableList {
.tableListOperator {
@@ -30,6 +30,7 @@
}
}
.submitButtons {
+ display: block;
white-space: nowrap;
margin-bottom: 24px;
}
diff --git a/src/models/rule.js b/src/pages/List/models/rule.js
similarity index 74%
rename from src/models/rule.js
rename to src/pages/List/models/rule.js
index 9ae968f8..b318dbe3 100644
--- a/src/models/rule.js
+++ b/src/pages/List/models/rule.js
@@ -1,4 +1,4 @@
-import { queryRule, removeRule, addRule } from '../services/api';
+import { queryRule, removeRule, addRule, updateRule } from '@/services/api';
export default {
namespace: 'rule',
@@ -34,6 +34,14 @@ export default {
});
if (callback) callback();
},
+ *update({ payload, callback }, { call, put }) {
+ const response = yield call(updateRule, payload);
+ yield put({
+ type: 'save',
+ payload: response,
+ });
+ if (callback) callback();
+ },
},
reducers: {
diff --git a/src/pages/Profile/AdvancedProfile.js b/src/pages/Profile/AdvancedProfile.js
index 09065602..0006f993 100644
--- a/src/pages/Profile/AdvancedProfile.js
+++ b/src/pages/Profile/AdvancedProfile.js
@@ -2,17 +2,31 @@ import React, { Component, Fragment } from 'react';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
import { connect } from 'dva';
-import { Button, Menu, Dropdown, Icon, Row, Col, Steps, Card, Popover, Badge, Table, Tooltip, Divider } from 'antd';
+import {
+ Button,
+ Menu,
+ Dropdown,
+ Icon,
+ Row,
+ Col,
+ Steps,
+ Card,
+ Popover,
+ Badge,
+ Table,
+ Tooltip,
+ Divider,
+} from 'antd';
import classNames from 'classnames';
-import DescriptionList from 'components/DescriptionList';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import DescriptionList from '@/components/DescriptionList';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './AdvancedProfile.less';
const { Step } = Steps;
const { Description } = DescriptionList;
const ButtonGroup = Button.Group;
-const getWindowWidth = () => (window.innerWidth || document.documentElement.clientWidth);
+const getWindowWidth = () => window.innerWidth || document.documentElement.clientWidth;
const menu = (
@@ -28,7 +42,9 @@ const action = (
操作
操作
-
+
+
+
主操作
@@ -53,19 +69,24 @@ const description = (
曲丽丽
XX 服务
2017-07-07
- 12421
+
+ 12421
+
2017-07-07 ~ 2017-08-08
请于两个工作日内确认
);
-const tabList = [{
- key: 'detail',
- tab: '详情',
-}, {
- key: 'rule',
- tab: '规则',
-}];
+const tabList = [
+ {
+ key: 'detail',
+ tab: '详情',
+ },
+ {
+ key: 'rule',
+ tab: '规则',
+ },
+];
const desc1 = (
@@ -83,7 +104,9 @@ const desc2 = (
周毛毛
-
+
);
@@ -93,61 +116,79 @@ const popoverContent = (
未响应 } />
- 耗时:2小时25分钟
+
+ 耗时:2小时25分钟
+
);
-const customDot = (dot, { status }) => (status === 'process' ? (
-
- {dot}
-
-) : dot);
+const customDot = (dot, { status }) =>
+ status === 'process' ? (
+
+ {dot}
+
+ ) : (
+ dot
+ );
-const operationTabList = [{
- key: 'tab1',
- tab: '操作日志一',
-}, {
- key: 'tab2',
- tab: '操作日志二',
-}, {
- key: 'tab3',
- tab: '操作日志三',
-}];
+const operationTabList = [
+ {
+ key: 'tab1',
+ tab: '操作日志一',
+ },
+ {
+ key: 'tab2',
+ tab: '操作日志二',
+ },
+ {
+ key: 'tab3',
+ tab: '操作日志三',
+ },
+];
-const columns = [{
- title: '操作类型',
- dataIndex: 'type',
- key: 'type',
-}, {
- title: '操作人',
- dataIndex: 'name',
- key: 'name',
-}, {
- title: '执行结果',
- dataIndex: 'status',
- key: 'status',
- render: text => (
- text === 'agree' ? :
- ),
-}, {
- title: '操作时间',
- dataIndex: 'updatedAt',
- key: 'updatedAt',
-}, {
- title: '备注',
- dataIndex: 'memo',
- key: 'memo',
-}];
+const columns = [
+ {
+ title: '操作类型',
+ dataIndex: 'type',
+ key: 'type',
+ },
+ {
+ title: '操作人',
+ dataIndex: 'name',
+ key: 'name',
+ },
+ {
+ title: '执行结果',
+ dataIndex: 'status',
+ key: 'status',
+ render: text =>
+ text === 'agree' ? (
+
+ ) : (
+
+ ),
+ },
+ {
+ title: '操作时间',
+ dataIndex: 'updatedAt',
+ key: 'updatedAt',
+ },
+ {
+ title: '备注',
+ dataIndex: 'memo',
+ key: 'memo',
+ },
+];
@connect(({ profile, loading }) => ({
profile,
loading: loading.effects['profile/fetchAdvanced'],
}))
-export default class AdvancedProfile extends Component {
+class AdvancedProfile extends Component {
state = {
operationkey: 'tab1',
stepDirection: 'horizontal',
- }
+ };
componentDidMount() {
const { dispatch } = this.props;
@@ -156,7 +197,7 @@ export default class AdvancedProfile extends Component {
});
this.setStepDirection();
- window.addEventListener('resize', this.setStepDirection);
+ window.addEventListener('resize', this.setStepDirection, { passive: true });
}
componentWillUnmount() {
@@ -164,9 +205,9 @@ export default class AdvancedProfile extends Component {
this.setStepDirection.cancel();
}
- onOperationTabChange = (key) => {
+ onOperationTabChange = key => {
this.setState({ operationkey: key });
- }
+ };
@Bind()
@Debounce(200)
@@ -185,34 +226,42 @@ export default class AdvancedProfile extends Component {
}
render() {
- const { stepDirection } = this.state;
+ const { stepDirection, operationkey } = this.state;
const { profile, loading } = this.props;
const { advancedOperation1, advancedOperation2, advancedOperation3 } = profile;
const contentList = {
- tab1: ,
- tab2: ,
- tab3: ,
+ tab1: (
+
+ ),
+ tab2: (
+
+ ),
+ tab3: (
+
+ ),
};
return (
- }
+ logo={
+
+ }
action={action}
content={description}
extraContent={extra}
@@ -232,19 +281,25 @@ export default class AdvancedProfile extends Component {
32943898021309809423
3321944288191034921
18112345678
- 曲丽丽 18100000000 浙江省杭州市西湖区黄姑山路工专路交叉路口
+
+ 曲丽丽 18100000000 浙江省杭州市西湖区黄姑山路工专路交叉路口
+
725
2017-08-08
-
- 某某数据
-
-
-
-
+
+ 某某数据
+
+
+
+
}
>
725
@@ -258,12 +313,15 @@ export default class AdvancedProfile extends Component {
1234567
XX公司 - YY部
2017-08-08
- 这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长...
+
+ 这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长...
+
- Citrullus lanatus (Thunb.) Matsum. et Nakai一年生蔓生藤本;茎、枝粗壮,具明显的棱。卷须较粗..
+ Citrullus lanatus (Thunb.) Matsum. et
+ Nakai一年生蔓生藤本;茎、枝粗壮,具明显的棱。卷须较粗..
@@ -275,7 +333,8 @@ export default class AdvancedProfile extends Component {
- 暂无数据
+
+ 暂无数据
- {contentList[this.state.operationkey]}
+ {contentList[operationkey]}
-
+
);
}
}
+
+export default AdvancedProfile;
diff --git a/src/pages/Profile/AdvancedProfile.less b/src/pages/Profile/AdvancedProfile.less
index 679a5720..3897f11c 100644
--- a/src/pages/Profile/AdvancedProfile.less
+++ b/src/pages/Profile/AdvancedProfile.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.headerList {
margin-bottom: 4px;
@@ -34,7 +34,10 @@
font-size: 14px;
position: relative;
left: 38px;
- & > div {
+ padding-top: 8px;
+ text-align: left;
+
+ > div {
margin-top: 8px;
margin-bottom: 4px;
}
diff --git a/src/pages/Profile/BasicProfile.js b/src/pages/Profile/BasicProfile.js
index ba585dc9..bd0b7c99 100644
--- a/src/pages/Profile/BasicProfile.js
+++ b/src/pages/Profile/BasicProfile.js
@@ -1,8 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import { Card, Badge, Table, Divider } from 'antd';
-import DescriptionList from 'components/DescriptionList';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import DescriptionList from '@/components/DescriptionList';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './BasicProfile.less';
const { Description } = DescriptionList;
@@ -23,11 +23,11 @@ const progressColumns = [
dataIndex: 'status',
key: 'status',
render: text =>
- (text === 'success' ? (
+ text === 'success' ? (
) : (
- )),
+ ),
},
{
title: '操作员ID',
@@ -45,7 +45,7 @@ const progressColumns = [
profile,
loading: loading.effects['profile/fetchBasic'],
}))
-export default class BasicProfile extends Component {
+class BasicProfile extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
@@ -60,7 +60,7 @@ export default class BasicProfile extends Component {
if (basicGoods.length) {
let num = 0;
let amount = 0;
- basicGoods.forEach((item) => {
+ basicGoods.forEach(item => {
num += Number(item.num);
amount += Number(item.amount);
});
@@ -142,7 +142,7 @@ export default class BasicProfile extends Component {
},
];
return (
-
+
1000000000
@@ -177,7 +177,9 @@ export default class BasicProfile extends Component {
columns={progressColumns}
/>
-
+
);
}
}
+
+export default BasicProfile;
diff --git a/src/pages/Profile/BasicProfile.less b/src/pages/Profile/BasicProfile.less
index cb3e6a37..c830b050 100644
--- a/src/pages/Profile/BasicProfile.less
+++ b/src/pages/Profile/BasicProfile.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.title {
color: @heading-color;
diff --git a/src/models/profile.js b/src/pages/Profile/models/profile.js
similarity index 89%
rename from src/models/profile.js
rename to src/pages/Profile/models/profile.js
index 7c9d0198..de651b8a 100644
--- a/src/models/profile.js
+++ b/src/pages/Profile/models/profile.js
@@ -1,4 +1,4 @@
-import { queryBasicProfile, queryAdvancedProfile } from '../services/api';
+import { queryBasicProfile, queryAdvancedProfile } from '@/services/api';
export default {
namespace: 'profile',
diff --git a/src/pages/Result/Error.js b/src/pages/Result/Error.js
deleted file mode 100644
index 41f5d826..00000000
--- a/src/pages/Result/Error.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React, { Fragment } from 'react';
-import { Button, Icon, Card } from 'antd';
-import Result from 'components/Result';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
-
-const extra = (
-
-
- 您提交的内容有如下错误:
-
-
-
-
-);
-
-const actions = 返回修改 ;
-
-export default () => (
-
-
-
-
-
-);
diff --git a/src/pages/Result/Fail.js b/src/pages/Result/Fail.js
new file mode 100644
index 00000000..fba2da6d
--- /dev/null
+++ b/src/pages/Result/Fail.js
@@ -0,0 +1,66 @@
+import React, { Fragment } from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import { Button, Icon, Card } from 'antd';
+import Result from '@/components/Result';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
+
+const extra = (
+
+
+
+
+
+
+
+);
+
+const actions = (
+
+
+
+);
+
+export default () => (
+
+
+
+
+
+);
diff --git a/src/pages/Result/Success.js b/src/pages/Result/Success.js
index 76fde593..74d19dc3 100644
--- a/src/pages/Result/Success.js
+++ b/src/pages/Result/Success.js
@@ -1,78 +1,144 @@
import React, { Fragment } from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
import { Button, Row, Col, Icon, Steps, Card } from 'antd';
-import Result from 'components/Result';
-import PageHeaderLayout from '../../layouts/PageHeaderLayout';
+import Result from '@/components/Result';
+import PageHeaderWrapper from '@/components/PageHeaderWrapper';
const { Step } = Steps;
const desc1 = (
-
+
- 曲丽丽
+
+
2016-12-12 12:32
);
const desc2 = (
-
+
);
const extra = (
-
- 项目名称
+
+
- 项目 ID:
+
+
+
23421
- 负责人:
- 曲丽丽
+
+
+
+
- 生效时间:
+
+
+
2016-12-12 ~ 2017-12-12
- 创建项目} description={desc1} />
- 部门初审} description={desc2} />
- 财务复核} />
- 完成} />
+
+
+
+ }
+ description={desc1}
+ />
+
+
+
+ }
+ description={desc2}
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
);
const actions = (
- 返回列表
- 查看项目
- 打 印
+
+
+
+
+
+
+
+
+
);
export default () => (
-
+
-
+
);
diff --git a/src/pages/Result/Success.test.js b/src/pages/Result/Success.test.js
new file mode 100644
index 00000000..9bc9b8df
--- /dev/null
+++ b/src/pages/Result/Success.test.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import Success from './Success';
+
+it('renders with Result', () => {
+ const wrapper = shallow(
);
+ expect(wrapper.find('Result').length).toBe(1);
+ expect(wrapper.find('Result').prop('type')).toBe('success');
+});
diff --git a/src/pages/User/Login.js b/src/pages/User/Login.js
index 7aa849ef..5036e607 100644
--- a/src/pages/User/Login.js
+++ b/src/pages/User/Login.js
@@ -1,8 +1,9 @@
import React, { Component } from 'react';
import { connect } from 'dva';
-import { Link } from 'dva/router';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import Link from 'umi/link';
import { Checkbox, Alert, Icon } from 'antd';
-import Login from 'components/Login';
+import Login from '@/components/Login';
import styles from './Login.less';
const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
@@ -11,20 +12,38 @@ const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
login,
submitting: loading.effects['login/login'],
}))
-export default class LoginPage extends Component {
+class LoginPage extends Component {
state = {
type: 'account',
autoLogin: true,
- }
+ };
- onTabChange = (type) => {
+ onTabChange = type => {
this.setState({ type });
- }
+ };
+
+ onGetCaptcha = () =>
+ new Promise((resolve, reject) => {
+ this.loginForm.validateFields(['mobile'], {}, (err, values) => {
+ if (err) {
+ reject(err);
+ } else {
+ const { dispatch } = this.props;
+ dispatch({
+ type: 'login/getCaptcha',
+ payload: values.mobile,
+ })
+ .then(resolve)
+ .catch(reject);
+ }
+ });
+ });
handleSubmit = (err, values) => {
const { type } = this.state;
if (!err) {
- this.props.dispatch({
+ const { dispatch } = this.props;
+ dispatch({
type: 'login/login',
payload: {
...values,
@@ -32,64 +51,83 @@ export default class LoginPage extends Component {
},
});
}
- }
+ };
- changeAutoLogin = (e) => {
+ changeAutoLogin = e => {
this.setState({
autoLogin: e.target.checked,
});
- }
+ };
- renderMessage = (content) => {
- return (
-
- );
- }
+ renderMessage = content => (
+
+ );
render() {
const { login, submitting } = this.props;
- const { type } = this.state;
+ const { type, autoLogin } = this.state;
return (
{
+ this.loginForm = form;
+ }}
>
-
- {
- login.status === 'error' &&
+
+ {login.status === 'error' &&
login.type === 'account' &&
- !login.submitting &&
- this.renderMessage('账户或密码错误(admin/888888)')
- }
-
-
+ !submitting &&
+ this.renderMessage(formatMessage({ id: 'app.login.message-invalid-credentials' }))}
+
+ this.loginForm.validateFields(this.handleSubmit)}
+ />
-
- {
- login.status === 'error' &&
+
+ {login.status === 'error' &&
login.type === 'mobile' &&
- !login.submitting &&
- this.renderMessage('验证码错误')
- }
+ !submitting &&
+ this.renderMessage(
+ formatMessage({ id: 'app.login.message-invalid-verification-code' })
+ )}
-
+
- 登录
+
+
+
- 其他登录方式
-
-
-
- 注册账户
+
+
+
+
+
+
+
);
}
}
+
+export default LoginPage;
diff --git a/src/pages/User/Login.less b/src/pages/User/Login.less
index c8d20c79..14a99ff1 100644
--- a/src/pages/User/Login.less
+++ b/src/pages/User/Login.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.main {
width: 368px;
@@ -13,7 +13,7 @@
margin-left: 16px;
vertical-align: middle;
cursor: pointer;
- transition: color .3s;
+ transition: color 0.3s;
&:hover {
color: @primary-color;
diff --git a/src/pages/User/Register.js b/src/pages/User/Register.js
index 5edeb584..ea4565c2 100644
--- a/src/pages/User/Register.js
+++ b/src/pages/User/Register.js
@@ -1,6 +1,8 @@
import React, { Component } from 'react';
import { connect } from 'dva';
-import { routerRedux, Link } from 'dva/router';
+import { formatMessage, FormattedMessage } from 'umi/locale';
+import Link from 'umi/link';
+import router from 'umi/router';
import { Form, Input, Button, Select, Row, Col, Popover, Progress } from 'antd';
import styles from './Register.less';
@@ -9,9 +11,21 @@ const { Option } = Select;
const InputGroup = Input.Group;
const passwordStatusMap = {
- ok:
强度:强
,
- pass:
强度:中
,
- poor:
强度:太短
,
+ ok: (
+
+
+
+ ),
+ pass: (
+
+
+
+ ),
+ poor: (
+
+
+
+ ),
};
const passwordProgressMap = {
@@ -25,7 +39,7 @@ const passwordProgressMap = {
submitting: loading.effects['register/submit'],
}))
@Form.create()
-export default class Register extends Component {
+class Register extends Component {
state = {
count: 0,
confirmDirty: false,
@@ -34,15 +48,16 @@ export default class Register extends Component {
prefix: '86',
};
- UNSAFE_componentWillReceiveProps(nextProps) {
- const account = this.props.form.getFieldValue('mail');
- if (nextProps.register.status === 'ok') {
- this.props.dispatch(routerRedux.push({
- pathname: '/User/RegisterResult',
+ componentDidUpdate() {
+ const { form, register } = this.props;
+ const account = form.getFieldValue('mail');
+ if (register.status === 'ok') {
+ router.push({
+ pathname: '/user/register-result',
state: {
account,
},
- }));
+ });
}
}
@@ -74,39 +89,43 @@ export default class Register extends Component {
return 'poor';
};
- handleSubmit = (e) => {
+ handleSubmit = e => {
e.preventDefault();
- this.props.form.validateFields({ force: true }, (err, values) => {
+ const { form, dispatch } = this.props;
+ form.validateFields({ force: true }, (err, values) => {
if (!err) {
- this.props.dispatch({
+ const { prefix } = this.state;
+ dispatch({
type: 'register/submit',
payload: {
...values,
- prefix: this.state.prefix,
+ prefix,
},
});
}
});
};
- handleConfirmBlur = (e) => {
+ handleConfirmBlur = e => {
const { value } = e.target;
- this.setState({ confirmDirty: this.state.confirmDirty || !!value });
+ const { confirmDirty } = this.state;
+ this.setState({ confirmDirty: confirmDirty || !!value });
};
checkConfirm = (rule, value, callback) => {
const { form } = this.props;
if (value && value !== form.getFieldValue('password')) {
- callback('两次输入的密码不匹配!');
+ callback(formatMessage({ id: 'validation.password.twice' }));
} else {
callback();
}
};
checkPassword = (rule, value, callback) => {
+ const { visible, confirmDirty } = this.state;
if (!value) {
this.setState({
- help: '请输入密码!',
+ help: formatMessage({ id: 'validation.password.required' }),
visible: !!value,
});
callback('error');
@@ -114,7 +133,7 @@ export default class Register extends Component {
this.setState({
help: '',
});
- if (!this.state.visible) {
+ if (!visible) {
this.setState({
visible: !!value,
});
@@ -123,7 +142,7 @@ export default class Register extends Component {
callback('error');
} else {
const { form } = this.props;
- if (value && this.state.confirmDirty) {
+ if (value && confirmDirty) {
form.validateFields(['confirm'], { force: true });
}
callback();
@@ -131,7 +150,7 @@ export default class Register extends Component {
}
};
- changePrefix = (value) => {
+ changePrefix = value => {
this.setState({
prefix: value,
});
@@ -157,39 +176,44 @@ export default class Register extends Component {
render() {
const { form, submitting } = this.props;
const { getFieldDecorator } = form;
- const { count, prefix } = this.state;
+ const { count, prefix, help, visible } = this.state;
return (
-
注册
+
+
+
{getFieldDecorator('mail', {
rules: [
{
required: true,
- message: '请输入邮箱地址!',
+ message: formatMessage({ id: 'validation.email.required' }),
},
{
type: 'email',
- message: '邮箱地址格式错误!',
+ message: formatMessage({ id: 'validation.email.wrong-format' }),
},
],
- })( )}
+ })(
+
+ )}
-
+
node.parentNode}
content={
{passwordStatusMap[this.getPasswordStatus()]}
{this.renderPasswordProgress()}
- 请至少输入 6 个字符。请不要使用容易被猜到的密码。
+
}
overlayStyle={{ width: 240 }}
placement="right"
- visible={this.state.visible}
+ visible={visible}
>
{getFieldDecorator('password', {
rules: [
@@ -201,7 +225,7 @@ export default class Register extends Component {
)}
@@ -211,13 +235,19 @@ export default class Register extends Component {
rules: [
{
required: true,
- message: '请确认密码!',
+ message: formatMessage({ id: 'validation.confirm-password.required' }),
},
{
validator: this.checkConfirm,
},
],
- })( )}
+ })(
+
+ )}
@@ -234,18 +264,18 @@ export default class Register extends Component {
rules: [
{
required: true,
- message: '请输入手机号!',
+ message: formatMessage({ id: 'validation.phone-number.required' }),
},
{
- pattern: /^1\d{10}$/,
- message: '手机号格式错误!',
+ pattern: /^\d{11}$/,
+ message: formatMessage({ id: 'validation.phone-number.wrong-format' }),
},
],
})(
)}
@@ -257,10 +287,15 @@ export default class Register extends Component {
rules: [
{
required: true,
- message: '请输入验证码!',
+ message: formatMessage({ id: 'validation.verification-code.required' }),
},
],
- })( )}
+ })(
+
+ )}
- {count ? `${count} s` : '获取验证码'}
+ {count
+ ? `${count} s`
+ : formatMessage({ id: 'app.register.get-verification-code' })}
@@ -282,10 +319,10 @@ export default class Register extends Component {
type="primary"
htmlType="submit"
>
- 注册
+
- 使用已有账户登录
+
@@ -293,3 +330,5 @@ export default class Register extends Component {
);
}
}
+
+export default Register;
diff --git a/src/pages/User/Register.less b/src/pages/User/Register.less
index f6d48208..acd1211a 100644
--- a/src/pages/User/Register.less
+++ b/src/pages/User/Register.less
@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
.main {
width: 368px;
diff --git a/src/pages/User/RegisterResult.js b/src/pages/User/RegisterResult.js
index 6dba5c1d..6e338b27 100644
--- a/src/pages/User/RegisterResult.js
+++ b/src/pages/User/RegisterResult.js
@@ -1,27 +1,41 @@
import React from 'react';
+import { formatMessage, FormattedMessage } from 'umi/locale';
import { Button } from 'antd';
-import { Link } from 'dva/router';
-import Result from 'components/Result';
+import Link from 'umi/link';
+import Result from '@/components/Result';
import styles from './RegisterResult.less';
const actions = (
);
-export default ({ location }) => (
+const RegisterResult = ({ location }) => (
- 你的账户:{location.state ? location.state.account : 'AntDesign@example.com'} 注册成功
+
}
- description="激活邮件已发送到你的邮箱中,邮件有效期为24小时。请及时登录邮箱,点击邮件中的链接激活帐户。"
+ description={formatMessage({ id: 'app.register-result.activation-email' })}
actions={actions}
style={{ marginTop: 56 }}
/>
);
+
+export default RegisterResult;
diff --git a/src/pages/User/_layout.js b/src/pages/User/_layout.js
deleted file mode 100644
index 8f312f9b..00000000
--- a/src/pages/User/_layout.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import React, { Fragment } from 'react';
-import { Link } from 'dva/router';
-import DocumentTitle from 'react-document-title';
-import { Icon } from 'antd';
-import GlobalFooter from 'components/GlobalFooter';
-import styles from './UserLayout.less';
-import logo from 'assets/logo.svg';
-import { getRouterData } from 'common/router';
-
-const links = [
- {
- key: 'help',
- title: '帮助',
- href: '',
- },
- {
- key: 'privacy',
- title: '隐私',
- href: '',
- },
- {
- key: 'terms',
- title: '条款',
- href: '',
- },
-];
-
-const copyright = (
-
- Copyright 2018 蚂蚁金服体验技术部出品
-
-);
-const routerData = getRouterData({});
-class UserLayout extends React.PureComponent {
- getPageTitle() {
- const { location } = this.props;
-
- const { pathname } = location;
- let title = 'Ant Design Pro';
- if (routerData[pathname] && routerData[pathname].name) {
- title = `${routerData[pathname].name} - Ant Design Pro`;
- }
- return title;
- }
- render() {
- const { children } = this.props;
- return (
-
-
-
-
-
-
-
-
Ant Design
-
-
-
Ant Design 是西湖区最具影响力的 Web 设计规范
-
- {children}
-
-
-
-
- );
- }
-}
-
-export default UserLayout;
diff --git a/src/models/register.js b/src/pages/User/models/register.js
similarity index 61%
rename from src/models/register.js
rename to src/pages/User/models/register.js
index 54c49633..a2901bbd 100644
--- a/src/models/register.js
+++ b/src/pages/User/models/register.js
@@ -1,6 +1,6 @@
-import { fakeRegister } from '../services/api';
-import { setAuthority } from '../utils/authority';
-import { reloadAuthorized } from '../utils/Authorized';
+import { fakeRegister } from '@/services/api';
+import { setAuthority } from '@/utils/authority';
+import { reloadAuthorized } from '@/utils/Authorized';
export default {
namespace: 'register',
@@ -10,8 +10,8 @@ export default {
},
effects: {
- *submit(_, { call, put }) {
- const response = yield call(fakeRegister);
+ *submit({ payload }, { call, put }) {
+ const response = yield call(fakeRegister, payload);
yield put({
type: 'registerHandle',
payload: response,
diff --git a/src/pages/_routes.json b/src/pages/_routes.json
index fdc112b6..9dc14031 100644
--- a/src/pages/_routes.json
+++ b/src/pages/_routes.json
@@ -1,42 +1,55 @@
[
{
- "path": "/Dashboard",
+ "path": "/",
"exact": true,
- "redirect": "/Dashboard/Analysis"
+ "redirect": "/dashboard/analysis"
},
{
- "path": "/Forms",
+ "path": "/dashboard",
"exact": true,
- "redirect": "/Forms/BasicForm"
+ "redirect": "/dashboard/analysis"
},
{
- "path": "/Forms/StepForm",
+ "path": "/form",
"exact": true,
- "redirect": "/Forms/StepForm/Step1"
+ "redirect": "/form/basicform"
},
{
- "path": "/List",
+ "path": "/form/stepform",
"exact": true,
- "redirect": "/List/TableList"
+ "redirect": "/form/stepForm/info"
},
{
- "path": "/List/Search",
+ "path": "/list",
"exact": true,
- "redirect": "/List/Search/Articles"
+ "redirect": "/list/tableList"
},
{
- "path": "/Profile",
+ "path": "/list/search",
"exact": true,
- "redirect": "/Profile/BasicProfile"
+ "redirect": "/list/search/articles"
},
{
- "path": "/Result",
+ "path": "/profile",
"exact": true,
- "redirect": "/Result/Success"
+ "redirect": "/profile/basicprofile"
},
{
- "path": "/Exception",
+ "path": "/result",
"exact": true,
- "redirect": "/Exception/403"
+ "redirect": "/result/success"
+ },
+ {
+ "path": "/exception",
+ "exact": true,
+ "redirect": "/exception/403"
+ },{
+ "path": "/account/center",
+ "exact": true,
+ "redirect": "/account/center/articles"
+ },{
+ "path": "/account/settings",
+ "exact": true,
+ "redirect": "/account/settings/base"
}
-]
\ No newline at end of file
+]
diff --git a/src/pages/document.ejs b/src/pages/document.ejs
new file mode 100644
index 00000000..e3aeb263
--- /dev/null
+++ b/src/pages/document.ejs
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
Ant Design Pro
+
+
+
+
+
+
+
diff --git a/src/pages/index.js b/src/pages/index.js
deleted file mode 100644
index 86a977c0..00000000
--- a/src/pages/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export default () =>
-
- 首页,需要在这里写重定向
-
diff --git a/src/service-worker.js b/src/service-worker.js
new file mode 100644
index 00000000..48d43c25
--- /dev/null
+++ b/src/service-worker.js
@@ -0,0 +1,65 @@
+/* globals workbox */
+/* eslint-disable no-restricted-globals */
+workbox.core.setCacheNameDetails({
+ prefix: 'antd-pro',
+ suffix: 'v1',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+/* eslint-disable no-underscore-dangle */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/**
+ * Handle API requests
+ */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/**
+ * Handle third party requests
+ */
+workbox.routing.registerRoute(
+ /^https:\/\/gw.alipayobjects.com\//,
+ workbox.strategies.networkFirst()
+);
+workbox.routing.registerRoute(
+ /^https:\/\/cdnjs.cloudflare.com\//,
+ workbox.strategies.networkFirst()
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/**
+ * Response to client after skipping waiting with MessageChannel
+ */
+addEventListener('message', event => {
+ const replyPort = event.ports[0];
+ const message = event.data;
+ if (replyPort && message && message.type === 'skip-waiting') {
+ event.waitUntil(
+ self
+ .skipWaiting()
+ .then(
+ () => replyPort.postMessage({ error: null }),
+ error => replyPort.postMessage({ error })
+ )
+ );
+ }
+});
diff --git a/src/services/api.js b/src/services/api.js
index 2f3e21a5..431f2ccd 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -1,5 +1,5 @@
import { stringify } from 'qs';
-import request from '../utils/request';
+import request from '@/utils/request';
export async function queryProjectNotice() {
return request('/api/project/notice');
@@ -33,6 +33,16 @@ export async function addRule(params) {
});
}
+export async function updateRule(params) {
+ return request('/api/rule', {
+ method: 'POST',
+ body: {
+ ...params,
+ method: 'update',
+ },
+ });
+}
+
export async function fakeSubmitForm(params) {
return request('/api/forms', {
method: 'POST',
@@ -60,6 +70,39 @@ export async function queryFakeList(params) {
return request(`/api/fake_list?${stringify(params)}`);
}
+export async function removeFakeList(params) {
+ const { count = 5, ...restParams } = params;
+ return request(`/api/fake_list?count=${count}`, {
+ method: 'POST',
+ body: {
+ ...restParams,
+ method: 'delete',
+ },
+ });
+}
+
+export async function addFakeList(params) {
+ const { count = 5, ...restParams } = params;
+ return request(`/api/fake_list?count=${count}`, {
+ method: 'POST',
+ body: {
+ ...restParams,
+ method: 'post',
+ },
+ });
+}
+
+export async function updateFakeList(params) {
+ const { count = 5, ...restParams } = params;
+ return request(`/api/fake_list?count=${count}`, {
+ method: 'POST',
+ body: {
+ ...restParams,
+ method: 'update',
+ },
+ });
+}
+
export async function fakeAccountLogin(params) {
return request('/api/login/account', {
method: 'POST',
@@ -77,3 +120,7 @@ export async function fakeRegister(params) {
export async function queryNotices() {
return request('/api/notices');
}
+
+export async function getFakeCaptcha(mobile) {
+ return request(`/api/captcha?mobile=${mobile}`);
+}
diff --git a/src/services/error.js b/src/services/error.js
index 9697b7ee..13f2e942 100644
--- a/src/services/error.js
+++ b/src/services/error.js
@@ -1,17 +1,5 @@
-import request from '../utils/request';
+import request from '@/utils/request';
-export async function query404() {
- return request('/api/404');
-}
-
-export async function query401() {
- return request('/api/401');
-}
-
-export async function query403() {
- return request('/api/403');
-}
-
-export async function query500() {
- return request('/api/500');
+export default async function queryError(code) {
+ return request(`/api/${code}`);
}
diff --git a/src/services/geographic.js b/src/services/geographic.js
new file mode 100644
index 00000000..a5defd87
--- /dev/null
+++ b/src/services/geographic.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request';
+
+export async function queryProvince() {
+ return request('/api/geographic/province');
+}
+
+export async function queryCity(province) {
+ return request(`/api/geographic/city/${province}`);
+}
diff --git a/src/services/user.js b/src/services/user.js
index c4defb4f..8f11a6f8 100644
--- a/src/services/user.js
+++ b/src/services/user.js
@@ -1,4 +1,4 @@
-import request from '../utils/request';
+import request from '@/utils/request';
export async function query() {
return request('/api/users');
@@ -7,3 +7,8 @@ export async function query() {
export async function queryCurrent() {
return request('/api/currentUser');
}
+
+
+export async function queryMenu() {
+ return request('/api/auth_routes');
+}
\ No newline at end of file
diff --git a/src/theme.js b/src/theme.js
deleted file mode 100644
index 9e125114..00000000
--- a/src/theme.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less
-module.exports = {
- // 'primary-color': '#10e99b',
- 'card-actions-background': '#f5f8fa',
-};
diff --git a/src/utils/Authorized.js b/src/utils/Authorized.js
index 697526ea..8c420cba 100644
--- a/src/utils/Authorized.js
+++ b/src/utils/Authorized.js
@@ -1,4 +1,4 @@
-import RenderAuthorized from 'components/Authorized';
+import RenderAuthorized from '@/components/Authorized';
import { getAuthority } from './authority';
let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
diff --git a/src/utils/Yuan.js b/src/utils/Yuan.js
new file mode 100644
index 00000000..434a57fb
--- /dev/null
+++ b/src/utils/Yuan.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import { yuan } from '@/components/Charts';
+/**
+ * 减少使用 dangerouslySetInnerHTML
+ */
+export default class Yuan extends React.PureComponent {
+ componentDidMount() {
+ this.rendertoHtml();
+ }
+
+ componentDidUpdate() {
+ this.rendertoHtml();
+ }
+
+ rendertoHtml = () => {
+ const { children } = this.props;
+ if (this.main) {
+ this.main.innerHTML = yuan(children);
+ }
+ };
+
+ render() {
+ return (
+
{
+ this.main = ref;
+ }}
+ />
+ );
+ }
+}
diff --git a/src/utils/authority.js b/src/utils/authority.js
index 8c48c8cd..3d2e7b34 100644
--- a/src/utils/authority.js
+++ b/src/utils/authority.js
@@ -1,8 +1,22 @@
// use localStorage to store the authority info, which might be sent from server in actual project.
-export function getAuthority() {
- return localStorage.getItem('umi-antd-pro-authority') || '';
+export function getAuthority(str) {
+ // return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
+ const authorityString =
+ typeof str === 'undefined' ? localStorage.getItem('antd-pro-authority') : str;
+ // authorityString could be admin, "admin", ["admin"]
+ let authority;
+ try {
+ authority = JSON.parse(authorityString);
+ } catch (e) {
+ authority = authorityString;
+ }
+ if (typeof authority === 'string') {
+ return [authority];
+ }
+ return authority || ['admin'];
}
export function setAuthority(authority) {
- return localStorage.setItem('umi-antd-pro-authority', authority);
+ const proAuthority = typeof authority === 'string' ? [authority] : authority;
+ return localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
}
diff --git a/src/utils/authority.test.js b/src/utils/authority.test.js
new file mode 100644
index 00000000..8a6cd41f
--- /dev/null
+++ b/src/utils/authority.test.js
@@ -0,0 +1,19 @@
+import { getAuthority } from './authority';
+
+describe('getAuthority should be strong', () => {
+ it('empty', () => {
+ expect(getAuthority(null)).toEqual(['admin']); // default value
+ });
+ it('string', () => {
+ expect(getAuthority('admin')).toEqual(['admin']);
+ });
+ it('array with double quotes', () => {
+ expect(getAuthority('"admin"')).toEqual(['admin']);
+ });
+ it('array with single item', () => {
+ expect(getAuthority('["admin"]')).toEqual(['admin']);
+ });
+ it('array with multiple items', () => {
+ expect(getAuthority('["admin", "guest"]')).toEqual(['admin', 'guest']);
+ });
+});
diff --git a/src/utils/config.js b/src/utils/config.js
index 016964a1..18abd85c 100644
--- a/src/utils/config.js
+++ b/src/utils/config.js
@@ -1,7 +1,7 @@
const isDEV = true;
module.exports = {
- name: '风控管理系统',
- prefix: 'risk04101425',
- openPages: ['/User','/User/Login','/User/Register','/User/RegisterResult'],
+ name: 'antd-pro',
+ prefix: 'pro',
+ openPages: ['/User','/user/login','/User/Login','/User/Register','/User/RegisterResult'],
serverUrl:isDEV?'':'http://localhost:3000'
}
diff --git a/src/utils/index.js b/src/utils/index.js
deleted file mode 100644
index 91461ea0..00000000
--- a/src/utils/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import config from './config'
-import request from './request'
-
-export {
- config,
- request
-}
diff --git a/src/utils/request.js b/src/utils/request.js
index 3480c692..398b787f 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -1,8 +1,9 @@
import fetch from 'dva/fetch';
import { notification } from 'antd';
-import router from "umi/router";
-import { config } from "utils";
-const { serverUrl } = config;
+import router from 'umi/router';
+import hash from 'hash.js';
+import { isAntdPro } from './utils';
+
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
@@ -20,7 +21,8 @@ const codeMessage = {
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
-function checkStatus(response) {
+
+const checkStatus = response => {
if (response.status >= 200 && response.status < 300) {
return response;
}
@@ -33,22 +35,58 @@ function checkStatus(response) {
error.name = response.status;
error.response = response;
throw error;
-}
+};
+
+const cachedSave = (response, hashcode) => {
+ /**
+ * Clone a response data and store it in sessionStorage
+ * Does not support data other than json, Cache only json
+ */
+ const contentType = response.headers.get('Content-Type');
+ if (contentType && contentType.match(/application\/json/i)) {
+ // All data is saved as text
+ response
+ .clone()
+ .text()
+ .then(content => {
+ sessionStorage.setItem(hashcode, content);
+ sessionStorage.setItem(`${hashcode}:timestamp`, Date.now());
+ });
+ }
+ return response;
+};
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
- * @param {object} [options] The options we want to pass to "fetch"
+ * @param {object} [option] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
-export default function request(url, options) {
- url = serverUrl+url;
+export default function request(url, option) {
+ const options = {
+ expirys: isAntdPro(),
+ ...option,
+ };
+ /**
+ * Produce fingerprints based on url and parameters
+ * Maybe url has the same parameters
+ */
+ const fingerprint = url + (options.body ? JSON.stringify(options.body) : '');
+ const hashcode = hash
+ .sha256()
+ .update(fingerprint)
+ .digest('hex');
+
const defaultOptions = {
- // credentials: 'include',
+ credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
- if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
+ if (
+ newOptions.method === 'POST' ||
+ newOptions.method === 'PUT' ||
+ newOptions.method === 'DELETE'
+ ) {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
Accept: 'application/json',
@@ -65,23 +103,43 @@ export default function request(url, options) {
}
}
+ const expirys = options.expirys && 60;
+ // options.expirys !== false, return the cache,
+ if (options.expirys !== false) {
+ const cached = sessionStorage.getItem(hashcode);
+ const whenCached = sessionStorage.getItem(`${hashcode}:timestamp`);
+ if (cached !== null && whenCached !== null) {
+ const age = (Date.now() - whenCached) / 1000;
+ if (age < expirys) {
+ const response = new Response(new Blob([cached]));
+ return response.json();
+ }
+ sessionStorage.removeItem(hashcode);
+ sessionStorage.removeItem(`${hashcode}:timestamp`);
+ }
+ }
return fetch(url, newOptions)
.then(checkStatus)
+ .then(response => cachedSave(response, hashcode))
.then(response => {
+ // DELETE and 204 do not return data by default
+ // using .json will report an error.
if (newOptions.method === 'DELETE' || response.status === 204) {
return response.text();
}
return response.json();
})
.catch(e => {
- const dispatch = window.g_app._store.dispatch;
const status = e.name;
if (status === 401) {
- dispatch({
+ // @HACK
+ /* eslint-disable no-underscore-dangle */
+ window.g_app._store.dispatch({
type: 'login/logout',
});
return;
}
+ // environment should not be used
if (status === 403) {
router.push('/exception/403');
return;
diff --git a/src/utils/roadhog-api-doc-util.js b/src/utils/roadhog-api-doc-util.js
deleted file mode 100644
index 67453ee4..00000000
--- a/src/utils/roadhog-api-doc-util.js
+++ /dev/null
@@ -1,37 +0,0 @@
-var format = function(mockData) {
- return delay(mockData, 0);
- };
-
- var delay = function(proxy, timer) {
- var mockApi = {};
- Object.keys(proxy).forEach(function(key) {
- var result = proxy[key].$body || proxy[key];
- if (
- Object.prototype.toString.call(result) === "[object String]" &&
- /^http/.test(result)
- ) {
- mockApi[key] = proxy[key];
- } else {
- mockApi[key] = function(req, res) {
- var foo;
- if (Object.prototype.toString.call(result) === "[object Function]") {
- foo = result;
- } else {
- foo = function(req, res) {
- res.json(result);
- };
- }
-
- setTimeout(function() {
- foo(req, res);
- }, timer);
- };
- }
- });
- mockApi.__mockData = proxy;
- return mockApi;
- };
-
- module.exports.delay = delay;
- module.exports.format = format;
-
\ No newline at end of file
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 0dad8d87..ea99be3b 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -1,4 +1,7 @@
import moment from 'moment';
+import React from 'react';
+import nzh from 'nzh/cn';
+import { parse, stringify } from 'qs';
export function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
@@ -45,11 +48,8 @@ export function getTimeDistance(type) {
];
}
- if (type === 'year') {
- const year = now.getFullYear();
-
- return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
- }
+ const year = now.getFullYear();
+ return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
}
export function getPlainNode(nodeList, parentPath = '') {
@@ -71,29 +71,7 @@ export function getPlainNode(nodeList, parentPath = '') {
}
export function digitUppercase(n) {
- const fraction = ['角', '分'];
- const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
- const unit = [['元', '万', '亿'], ['', '拾', '佰', '仟']];
- let num = Math.abs(n);
- let s = '';
- fraction.forEach((item, index) => {
- s += (digit[Math.floor(num * 10 * 10 ** index) % 10] + item).replace(/零./, '');
- });
- s = s || '整';
- num = Math.floor(num);
- for (let i = 0; i < unit[0].length && num > 0; i += 1) {
- let p = '';
- for (let j = 0; j < unit[1].length && num > 0; j += 1) {
- p = digit[num % 10] + unit[1][j] + p;
- num = Math.floor(num / 10);
- }
- s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
- }
-
- return s
- .replace(/(零.)*零元/, '元')
- .replace(/(零.)+/g, '零')
- .replace(/^整$/, '零元整');
+ return nzh.toMoney(n);
}
function getRelation(str1, str2) {
@@ -104,7 +82,8 @@ function getRelation(str1, str2) {
const arr2 = str2.split('/');
if (arr2.every((item, index) => item === arr1[index])) {
return 1;
- } else if (arr1.every((item, index) => item === arr2[index])) {
+ }
+ if (arr1.every((item, index) => item === arr2[index])) {
return 2;
}
return 3;
@@ -114,11 +93,10 @@ function getRenderArr(routes) {
let renderArr = [];
renderArr.push(routes[0]);
for (let i = 1; i < routes.length; i += 1) {
- let isAdd = false;
- // 是否包含
- isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
// 去重
renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1);
+ // 是否包含
+ const isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
if (isAdd) {
renderArr.push(routes[i]);
}
@@ -153,9 +131,54 @@ export function getRoutes(path, routerData) {
return renderRoutes;
}
+export function getPageQuery() {
+ return parse(window.location.href.split('?')[1]);
+}
+
+export function getQueryPath(path = '', query = {}) {
+ const search = stringify(query);
+ if (search.length) {
+ return `${path}?${search}`;
+ }
+ return path;
+}
+
/* eslint no-useless-escape:0 */
-const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/g;
+const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/;
export function isUrl(path) {
return reg.test(path);
}
+
+export function formatWan(val) {
+ const v = val * 1;
+ if (!v || Number.isNaN(v)) return '';
+
+ let result = val;
+ if (val > 10000) {
+ result = Math.floor(val / 10000);
+ result = (
+
+ {result}
+
+ 万
+
+
+ );
+ }
+ return result;
+}
+
+// 给官方演示站点用,用于关闭真实开发环境不需要使用的特性
+export function isAntdPro() {
+ return window.location.hostname === 'preview.pro.ant.design';
+}
diff --git a/src/utils/utils.test.js b/src/utils/utils.test.js
new file mode 100644
index 00000000..c97d3254
--- /dev/null
+++ b/src/utils/utils.test.js
@@ -0,0 +1,62 @@
+import { fixedZero, isUrl } from './utils';
+
+describe('fixedZero tests', () => {
+ it('should not pad large numbers', () => {
+ expect(fixedZero(10)).toEqual(10);
+ expect(fixedZero(11)).toEqual(11);
+ expect(fixedZero(15)).toEqual(15);
+ expect(fixedZero(20)).toEqual(20);
+ expect(fixedZero(100)).toEqual(100);
+ expect(fixedZero(1000)).toEqual(1000);
+ });
+
+ it('should pad single digit numbers and return them as string', () => {
+ expect(fixedZero(0)).toEqual('00');
+ expect(fixedZero(1)).toEqual('01');
+ expect(fixedZero(2)).toEqual('02');
+ expect(fixedZero(3)).toEqual('03');
+ expect(fixedZero(4)).toEqual('04');
+ expect(fixedZero(5)).toEqual('05');
+ expect(fixedZero(6)).toEqual('06');
+ expect(fixedZero(7)).toEqual('07');
+ expect(fixedZero(8)).toEqual('08');
+ expect(fixedZero(9)).toEqual('09');
+ });
+});
+
+describe('isUrl tests', () => {
+ it('should return false for invalid and corner case inputs', () => {
+ expect(isUrl([])).toBeFalsy();
+ expect(isUrl({})).toBeFalsy();
+ expect(isUrl(false)).toBeFalsy();
+ expect(isUrl(true)).toBeFalsy();
+ expect(isUrl(NaN)).toBeFalsy();
+ expect(isUrl(null)).toBeFalsy();
+ expect(isUrl(undefined)).toBeFalsy();
+ expect(isUrl()).toBeFalsy();
+ expect(isUrl('')).toBeFalsy();
+ });
+
+ it('should return false for invalid URLs', () => {
+ expect(isUrl('foo')).toBeFalsy();
+ expect(isUrl('bar')).toBeFalsy();
+ expect(isUrl('bar/test')).toBeFalsy();
+ expect(isUrl('http:/example.com/')).toBeFalsy();
+ expect(isUrl('ttp://example.com/')).toBeFalsy();
+ });
+
+ it('should return true for valid URLs', () => {
+ expect(isUrl('http://example.com/')).toBeTruthy();
+ expect(isUrl('https://example.com/')).toBeTruthy();
+ expect(isUrl('http://example.com/test/123')).toBeTruthy();
+ expect(isUrl('https://example.com/test/123')).toBeTruthy();
+ expect(isUrl('http://example.com/test/123?foo=bar')).toBeTruthy();
+ expect(isUrl('https://example.com/test/123?foo=bar')).toBeTruthy();
+ expect(isUrl('http://www.example.com/')).toBeTruthy();
+ expect(isUrl('https://www.example.com/')).toBeTruthy();
+ expect(isUrl('http://www.example.com/test/123')).toBeTruthy();
+ expect(isUrl('https://www.example.com/test/123')).toBeTruthy();
+ expect(isUrl('http://www.example.com/test/123?foo=bar')).toBeTruthy();
+ expect(isUrl('https://www.example.com/test/123?foo=bar')).toBeTruthy();
+ });
+});