fix: Update component && validate

1. Add 'ready' status to State component
2. Add price render in table
3. Support children column in table
4. Fix image/instance name validate
5. Support loading in Label component
6. Fix MagicInput component style
7. Fix role check in layout
8. Update class export
9. Remove useless component

Change-Id: I0e5d7e4a23fb0a68e17ae57eba83608be3a3df0e
This commit is contained in:
Jingwei.Zhang 2021-09-08 15:26:49 +08:00
parent a6f387d67e
commit 2b68f2a3d7
23 changed files with 62 additions and 900 deletions

View File

@ -108,6 +108,7 @@ const DetailCard = ({
arrowPointAtCenter="true" arrowPointAtCenter="true"
placement="rightTop" placement="rightTop"
content={titleHelp} content={titleHelp}
getPopupContainer={(node) => node.parentNode}
> >
<InfoCircleOutlined className={styles['title-help']} /> <InfoCircleOutlined className={styles['title-help']} />
</Popover> </Popover>

View File

@ -49,6 +49,3 @@
} }
} }
} }

View File

@ -31,6 +31,7 @@ import {
TagOutlined, TagOutlined,
HddOutlined, HddOutlined,
CloudServerOutlined, CloudServerOutlined,
LoadingOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import styles from './index.less'; import styles from './index.less';
@ -82,14 +83,19 @@ export default class index extends Component {
} }
render() { render() {
const { content, value, iconType, ...rest } = this.props; const { content, value, iconType, showLoading, ...rest } = this.props;
const failValues = [undefined, null, ''];
if (content) { if (content) {
return content; return content;
} }
return ( return (
<span {...rest}> <span {...rest}>
{this.renderIcon()} {this.renderIcon()}
{value} {showLoading && failValues.includes(value) ? (
<LoadingOutlined />
) : (
value
)}
</span> </span>
); );
} }

View File

@ -82,6 +82,7 @@ export default class SelectTable extends React.Component {
defaultSortKey: PropTypes.string, defaultSortKey: PropTypes.string,
defaultSortOrder: PropTypes.string, defaultSortOrder: PropTypes.string,
onRow: PropTypes.func, onRow: PropTypes.func,
childrenColumnName: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -105,6 +106,7 @@ export default class SelectTable extends React.Component {
isSortByBack: false, isSortByBack: false,
defaultSortKey: '', defaultSortKey: '',
defaultSortOrder: '', defaultSortOrder: '',
childrenColumnName: 'children',
}; };
constructor(props) { constructor(props) {
@ -652,6 +654,7 @@ export default class SelectTable extends React.Component {
filterParams, filterParams,
onRow, onRow,
rowKey, rowKey,
childrenColumnName,
} = this.props; } = this.props;
const { current, pageSize, total, filters } = this.state; const { current, pageSize, total, filters } = this.state;
const defaultPageSizeOptions = [10, 20, 50, 100]; const defaultPageSizeOptions = [10, 20, 50, 100];
@ -697,6 +700,7 @@ export default class SelectTable extends React.Component {
onChange={this.handleChange} onChange={this.handleChange}
footer={footer} footer={footer}
onRow={onRow} onRow={onRow}
childrenColumnName={childrenColumnName}
/> />
); );
} }

View File

@ -60,7 +60,7 @@ import TabSelectTable from './TabSelectTable';
import TreeSelect from './TreeSelect'; import TreeSelect from './TreeSelect';
// import styles from './index.less'; // import styles from './index.less';
const type2component = { export const type2component = {
label: Label, label: Label,
input: Input, input: Input,
select: Select, select: Select,
@ -132,8 +132,8 @@ export default class FormItem extends React.Component {
getComponentProps(type) { getComponentProps(type) {
switch (type) { switch (type) {
case 'label': { case 'label': {
const { content, icon, iconType } = this.props; const { content, icon, iconType, showLoading } = this.props;
return { content, icon, iconType }; return { content, icon, iconType, showLoading };
} }
case 'divider': case 'divider':
return { return {
@ -319,6 +319,10 @@ export default class FormItem extends React.Component {
return newRules; return newRules;
} }
getComponent(type) {
return type2component[type];
}
renderTip(tip) { renderTip(tip) {
if (!tip) { if (!tip) {
return null; return null;
@ -347,7 +351,7 @@ export default class FormItem extends React.Component {
if (component) { if (component) {
return <Form.Item {...formItemProps}>{component}</Form.Item>; return <Form.Item {...formItemProps}>{component}</Form.Item>;
} }
const TypeComp = type2component[type]; const TypeComp = this.getComponent(type);
const props = this.getComponentProps(type); const props = this.getComponentProps(type);
if (type === 'divider') { if (type === 'divider') {
return <Divider className="form-item-divider" />; return <Divider className="form-item-divider" />;

View File

@ -1,81 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { trimEnd } from 'lodash';
import NavItem from './item';
import styles from './index.less';
class GlobalNav extends React.Component {
static propTypes = {
className: PropTypes.string,
navs: PropTypes.array.isRequired,
// eslint-disable-next-line react/no-unused-prop-types
prefix: PropTypes.string,
// eslint-disable-next-line react/no-unused-prop-types
checkSelect: PropTypes.func,
onItemClick: PropTypes.func,
innerRef: PropTypes.object,
};
static defaultProps = {
className: '',
prefix: '',
checkSelect() {},
onItemClick() {},
};
get currentPath() {
const {
location: { pathname },
match: { url },
} = this.props;
const { length } = trimEnd(url, '/');
return pathname.slice(length + 1);
}
render() {
const { className, navs, innerRef, onItemClick } = this.props;
const classNames = classnames(styles.wrapper, className);
return (
<div ref={innerRef} className={classNames}>
{navs.map((nav) => (
<div key={nav.cate} className={styles.subNav}>
{nav.title && <p>{t(nav.title)}</p>}
<ul>
{nav.items.map((item) => (
<NavItem
key={item.name}
item={item}
prefix=""
current={this.currentPath}
onClick={onItemClick}
/>
))}
</ul>
</div>
))}
</div>
);
}
}
export default GlobalNav;

View File

@ -1,102 +0,0 @@
@import '~styles/variables';
@import '~styles/mixins';
.wrapper {
position: fixed;
top: 60px;
left: 8px;
width: $nav-width;
height: calc(100vh - 68px);
padding: 40px 20px;
border-radius: $border-radius;
background-color: $dark;
box-shadow: 4px 8px 16px 0 rgba(0, 0, 0, 0.1);
transition: left $trans-speed ease-in-out;
overflow-y: auto;
z-index: 212;
.subNav > ul > li {
&.select {
box-shadow: 0 4px 8px 0 rgba(25, 30, 41, 0.2);
background-color: #d8dee5;
border: solid 1px #404e68;
}
&:hover {
background-color: #d8dee5;
}
&:active {
background-color: #d8dee5;
border: solid 1px #404e68;
}
& > a {
color: $light;
transition: color $trans-speed ease-in-out;
@media (max-width: 1366px) {
padding: 7px 12px;
}
:global .qicon {
color: #b6c2cd;
fill: #b6c2cd;
}
}
}
}
.subNav {
& > p {
color: $light-color02;
margin-bottom: 12px;
}
& > ul {
margin-bottom: 20px;
& > li {
border-radius: 18px;
border: solid 1px transparent;
transition: all $trans-speed ease-in-out;
& > a,
.title {
display: block;
padding: 7px 12px;
color: #4a5974;
font-weight: 500;
cursor: pointer;
@media (max-width: 1366px) {
padding: 7px 0;
}
:global {
.icon {
margin-right: 8px;
vertical-align: text-bottom;
}
}
}
&.select,
&:hover,
&:active {
& > a {
color: $primary;
:global .qicon {
color: #1890ff;
fill:#6fb4f5;
}
}
}
& + li {
margin-top: 4px;
}
}
}
}

View File

@ -1,57 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { Icon } from 'antd';
import styles from './index.less';
export default class NavItem extends React.Component {
static propTypes = {
item: PropTypes.object,
current: PropTypes.string,
prefix: PropTypes.string,
onClick: PropTypes.func,
};
checkSelect = (item = {}) => {
const { current } = this.props;
return current.startsWith(item.name);
};
renderIcon(icon) {
return <Icon name={icon} />;
}
render() {
const { item, prefix, onClick } = this.props;
return (
<li
key={item.name}
className={classnames({
[styles.select]: this.checkSelect(item),
})}
>
<Link to={`${prefix}/${item.name}`} onClick={onClick}>
{this.renderIcon(item.icon)} {t(item.title)}
</Link>
</li>
);
}
}

View File

@ -1,81 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { trimEnd } from 'lodash';
import NavItem from './item';
import styles from './index.less';
class Nav extends React.Component {
static propTypes = {
className: PropTypes.string,
navs: PropTypes.array.isRequired,
// eslint-disable-next-line react/no-unused-prop-types
prefix: PropTypes.string,
// eslint-disable-next-line react/no-unused-prop-types
checkSelect: PropTypes.func,
onItemClick: PropTypes.func,
innerRef: PropTypes.object,
};
static defaultProps = {
className: '',
prefix: '',
checkSelect() {},
onItemClick() {},
};
get currentPath() {
const {
location: { pathname },
match: { url },
} = this.props;
const { length } = trimEnd(url, '/');
return pathname.slice(length + 1);
}
render() {
const { className, navs, match, innerRef, onItemClick } = this.props;
const prefix = trimEnd(match.url, '/');
return (
<div ref={innerRef} className={className}>
{navs.map((nav) => (
<div key={nav.cate} className={styles.subNav}>
{nav.title && <p>{t(nav.title)}</p>}
<ul>
{nav.items.map((item) => (
<NavItem
key={item.name}
item={item}
prefix={prefix}
current={this.currentPath}
onClick={onItemClick}
/>
))}
</ul>
</div>
))}
</div>
);
}
}
export default Nav;

View File

@ -1,134 +0,0 @@
@import '~styles/variables';
.subNav {
& > p {
color: #79879c;
margin-bottom: 12px;
}
& > ul {
margin-bottom: 20px;
& > li {
border-radius: 18px;
border: solid 1px transparent;
transition: all $trans-speed ease-in-out;
& > a,
.title {
display: block;
padding: 7px 12px;
color: #4a5974;
font-weight: 500;
cursor: pointer;
@media (max-width: 1366px) {
padding: 7px 0;
}
:global {
.icon {
margin-right: 8px;
vertical-align: text-bottom;
}
.qicon-chevron-down {
margin-top: 4px;
transition: all $trans-speed ease-in-out;
}
}
.devopsIcon {
width: 16px;
height: 16px;
padding: 2px;
margin-right: 8px;
vertical-align: text-bottom;
}
}
&.select,
&.childSelect,
&:hover,
&:active {
& > a {
color: $primary;
:global .qicon {
color: $icon-color;
fill: #6fb4f5;
}
.devopsIcon {
color: $icon-color;
fill: #6fb4f5;
}
}
.title {
:global .qicon-chevron-down {
transform: rotate(-180deg);
}
}
.innerNav > li {
height: 20px;
margin-top: 8px;
opacity: 1;
transition: height $trans-speed ease-in-out,
margin-top $trans-speed ease-in-out,
opacity $trans-speed ease-in-out 0.1s;
}
}
& + li {
margin-top: 4px;
}
}
}
}
.innerNav {
margin-bottom: 4px;
padding-left: 38px;
@media (max-width: 1366px) {
padding-left: 26px;
}
& > li {
height: 0;
opacity: 0;
overflow: auto;
transition: height $trans-speed ease-in-out 0.1s,
margin-top $trans-speed ease-in-out 0.1s, opacity $trans-speed ease-in-out;
& > a {
color: #4a5974;
}
&.select,
&:hover,
&:active {
& > a {
color: $primary;
}
}
}
}
.back {
margin: 20px 0;
padding: 8px 12px;
& > a > svg {
width: 16px;
height: 16px;
margin-right: 8px;
vertical-align: text-top;
}
}
.rightIcon {
float: right;
margin-right: 0 !important;
}

View File

@ -1,88 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Link } from 'react-router-dom';
import { Icon } from 'antd';
import styles from './index.less';
export default class NavItem extends React.Component {
static propTypes = {
item: PropTypes.object,
current: PropTypes.string,
prefix: PropTypes.string,
onClick: PropTypes.func,
};
checkSelect = (item = {}) => {
const { current } = this.props;
if (item.children) {
return item.children.some((child) => this.checkSelect(child));
}
if (item.tabs) {
return item.tabs.some((tab) => this.checkSelect(tab));
}
return current.startsWith(item.name);
};
render() {
const { item, prefix, onClick } = this.props;
if (item.children) {
return (
<li
className={classnames({
[styles.childSelect]: item.open || this.checkSelect(item),
})}
>
<div className={styles.title}>
<Icon name={item.icon} /> {t(item.title)}
<Icon name="chevron-down" className={styles.rightIcon} />
</div>
<ul className={styles.innerNav}>
{item.children.map((child) => (
<li
key={child.name}
className={classnames({
[styles.select]: this.checkSelect(child),
})}
>
<Link to={`${prefix}/${child.name}`}>{t(child.title)}</Link>
</li>
))}
</ul>
</li>
);
}
return (
<li
key={item.name}
className={classnames({
[styles.select]: this.checkSelect(item),
})}
>
<Link to={`${prefix}/${item.name}`} onClick={onClick}>
<Icon name={item.icon} /> {t(item.title)}
</Link>
</li>
);
}
}

View File

@ -1,150 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { Icon, Dropdown, Spin, Menu } from 'antd';
import styles from './index.less';
export default class Selector extends React.Component {
static propTypes = {
icon: PropTypes.string,
defaultIcon: PropTypes.string,
value: PropTypes.string,
type: PropTypes.string,
loading: PropTypes.bool,
options: PropTypes.array,
onSelect: PropTypes.func,
onScrollBottom: PropTypes.func,
};
static defaultProps = {
icon: '',
defaultIcon: '',
value: '',
type: '',
loading: false,
options: [],
onSelect() {},
onScrollBottom() {},
};
constructor(props) {
super(props);
this.contentRef = React.createRef();
}
componentDidMount() {
if (this.contentRef.current) {
this.$dropdownContent =
this.contentRef.current.querySelector('.dropdown-content');
this.$dropdownContent.addEventListener('scroll', this.handleScroll);
}
}
componentDidUpdate() {
if (this.contentRef.current) {
const $menu = this.contentRef.current.querySelector(
'.dropdown-content > .menu-wrapper'
);
if ($menu && this.$dropdownContent) {
this.threshold =
$menu.offsetHeight - this.$dropdownContent.offsetHeight;
}
}
}
componentWillUnmount() {
if (this.$dropdownContent) {
this.$dropdownContent.removeEventListener('scroll', this.handleScroll);
}
}
get isMulti() {
return this.props.options.length > 1;
}
handleScroll = (e) => {
if (this.threshold && e.target.scrollTop >= this.threshold - 2) {
this.props.onScrollBottom();
}
};
handleMenuClick = (e, key) => {
this.props.onSelect(key);
};
renderList() {
const { defaultIcon, options, loading } = this.props;
if (!this.isMulti) {
return null;
}
return (
<div className="menu-wrapper">
<Menu width={220} onClick={this.handleMenuClick}>
{options.map((option) => (
<Menu.MenuItem key={option.value}>
<img src={defaultIcon} alt="" />
{option.label}
</Menu.MenuItem>
))}
</Menu>
<div className={styles.bottom}>
{loading && <Spin size="small" spinning={loading} />}
</div>
</div>
);
}
render() {
const { icon, defaultIcon, value, type, options } = this.props;
const option = options.find((item) => item.value === value) || {};
return (
<div ref={this.contentRef}>
<Dropdown
className={classNames('dropdown-default', styles.dropdown)}
content={this.renderList()}
>
<div
className={classNames(styles.titleWrapper, {
[styles.multi]: this.isMulti,
})}
>
<div className={styles.icon}>
<img src={icon || defaultIcon} alt="" />
</div>
<div className={styles.text}>
<p>{type}</p>
<div className="h6">{option.label || value}</div>
</div>
{this.isMulti && (
<div className={styles.arrow}>
<Icon name="caret-down" type="light" />
</div>
)}
</div>
</Dropdown>
</div>
);
}
}

View File

@ -1,98 +0,0 @@
@import '~styles/variables';
@import '~styles/mixins';
.titleWrapper {
position: relative;
margin-bottom: 20px;
padding: 12px;
border-radius: $border-radius;
background-color: $background-color;
box-shadow: 0 8px 16px 0 rgba(36, 46, 66, 0.2);
.icon {
display: inline-block;
vertical-align: middle;
width: 40px;
height: 40px;
padding: 8px;
margin-right: 12px;
border-radius: 100px 0 100px 100px;
background-color: rgba(239, 244, 249, 0.08);
img {
width: 24px;
height: 24px;
}
}
.text {
display: inline-block;
vertical-align: middle;
width: 124px;
:global .h6 {
font-family: $font-family-id;
line-height: 1.43;
color: #ffffff;
@include ellipsis;
}
p {
color: #d8dee5;
}
}
.arrow {
position: absolute;
bottom: 12px;
right: 12px;
width: 20px;
height: 20px;
padding: 3px;
border-radius: 50%;
background-color: rgba(85, 188, 138, 0.1);
:global .icon {
width: 14px;
height: 14px;
background-color: $primary;
border-radius: 50%;
vertical-align: inherit;
}
}
}
.multi {
cursor: pointer;
}
.dropdown {
background-color: $background-color;
:global {
.dropdown-content {
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
}
.menu {
padding: 12px;
}
.menu-item {
padding: 6px 20px 6px 14px;
img {
width: 16px;
height: 16px;
margin-right: 10px;
}
}
}
}
.bottom {
position: relative;
text-align: center;
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export { default as Nav } from './Nav';
export { default as GlobalNav } from './GlobalNav';
export { default as Selector } from './Selector';

View File

@ -165,4 +165,5 @@
.magic-input-checks { .magic-input-checks {
line-height: 32px; line-height: 32px;
margin-left: 8px; margin-left: 8px;
min-width: 120px;
} }

View File

@ -32,6 +32,7 @@ const successKeys = [
'power on', 'power on',
'complete', 'complete',
'online', 'online',
'ready',
]; ];
const successKeysContain = ['complete']; const successKeysContain = ['complete'];
@ -90,7 +91,7 @@ const getStatus = (key) => {
export default class index extends Component { export default class index extends Component {
static propTypes = { static propTypes = {
status: PropTypes.string, status: PropTypes.any,
text: PropTypes.string, text: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
}; };

View File

@ -48,6 +48,7 @@ export default class SimpleTable extends React.Component {
// eslint-disable-next-line react/no-unused-prop-types // eslint-disable-next-line react/no-unused-prop-types
defaultSortOrder: PropTypes.string, defaultSortOrder: PropTypes.string,
onRow: PropTypes.func, onRow: PropTypes.func,
childrenColumnName: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -78,6 +79,7 @@ export default class SimpleTable extends React.Component {
render, render,
isStatus, isStatus,
isName, isName,
isPrice,
...rest ...rest
} = column; } = column;
if (column.key === 'operation') { if (column.key === 'operation') {
@ -99,6 +101,9 @@ export default class SimpleTable extends React.Component {
if (dataIndex === 'name' || isName) { if (dataIndex === 'name' || isName) {
newRender = this.getNameRender(newRender, column); newRender = this.getNameRender(newRender, column);
} }
if (dataIndex === 'cost' || isPrice) {
newRender = this.getPriceRender(newRender, column);
}
const newColumn = { const newColumn = {
...rest, ...rest,
dataIndex, dataIndex,
@ -196,6 +201,17 @@ export default class SimpleTable extends React.Component {
return baseColumns; return baseColumns;
}; };
// eslint-disable-next-line no-unused-vars
getPriceRender = (render, column) => {
if (render) {
return render;
}
return (value) => {
const valueStr = isString(value) ? value : (value || 0).toFixed(2);
return <span style={{ color: '#f50' }}>{valueStr}</span>;
};
};
getDataSource = () => { getDataSource = () => {
const { datas, filters, filterByBackend } = this.props; const { datas, filters, filterByBackend } = this.props;
if (filterByBackend) { if (filterByBackend) {
@ -297,7 +313,8 @@ export default class SimpleTable extends React.Component {
}; };
render() { render() {
const { className, isLoading, rowSelection, footer } = this.props; const { className, isLoading, rowSelection, footer, childrenColumnName } =
this.props;
const currentColumns = this.getColumns(); const currentColumns = this.getColumns();
const dataSource = this.getDataSource(); const dataSource = this.getDataSource();
@ -314,6 +331,7 @@ export default class SimpleTable extends React.Component {
showSorterTooltip={false} showSorterTooltip={false}
footer={footer} footer={footer}
onRow={this.onRow} onRow={this.onRow}
childrenColumnName={childrenColumnName}
/> />
); );
} }

View File

@ -1,62 +0,0 @@
// Copyright 2021 99cloud
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { Component } from 'react';
import { observer } from 'mobx-react';
@observer
export default class Observer extends Component {
constructor(props) {
super(props);
this.state = { visible: !window.IntersectionObserver };
this.io = null;
this.container = null;
}
componentDidMount() {
(window.IntersectionObserver
? Promise.resolve()
: import('intersection-observer')
).then(() => {
this.io = new window.IntersectionObserver((entries) => {
entries.forEach((entry) => {
this.setState({ visible: entry.isIntersecting });
});
}, {});
this.io.observe(this.container);
});
}
componentWillUnmount() {
if (this.io) {
this.io.disconnect();
}
}
render() {
return (
// 使 findDOMNode
<div
ref={(div) => {
this.container = div;
}}
{...this.props}
>
{Array.isArray(this.props.children)
? this.props.children.map((child) => child(this.state.visible))
: this.props.children(this.state.visible)}
</div>
);
}
}

View File

@ -25,9 +25,7 @@ import styles from './index.less';
const { SubMenu } = Menu; const { SubMenu } = Menu;
@inject('rootStore') export class LayoutMenu extends Component {
@observer
class LayoutMenu extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -57,6 +55,10 @@ class LayoutMenu extends Component {
this.setState({ collapsed }); this.setState({ collapsed });
}; };
getImage(isExtend) {
return !isExtend ? logoSmall : logoExtend;
}
changeCollapse = () => { changeCollapse = () => {
const { collapsed } = this.state; const { collapsed } = this.state;
this.setState({ this.setState({
@ -200,7 +202,7 @@ class LayoutMenu extends Component {
renderLogo() { renderLogo() {
const { collapsed, hover } = this.state; const { collapsed, hover } = this.state;
const isExtend = !collapsed || hover; const isExtend = !collapsed || hover;
const imageSvg = !isExtend ? logoSmall : logoExtend; const imageSvg = this.getImage(isExtend);
const homeUrl = this.getUrl('/base/overview'); const homeUrl = this.getUrl('/base/overview');
return ( return (
<div <div
@ -240,4 +242,4 @@ class LayoutMenu extends Component {
} }
} }
export default LayoutMenu; export default inject('rootStore')(observer(LayoutMenu));

View File

@ -30,9 +30,7 @@ import styles from './index.less';
const { Header } = Layout; const { Header } = Layout;
@inject('rootStore') export class BaseLayout extends Component {
@observer
class BaseLayout extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -204,7 +202,7 @@ class BaseLayout extends Component {
}; };
init() { init() {
if (this.isAdminPage && !this.hasAdminRole) { if (this.isAdminPage && !this.hasAdminPageRole) {
window.location.href = '/base/overview'; window.location.href = '/base/overview';
} }
this.routes = this.props.route.routes; this.routes = this.props.route.routes;
@ -260,4 +258,4 @@ class BaseLayout extends Component {
} }
} }
export default BaseLayout; export default inject('rootStore')(observer(BaseLayout));

View File

@ -1572,9 +1572,9 @@
"The name cannot be modified after creation": "The name cannot be modified after creation", "The name cannot be modified after creation": "The name cannot be modified after creation",
"The name of the physical network to which a port is connected": "The name of the physical network to which a port is connected", "The name of the physical network to which a port is connected": "The name of the physical network to which a port is connected",
"The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".", "The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_().\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_().\".", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].\".",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_.\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_.\".",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".",
"The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".": "The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".", "The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".": "The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".",
"The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".": "The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".", "The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".": "The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".",

View File

@ -869,6 +869,7 @@
"Invalid: Please input a valid ip": "无效请输入有效的IP", "Invalid: Please input a valid ip": "无效请输入有效的IP",
"Invalid: Please input a valid ipv4": "无效请输入有效的IPV4", "Invalid: Please input a valid ipv4": "无效请输入有效的IPV4",
"Invalid: Please input a valid ipv6.": "无效请输入有效的IPV6", "Invalid: Please input a valid ipv6.": "无效请输入有效的IPV6",
"Invalid: Project name can not be chinese": "无效:项目名称不可使用中文",
"Invalid: Project name can not be duplicated": "无效:项目名称不可重复", "Invalid: Project name can not be duplicated": "无效:项目名称不可重复",
"Invalid: Quota value(s) cannot be less than the current usage value(s): { used } used.": "无效:配额必须大于已使用数量{ used }且为整数", "Invalid: Quota value(s) cannot be less than the current usage value(s): { used } used.": "无效:配额必须大于已使用数量{ used }且为整数",
"Invalid: User Group name can not be duplicated": "无效:用户组名称不可重复", "Invalid: User Group name can not be duplicated": "无效:用户组名称不可重复",
@ -1571,9 +1572,9 @@
"The name cannot be modified after creation": "名称创建后不可修改", "The name cannot be modified after creation": "名称创建后不可修改",
"The name of the physical network to which a port is connected": "端口连接到的物理网络的名称", "The name of the physical network to which a port is connected": "端口连接到的物理网络的名称",
"The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "名称应以大写字母或小写字母开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].:^”。", "The name should start with upper letter or lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "名称应以大写字母或小写字母开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].:^”。",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_().\".": "名称应以大写字母或小写字母开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_().”。", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].\".": "名称应以大写字母,小写字母或中文开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].”。",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "名称应以大写字母小写字母或中文开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].:^”。", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].:^\".": "名称应以大写字母小写字母或中文开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].:^”。",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "名称应以大写字母小写字母或中文开头最长为128字符且只包含“0-9, a-z, A-Z, \"'-_()[].^”。", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, \"-'_.\".": "名称应以大写字母小写字母或中文开头最长为128字符且只包含“0-9, a-z, A-Z, \"-'_.”。",
"The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "名称应以大写字母小写字母或中文开头最长为64字符且只包含“0-9, a-z, A-Z, \"'-_()[].^”。", "The name should start with upper letter, lower letter or chinese, and be a string of 1 to 64, characters can only contain \"0-9, a-z, A-Z, \"-'_()[].^\".": "名称应以大写字母小写字母或中文开头最长为64字符且只包含“0-9, a-z, A-Z, \"'-_()[].^”。",
"The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".": "名称应以大写字母或小写字母开头最长为128字符且只包含“0-9, a-z, A-Z, -, _”。", "The name should start with upper letter, lower letter, and be a string of 1 to 128, characters can only contain \"0-9, a-z, A-Z, -, _\".": "名称应以大写字母或小写字母开头最长为128字符且只包含“0-9, a-z, A-Z, -, _”。",
"The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".": "名称应以大写字母或小写字母开头最长为255字符且只包含“0-9, a-z, A-Z, -, ., _”。", "The name should start with upper letter, lower letter, and be a string of 2 to 255, characters can only contain \"0-9, a-z, A-Z, -, ., _\".": "名称应以大写字母或小写字母开头最长为255字符且只包含“0-9, a-z, A-Z, -, ., _”。",

View File

@ -49,9 +49,9 @@ const keypairNameRegex = /^[a-zA-Z][\w_-]{0,127}$/;
const crontabNameRegex = const crontabNameRegex =
/^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'\[\]^.:()_-]{0,63}$/; // eslint-disable-line /^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'\[\]^.:()_-]{0,63}$/; // eslint-disable-line
const imageNameRegex = const imageNameRegex =
/^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'\[\]^.()_-]{0,127}$/; // eslint-disable-line /^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'\[\].()_-]{0,127}$/; // eslint-disable-line
const instanceNameRegex = const instanceNameRegex =
/^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'.()_-]{0,127}$/; /^[a-zA-Z\u4e00-\u9fa5][\u4e00-\u9fa5\w"'._-]{0,127}$/;
const ipv6CidrOnly = const ipv6CidrOnly =
/^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*\/(1[01][0-9]|12[0-8]|[0-9]{1,2})$/; /^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$|^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*\/(1[01][0-9]|12[0-8]|[0-9]{1,2})$/;
@ -307,11 +307,11 @@ const crontabNameMessage = t(
); );
const imageNameMessage = t( const imageNameMessage = t(
'The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain "0-9, a-z, A-Z, "-\'_()[].^".' 'The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain "0-9, a-z, A-Z, "-\'_()[].".'
); );
const instanceNameMessage = t( const instanceNameMessage = t(
'The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain "0-9, a-z, A-Z, "-\'_().".' 'The name should start with upper letter, lower letter or chinese, and be a string of 1 to 128, characters can only contain "0-9, a-z, A-Z, "-\'_.".'
); );
export const nameMessageInfo = { export const nameMessageInfo = {
@ -414,7 +414,6 @@ const stackNameValidate = (rule, value) => {
}; };
const crontabNameValidate = (rule, value) => { const crontabNameValidate = (rule, value) => {
console.log(rule, value, isCrontabName(value));
if (!rule.required && value === undefined) { if (!rule.required && value === undefined) {
return Promise.resolve(true); return Promise.resolve(true);
} }