Commit 18e249b4 authored by 陈帅's avatar 陈帅

SearchListApplications finish

parent cf1553fb
import React from 'react';
import { FormattedMessage } from 'umi-plugin-react/locale';
import Link from 'umi/link';
import { PageHeader } from 'ant-design-pro';
import styles from './index.less';
const PageHeaderWrapper = ({ children, wrapperClassName, ...restProps }) => (
<div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
<PageHeader
home={<FormattedMessage id="BLOCK_NAME.menu.home" defaultMessage="Home" />}
key="pageheader"
{...restProps}
linkElement={Link}
itemRender={item => {
if (item.locale) {
return <FormattedMessage id={item.locale} defaultMessage={item.title} />;
}
return item.title;
}}
/>
{children ? <div className={styles.content}>{children}</div> : null}
</div>
);
export default PageHeaderWrapper;
@import '~antd/lib/style/themes/default.less';
.content {
.children-content {
margin: 24px 24px 0;
}
@media screen and (max-width: @screen-sm) {
.main {
.detail {
display: flex;
}
.row {
display: flex;
width: 100%;
}
.title-content {
margin-bottom: 16px;
}
@media screen and (max-width: @screen-sm) {
.content {
margin: 24px 0 0;
}
}
.title,
.content {
flex: auto;
}
.extraContent,
.main {
flex: 0 1 auto;
}
.main {
width: 100%;
}
.title {
margin-bottom: 16px;
}
.logo,
.content,
.extraContent {
margin-bottom: 16px;
}
.extraContent {
min-width: 242px;
margin-left: 88px;
text-align: right;
}
}
@media screen and (max-width: @screen-xl) {
.extraContent {
margin-left: 44px;
}
}
@media screen and (max-width: @screen-lg) {
.extraContent {
margin-left: 20px;
}
}
@media screen and (max-width: @screen-md) {
.row {
display: block;
}
.action,
.extraContent {
margin-left: 0;
text-align: left;
}
}
@media screen and (max-width: @screen-sm) {
.detail {
display: block;
}
}
import React from 'react';
import { RouteContext } from '@ant-design/pro-layout';
import { PageHeader, Typography } from 'antd';
import styles from './index.less';
import { GridContent } from '@ant-design/pro-layout';
interface IPageHeaderWrapperProps {
content?: React.ReactNode;
title: React.ReactNode;
extraContent?: React.ReactNode;
}
const PageHeaderWrapper: React.SFC<IPageHeaderWrapperProps> = ({
children,
title,
content,
extraContent,
...restProps
}) => (
<RouteContext.Consumer>
{value => (
<div style={{ margin: '-24px -24px 0' }}>
<PageHeader
title={
<Typography.Title
level={4}
style={{
margin: 0,
}}
>
{title}
</Typography.Title>
}
{...restProps}
{...value}
>
<div className={styles.detail}>
<div className={styles.main}>
<div className={styles.row}>
{content && <div className={styles.content}>{content}</div>}
{extraContent && <div className={styles.extraContent}>{extraContent}</div>}
</div>
</div>
</div>
</PageHeader>
{children ? (
<GridContent>
<div className={styles['children-content']}>{children}</div>
</GridContent>
) : null}
</div>
)}
</RouteContext.Consumer>
);
export default PageHeaderWrapper;
......@@ -2,7 +2,22 @@ import React from 'react';
import classNames from 'classnames';
import styles from './index.less';
const StandardFormRow = ({ title, children, last, block, grid, ...rest }) => {
interface StandardFormRowProps {
title?: string;
last?: boolean;
block?: boolean;
grid?: boolean;
style?: React.CSSProperties;
}
const StandardFormRow: React.SFC<StandardFormRowProps> = ({
title,
children,
last,
block,
grid,
...rest
}) => {
const cls = classNames(styles.standardFormRow, {
[styles.standardFormRowBlock]: block,
[styles.standardFormRowLast]: last,
......
@import '~antd/lib/style/themes/default.less';
.tagSelect {
position: relative;
max-height: 32px;
margin-left: -8px;
overflow: hidden;
line-height: 32px;
transition: all 0.3s;
user-select: none;
:global {
.ant-tag {
margin-right: 24px;
padding: 0 8px;
font-size: @font-size-base;
}
}
&.expanded {
max-height: 200px;
transition: all 0.3s;
}
.trigger {
position: absolute;
top: 0;
right: 0;
i {
font-size: 12px;
}
}
&.hasExpandTag {
padding-right: 50px;
}
}
import React, { Component } from 'react';
import classNames from 'classnames';
import { Tag, Icon } from 'antd';
import styles from './index.less';
const { CheckableTag } = Tag;
export interface TagSelectOptionProps {
value?: string | number;
style?: React.CSSProperties;
checked?: boolean;
onChange?: (value: string | number | undefined, state: boolean) => void;
}
export interface TagSelectProps {
onChange?: (value: (string | number)[]) => void;
expandable?: boolean;
value?: (string | number)[];
defaultValue?: (string | number)[];
style?: React.CSSProperties;
hideCheckAll?: boolean;
actionsText?: {
expandText?: React.ReactNode;
collapseText?: React.ReactNode;
selectAllText?: React.ReactNode;
};
className?: string;
Option?: TagSelectOptionProps;
children?: React.ReactElement<TagSelectOption> | Array<React.ReactElement<TagSelectOption>>;
}
const TagSelectOption: React.SFC<TagSelectOptionProps> & {
isTagSelectOption: boolean;
} = ({ children, checked, onChange, value }) => (
<CheckableTag
checked={!!checked}
key={value}
onChange={state => onChange && onChange(value, state)}
>
{children}
</CheckableTag>
);
TagSelectOption.isTagSelectOption = true;
interface TagSelectState {
expand: boolean;
value: (string | number)[];
}
class TagSelect extends Component<TagSelectProps, TagSelectState> {
static defaultProps = {
hideCheckAll: false,
actionsText: {
expandText: 'Expand',
collapseText: 'Collapse',
selectAllText: 'All',
},
};
static Option: TagSelectOption = TagSelectOption;
constructor(props: TagSelectProps) {
super(props);
this.state = {
expand: false,
value: props.value || props.defaultValue || [],
};
}
static getDerivedStateFromProps(nextProps: TagSelectProps) {
if ('value' in nextProps) {
return { value: nextProps.value || [] };
}
return null;
}
onChange = (value: (string | number)[]) => {
const { onChange } = this.props;
if (!('value' in this.props)) {
this.setState({ value });
}
if (onChange) {
onChange(value);
}
};
onSelectAll = (checked: boolean) => {
let checkedTags: (string | number)[] = [];
if (checked) {
checkedTags = this.getAllTags();
}
this.onChange(checkedTags);
};
getAllTags() {
let { children } = this.props;
const childrenArray = React.Children.toArray(children) as React.ReactElement<TagSelectOption>[];
const checkedTags = childrenArray
.filter(child => this.isTagSelectOption(child))
.map(child => child.props.value);
return checkedTags || [];
}
handleTagChange = (value: string | number, checked: boolean) => {
const { value: StateValue } = this.state;
const checkedTags: (string | number)[] = [...StateValue];
const index = checkedTags.indexOf(value);
if (checked && index === -1) {
checkedTags.push(value);
} else if (!checked && index > -1) {
checkedTags.splice(index, 1);
}
this.onChange(checkedTags);
};
handleExpand = () => {
const { expand } = this.state;
this.setState({
expand: !expand,
});
};
isTagSelectOption = (node: React.ReactElement<TagSelectOption, TagSelectOption>) =>
node &&
node.type &&
(node.type.isTagSelectOption || node.type.displayName === 'TagSelectOption');
render() {
const { value, expand } = this.state;
const { children, hideCheckAll, className, style, expandable, actionsText = {} } = this.props;
const checkedAll = this.getAllTags().length === value.length;
const { expandText = 'Expand', collapseText = 'Collapse', selectAllText = 'All' } = actionsText;
const cls = classNames(styles.tagSelect, className, {
[styles.hasExpandTag]: expandable,
[styles.expanded]: expand,
});
return (
<div className={cls} style={style}>
{hideCheckAll ? null : (
<CheckableTag checked={checkedAll} key="tag-select-__all__" onChange={this.onSelectAll}>
{selectAllText}
</CheckableTag>
)}
{value &&
React.Children.map(children, (child: React.ReactElement<TagSelectOption>) => {
if (this.isTagSelectOption(child)) {
return React.cloneElement(child, {
key: `tag-select-${child.props.value}`,
value: child.props.value,
checked: value.indexOf(child.props.value) > -1,
onChange: this.handleTagChange,
});
}
return child;
})}
{expandable && (
<a className={styles.trigger} onClick={this.handleExpand}>
{expand ? collapseText : expandText} <Icon type={expand ? 'up' : 'down'} />
</a>
)}
</div>
);
}
}
export default TagSelect;
export interface Member {
avatar: string;
name: string;
id: string;
}
export interface ListItemDataType {
id: string;
owner: string;
title: string;
avatar: string;
cover: string;
status: 'normal' | 'exception' | 'active' | 'success';
percent: number;
logo: string;
href: string;
body?: any;
updatedAt: number;
createdAt: number;
subDescription: string;
description: string;
activeUser: number;
newUser: number;
star: number;
like: number;
message: number;
content: string;
members: Member[];
}
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,
Input,
} from 'antd';
import { TagSelect } from 'ant-design-pro';
import StandardFormRow from './components/StandardFormRow';
import PageHeaderWrapper from './components/PageHeaderWrapper';
import { formatWan } from './utils/utils';
import styles from './style.less';
const { Option } = Select;
const FormItem = Form.Item;
@connect(({ BLOCK_NAME_CAMEL_CASE, loading }) => ({
BLOCK_NAME_CAMEL_CASE,
loading: loading.models.BLOCK_NAME_CAMEL_CASE,
}))
@Form.create({
onValuesChange({ dispatch }, changedValues, allValues) {
// 表单项变化时请求数据
// eslint-disable-next-line
console.log(changedValues, allValues);
// 模拟查询表单生效
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetch',
payload: {
count: 8,
},
});
},
})
class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetch',
payload: {
count: 8,
},
});
}
render() {
const {
BLOCK_NAME_CAMEL_CASE: { list },
loading,
form,
} = this.props;
const { getFieldDecorator } = form;
const CardInfo = ({ activeUser, newUser }) => (
<div className={styles.cardInfo}>
<div>
<p>活跃用户</p>
<p>{activeUser}</p>
</div>
<div>
<p>新增用户</p>
<p>{newUser}</p>
</div>
</div>
);
const formItemLayout = {
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const itemMenu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.alipay.com/">
1st menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.taobao.com/">
2nd menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.tmall.com/">
3d menu item
</a>
</Menu.Item>
</Menu>
);
const mainSearch = (
<div style={{ textAlign: 'center' }}>
<Input.Search
placeholder="请输入"
enterButton="搜索"
size="large"
onSearch={this.handleFormSubmit}
style={{ width: 522 }}
/>
</div>
);
return (
<PageHeaderWrapper title="搜索列表" content={mainSearch} onTabChange={this.handleTabChange}>
<div className={styles.filterCardList}>
<Card bordered={false}>
<Form layout="inline">
<StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
<FormItem>
{getFieldDecorator('category')(
<TagSelect expandable>
<TagSelect.Option value="cat1">类目一</TagSelect.Option>
<TagSelect.Option value="cat2">类目二</TagSelect.Option>
<TagSelect.Option value="cat3">类目三</TagSelect.Option>
<TagSelect.Option value="cat4">类目四</TagSelect.Option>
<TagSelect.Option value="cat5">类目五</TagSelect.Option>
<TagSelect.Option value="cat6">类目六</TagSelect.Option>
<TagSelect.Option value="cat7">类目七</TagSelect.Option>
<TagSelect.Option value="cat8">类目八</TagSelect.Option>
<TagSelect.Option value="cat9">类目九</TagSelect.Option>
<TagSelect.Option value="cat10">类目十</TagSelect.Option>
<TagSelect.Option value="cat11">类目十一</TagSelect.Option>
<TagSelect.Option value="cat12">类目十二</TagSelect.Option>
</TagSelect>
)}
</FormItem>
</StandardFormRow>
<StandardFormRow title="其它选项" grid last>
<Row gutter={16}>
<Col lg={8} md={10} sm={10} xs={24}>
<FormItem {...formItemLayout} label="作者">
{getFieldDecorator('author', {})(
<Select placeholder="不限" style={{ maxWidth: 200, width: '100%' }}>
<Option value="lisa">王昭君</Option>
</Select>
)}
</FormItem>
</Col>
<Col lg={8} md={10} sm={10} xs={24}>
<FormItem {...formItemLayout} label="好评度">
{getFieldDecorator('rate', {})(
<Select placeholder="不限" style={{ maxWidth: 200, width: '100%' }}>
<Option value="good">优秀</Option>
<Option value="normal">普通</Option>
</Select>
)}
</FormItem>
</Col>
</Row>
</StandardFormRow>
</Form>
</Card>
<List
rowKey="id"
style={{ marginTop: 24 }}
grid={{ gutter: 24, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
loading={loading}
dataSource={list}
renderItem={item => (
<List.Item key={item.id}>
<Card
hoverable
bodyStyle={{ paddingBottom: 20 }}
actions={[
<Tooltip title="下载">
<Icon type="download" />
</Tooltip>,
<Tooltip title="编辑">
<Icon type="edit" />
</Tooltip>,
<Tooltip title="分享">
<Icon type="share-alt" />
</Tooltip>,
<Dropdown overlay={itemMenu}>
<Icon type="ellipsis" />
</Dropdown>,
]}
>
<Card.Meta
avatar={<Avatar size="small" src={item.avatar} />}
title={item.title}
/>
<div className={styles.cardItemContent}>
<CardInfo
activeUser={formatWan(item.activeUser)}
newUser={numeral(item.newUser).format('0,0')}
/>
</div>
</Card>
</List.Item>
)}
/>
</div>
</PageHeaderWrapper>
);
}
}
export default PAGE_NAME_UPPER_CAMEL_CASE;
import React, { Component } from 'react';
import numeral from 'numeral';
import { connect } from 'dva';
import {
Row,
Col,
Form,
Card,
Select,
Icon,
Avatar,
List,
Tooltip,
Dropdown,
Menu,
Input,
} from 'antd';
import TagSelect from './components/TagSelect';
import StandardFormRow from './components/StandardFormRow';
import PageHeaderWrapper from './components/PageHeaderWrapper';
import { formatWan } from './utils/utils';
import styles from './style.less';
import { IStateType } from './model';
import { Dispatch } from 'redux';
import { FormComponentProps } from 'antd/lib/form';
import { ListItemDataType } from './data';
const { Option } = Select;
const FormItem = Form.Item;
interface PAGE_NAME_UPPER_CAMEL_CASEProps extends FormComponentProps {
dispatch: Dispatch;
BLOCK_NAME_CAMEL_CASE: IStateType;
loading: boolean;
}
@connect(
({
BLOCK_NAME_CAMEL_CASE,
loading,
}: {
BLOCK_NAME_CAMEL_CASE: IStateType;
loading: { models: { [key: string]: boolean } };
}) => ({
BLOCK_NAME_CAMEL_CASE,
loading: loading.models.BLOCK_NAME_CAMEL_CASE,
})
)
class PAGE_NAME_UPPER_CAMEL_CASE extends Component<PAGE_NAME_UPPER_CAMEL_CASEProps> {
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetch',
payload: {
count: 8,
},
});
}
render() {
const {
BLOCK_NAME_CAMEL_CASE: { list },
loading,
form,
} = this.props;
const { getFieldDecorator } = form;
const CardInfo: React.SFC<{
activeUser: React.ReactNode;
newUser: React.ReactNode;
}> = ({ activeUser, newUser }) => (
<div className={styles.cardInfo}>
<div>
<p>活跃用户</p>
<p>{activeUser}</p>
</div>
<div>
<p>新增用户</p>
<p>{newUser}</p>
</div>
</div>
);
const formItemLayout = {
wrapperCol: {
xs: { span: 24 },
sm: { span: 16 },
},
};
const itemMenu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.alipay.com/">
1st menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.taobao.com/">
2nd menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.tmall.com/">
3d menu item
</a>
</Menu.Item>
</Menu>
);
return (
<div className={styles.filterCardList}>
<Card bordered={false}>
<Form layout="inline">
<StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
<FormItem>
{getFieldDecorator('category')(
<TagSelect expandable>
<TagSelect.Option value="cat1">类目一</TagSelect.Option>
<TagSelect.Option value="cat2">类目二</TagSelect.Option>
<TagSelect.Option value="cat3">类目三</TagSelect.Option>
<TagSelect.Option value="cat4">类目四</TagSelect.Option>
<TagSelect.Option value="cat5">类目五</TagSelect.Option>
<TagSelect.Option value="cat6">类目六</TagSelect.Option>
<TagSelect.Option value="cat7">类目七</TagSelect.Option>
<TagSelect.Option value="cat8">类目八</TagSelect.Option>
<TagSelect.Option value="cat9">类目九</TagSelect.Option>
<TagSelect.Option value="cat10">类目十</TagSelect.Option>
<TagSelect.Option value="cat11">类目十一</TagSelect.Option>
<TagSelect.Option value="cat12">类目十二</TagSelect.Option>
</TagSelect>
)}
</FormItem>
</StandardFormRow>
<StandardFormRow title="其它选项" grid last>
<Row gutter={16}>
<Col lg={8} md={10} sm={10} xs={24}>
<FormItem {...formItemLayout} label="作者">
{getFieldDecorator('author', {})(
<Select placeholder="不限" style={{ maxWidth: 200, width: '100%' }}>
<Option value="lisa">王昭君</Option>
</Select>
)}
</FormItem>
</Col>
<Col lg={8} md={10} sm={10} xs={24}>
<FormItem {...formItemLayout} label="好评度">
{getFieldDecorator('rate', {})(
<Select placeholder="不限" style={{ maxWidth: 200, width: '100%' }}>
<Option value="good">优秀</Option>
<Option value="normal">普通</Option>
</Select>
)}
</FormItem>
</Col>
</Row>
</StandardFormRow>
</Form>
</Card>
<List<ListItemDataType>
rowKey="id"
grid={{ gutter: 24, xl: 4, lg: 3, md: 3, sm: 2, xs: 1 }}
loading={loading}
dataSource={list}
renderItem={item => (
<List.Item key={item.id}>
<Card
hoverable
bodyStyle={{ paddingBottom: 20 }}
actions={[
<Tooltip title="下载">
<Icon type="download" />
</Tooltip>,
<Tooltip title="编辑">
<Icon type="edit" />
</Tooltip>,
<Tooltip title="分享">
<Icon type="share-alt" />
</Tooltip>,
<Dropdown overlay={itemMenu}>
<Icon type="ellipsis" />
</Dropdown>,
]}
>
<Card.Meta avatar={<Avatar size="small" src={item.avatar} />} title={item.title} />
<div className={styles.cardItemContent}>
<CardInfo
activeUser={formatWan(item.activeUser)}
newUser={numeral(item.newUser).format('0,0')}
/>
</div>
</Card>
</List.Item>
)}
/>
</div>
);
}
}
export default Form.create({
onValuesChange({ dispatch }: PAGE_NAME_UPPER_CAMEL_CASEProps, changedValues, allValues) {
// 表单项变化时请求数据
// 模拟查询表单生效
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetch',
payload: {
count: 8,
},
});
},
})(PAGE_NAME_UPPER_CAMEL_CASE);
import { queryFakeList } from './service';
import { ListItemDataType } from './data';
import { Reducer } from 'redux';
import { EffectsCommandMap } from 'dva';
import { AnyAction } from 'redux';
export default {
export interface IStateType {
list: ListItemDataType[];
}
export type Effect = (
action: AnyAction,
effects: EffectsCommandMap & { select: <T>(func: (state: IStateType) => T) => T }
) => void;
export interface ModelType {
namespace: string;
state: IStateType;
effects: {
fetch: Effect;
};
reducers: {
queryList: Reducer<IStateType>;
};
}
const Model: ModelType = {
namespace: 'BLOCK_NAME_CAMEL_CASE',
state: {
......@@ -26,3 +50,5 @@ export default {
},
},
};
export default Model;
import React, { Component } from 'react';
import { connect } from 'dva';
import { Form, Card, Select, List, Tag, Icon, Row, Col, Button, Input } from 'antd';
import { Form, Card, Select, List, Tag, Icon, Row, Col, Button } from 'antd';
import TagSelect from 'ant-design-pro/lib/TagSelect';
import TagSelect from './components/TagSelect';
import StandardFormRow from './components/StandardFormRow';
import ArticleListContent from './components/ArticleListContent';
import PageHeaderWrapper from './components/PageHeaderWrapper';
import styles from './style.less';
const { Option } = Select;
......@@ -120,20 +119,8 @@ class SearchList extends Component {
</div>
) : null;
const mainSearch = (
<div style={{ textAlign: 'center' }}>
<Input.Search
placeholder="请输入"
enterButton="搜索"
size="large"
onSearch={this.handleFormSubmit}
style={{ width: 522 }}
/>
</div>
);
return (
<PageHeaderWrapper title="搜索列表" content={mainSearch} onTabChange={this.handleTabChange}>
<>
<Card bordered={false}>
<Form layout="inline">
<StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}>
......@@ -247,7 +234,7 @@ class SearchList extends Component {
)}
/>
</Card>
</PageHeaderWrapper>
</>
);
}
}
......
......@@ -6,7 +6,6 @@ import { Row, Col, Form, Card, Select, List, Input } from 'antd';
import { TagSelect, AvatarList, Ellipsis } from 'ant-design-pro';
import StandardFormRow from './components/StandardFormRow';
import PageHeaderWrapper from './components/PageHeaderWrapper';
import styles from './style.less';
const { Option } = Select;
......@@ -107,7 +106,6 @@ class CoverCardList extends PureComponent {
);
return (
<PageHeaderWrapper title="搜索列表" content={mainSearch} onTabChange={this.handleTabChange}>
<div className={styles.coverCardList}>
<Card bordered={false}>
<Form layout="inline">
......@@ -158,7 +156,6 @@ class CoverCardList extends PureComponent {
</Card>
<div className={styles.cardList}>{cardList}</div>
</div>
</PageHeaderWrapper>
);
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment