23 changed files with 0 additions and 4741 deletions
@ -1,130 +0,0 @@ |
|||||
import { |
|
||||
DownloadOutlined, |
|
||||
EditOutlined, |
|
||||
EllipsisOutlined, |
|
||||
ShareAltOutlined, |
|
||||
} from '@ant-design/icons'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Avatar, Card, Dropdown, List, Tooltip } from 'antd'; |
|
||||
import numeral from 'numeral'; |
|
||||
import React from 'react'; |
|
||||
import type { ListItemDataType } from '../../data.d'; |
|
||||
import { queryFakeList } from '../../service'; |
|
||||
import useStyles from './index.style'; |
|
||||
export function formatWan(val: number) { |
|
||||
const v = val * 1; |
|
||||
if (!v || Number.isNaN(v)) return ''; |
|
||||
let result: React.ReactNode = val; |
|
||||
if (val > 10000) { |
|
||||
result = ( |
|
||||
<span> |
|
||||
{Math.floor(val / 10000)} |
|
||||
<span |
|
||||
style={{ |
|
||||
position: 'relative', |
|
||||
top: -2, |
|
||||
fontSize: 14, |
|
||||
fontStyle: 'normal', |
|
||||
marginLeft: 2, |
|
||||
}} |
|
||||
> |
|
||||
万 |
|
||||
</span> |
|
||||
</span> |
|
||||
); |
|
||||
} |
|
||||
return result; |
|
||||
} |
|
||||
const Applications: React.FC = () => { |
|
||||
const { styles: stylesApplications } = useStyles(); |
|
||||
// 获取tab列表数据
|
|
||||
const { data: listData } = useRequest(() => { |
|
||||
return queryFakeList({ |
|
||||
count: 30, |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
const CardInfo: React.FC<{ |
|
||||
activeUser: React.ReactNode; |
|
||||
newUser: React.ReactNode; |
|
||||
}> = ({ activeUser, newUser }) => ( |
|
||||
<div className={stylesApplications.cardInfo}> |
|
||||
<div> |
|
||||
<p>活跃用户</p> |
|
||||
<p>{activeUser}</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<p>新增用户</p> |
|
||||
<p>{newUser}</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
return ( |
|
||||
<List<ListItemDataType> |
|
||||
rowKey="id" |
|
||||
className={stylesApplications.filterCardList} |
|
||||
grid={{ |
|
||||
gutter: 24, |
|
||||
xxl: 3, |
|
||||
xl: 2, |
|
||||
lg: 2, |
|
||||
md: 2, |
|
||||
sm: 2, |
|
||||
xs: 1, |
|
||||
}} |
|
||||
dataSource={listData?.list || []} |
|
||||
renderItem={(item) => ( |
|
||||
<List.Item key={item.id}> |
|
||||
<Card |
|
||||
hoverable |
|
||||
styles={{ |
|
||||
body: { |
|
||||
paddingBottom: 20, |
|
||||
}, |
|
||||
}} |
|
||||
actions={[ |
|
||||
<Tooltip key="download" title="下载"> |
|
||||
<DownloadOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Tooltip title="编辑" key="edit"> |
|
||||
<EditOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Tooltip title="分享" key="share"> |
|
||||
<ShareAltOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Dropdown |
|
||||
menu={{ |
|
||||
items: [ |
|
||||
{ |
|
||||
key: '1', |
|
||||
title: '1st menu item', |
|
||||
}, |
|
||||
{ |
|
||||
key: '2', |
|
||||
title: '2nd menu item', |
|
||||
}, |
|
||||
], |
|
||||
}} |
|
||||
key="ellipsis" |
|
||||
> |
|
||||
<EllipsisOutlined /> |
|
||||
</Dropdown>, |
|
||||
]} |
|
||||
> |
|
||||
<Card.Meta |
|
||||
avatar={<Avatar size="small" src={item.avatar} />} |
|
||||
title={item.title} |
|
||||
/> |
|
||||
<div> |
|
||||
<CardInfo |
|
||||
activeUser={formatWan(item.activeUser)} |
|
||||
newUser={numeral(item.newUser).format('0,0')} |
|
||||
/> |
|
||||
</div> |
|
||||
</Card> |
|
||||
</List.Item> |
|
||||
)} |
|
||||
/> |
|
||||
); |
|
||||
}; |
|
||||
export default Applications; |
|
||||
@ -1,278 +0,0 @@ |
|||||
import { |
|
||||
ClusterOutlined, |
|
||||
ContactsOutlined, |
|
||||
HomeOutlined, |
|
||||
PlusOutlined, |
|
||||
} from '@ant-design/icons'; |
|
||||
import { GridContent } from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { |
|
||||
Avatar, |
|
||||
Card, |
|
||||
Col, |
|
||||
Divider, |
|
||||
Input, |
|
||||
type InputRef, |
|
||||
Row, |
|
||||
Tag, |
|
||||
} from 'antd'; |
|
||||
import React, { useRef, useState } from 'react'; |
|
||||
import useStyles from './Center.style'; |
|
||||
import Applications from './components/Applications'; |
|
||||
import Articles from './components/Articles'; |
|
||||
import Projects from './components/Projects'; |
|
||||
import type { CurrentUser, TagType, tabKeyType } from './data.d'; |
|
||||
import { queryCurrent } from './service'; |
|
||||
|
|
||||
const operationTabList = [ |
|
||||
{ |
|
||||
key: 'articles', |
|
||||
tab: ( |
|
||||
<span> |
|
||||
文章{' '} |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
(8) |
|
||||
</span> |
|
||||
</span> |
|
||||
), |
|
||||
}, |
|
||||
{ |
|
||||
key: 'applications', |
|
||||
tab: ( |
|
||||
<span> |
|
||||
应用{' '} |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
(8) |
|
||||
</span> |
|
||||
</span> |
|
||||
), |
|
||||
}, |
|
||||
{ |
|
||||
key: 'projects', |
|
||||
tab: ( |
|
||||
<span> |
|
||||
项目{' '} |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
(8) |
|
||||
</span> |
|
||||
</span> |
|
||||
), |
|
||||
}, |
|
||||
]; |
|
||||
const TagList: React.FC<{ |
|
||||
tags: CurrentUser['tags']; |
|
||||
}> = ({ tags }) => { |
|
||||
const { styles } = useStyles(); |
|
||||
const ref = useRef<InputRef | null>(null); |
|
||||
const [newTags, setNewTags] = useState<TagType[]>([]); |
|
||||
const [inputVisible, setInputVisible] = useState<boolean>(false); |
|
||||
const [inputValue, setInputValue] = useState<string>(''); |
|
||||
const showInput = () => { |
|
||||
setInputVisible(true); |
|
||||
if (ref.current) { |
|
||||
// eslint-disable-next-line no-unused-expressions
|
|
||||
ref.current?.focus(); |
|
||||
} |
|
||||
}; |
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
|
||||
setInputValue(e.target.value); |
|
||||
}; |
|
||||
const handleInputConfirm = () => { |
|
||||
let tempsTags = [...newTags]; |
|
||||
if ( |
|
||||
inputValue && |
|
||||
tempsTags.filter((tag) => tag.label === inputValue).length === 0 |
|
||||
) { |
|
||||
tempsTags = [ |
|
||||
...tempsTags, |
|
||||
{ |
|
||||
key: `new-${tempsTags.length}`, |
|
||||
label: inputValue, |
|
||||
}, |
|
||||
]; |
|
||||
} |
|
||||
setNewTags(tempsTags); |
|
||||
setInputVisible(false); |
|
||||
setInputValue(''); |
|
||||
}; |
|
||||
return ( |
|
||||
<div className={styles.tags}> |
|
||||
<div className={styles.tagsTitle}>标签</div> |
|
||||
{(tags || []).concat(newTags).map((item) => ( |
|
||||
<Tag key={item.key}>{item.label}</Tag> |
|
||||
))} |
|
||||
{inputVisible && ( |
|
||||
<Input |
|
||||
ref={ref} |
|
||||
size="small" |
|
||||
style={{ |
|
||||
width: 78, |
|
||||
}} |
|
||||
value={inputValue} |
|
||||
onChange={handleInputChange} |
|
||||
onBlur={handleInputConfirm} |
|
||||
onPressEnter={handleInputConfirm} |
|
||||
/> |
|
||||
)} |
|
||||
{!inputVisible && ( |
|
||||
<Tag |
|
||||
onClick={showInput} |
|
||||
style={{ |
|
||||
borderStyle: 'dashed', |
|
||||
}} |
|
||||
> |
|
||||
<PlusOutlined /> |
|
||||
</Tag> |
|
||||
)} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
const Center: React.FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const [tabKey, setTabKey] = useState<tabKeyType>('articles'); |
|
||||
|
|
||||
// 获取用户信息
|
|
||||
const { data: currentUser, loading } = useRequest(() => { |
|
||||
return queryCurrent(); |
|
||||
}); |
|
||||
|
|
||||
// 渲染用户信息
|
|
||||
const renderUserInfo = ({ |
|
||||
title, |
|
||||
group, |
|
||||
geographic, |
|
||||
}: Partial<CurrentUser>) => { |
|
||||
return ( |
|
||||
<div className={styles.detail}> |
|
||||
<p> |
|
||||
<ContactsOutlined |
|
||||
style={{ |
|
||||
marginRight: 8, |
|
||||
}} |
|
||||
/> |
|
||||
{title} |
|
||||
</p> |
|
||||
<p> |
|
||||
<ClusterOutlined |
|
||||
style={{ |
|
||||
marginRight: 8, |
|
||||
}} |
|
||||
/> |
|
||||
{group} |
|
||||
</p> |
|
||||
<p> |
|
||||
<HomeOutlined |
|
||||
style={{ |
|
||||
marginRight: 8, |
|
||||
}} |
|
||||
/> |
|
||||
{ |
|
||||
( |
|
||||
geographic || { |
|
||||
province: { |
|
||||
label: '', |
|
||||
}, |
|
||||
} |
|
||||
).province.label |
|
||||
} |
|
||||
{ |
|
||||
( |
|
||||
geographic || { |
|
||||
city: { |
|
||||
label: '', |
|
||||
}, |
|
||||
} |
|
||||
).city.label |
|
||||
} |
|
||||
</p> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
// 渲染tab切换
|
|
||||
const renderChildrenByTabKey = (tabValue: tabKeyType) => { |
|
||||
if (tabValue === 'projects') { |
|
||||
return <Projects />; |
|
||||
} |
|
||||
if (tabValue === 'applications') { |
|
||||
return <Applications />; |
|
||||
} |
|
||||
if (tabValue === 'articles') { |
|
||||
return <Articles />; |
|
||||
} |
|
||||
return null; |
|
||||
}; |
|
||||
return ( |
|
||||
<GridContent> |
|
||||
<Row gutter={24}> |
|
||||
<Col lg={7} md={24}> |
|
||||
<Card |
|
||||
variant="borderless" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
loading={loading} |
|
||||
> |
|
||||
{!loading && currentUser && ( |
|
||||
<> |
|
||||
<div className={styles.avatarHolder}> |
|
||||
<img alt="" src={currentUser.avatar} /> |
|
||||
<div className={styles.name}>{currentUser.name}</div> |
|
||||
<div>{currentUser?.signature}</div> |
|
||||
</div> |
|
||||
{renderUserInfo(currentUser)} |
|
||||
<Divider dashed /> |
|
||||
<TagList tags={currentUser.tags || []} /> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
marginTop: 16, |
|
||||
}} |
|
||||
dashed |
|
||||
/> |
|
||||
<div className={styles.team}> |
|
||||
<div className={styles.teamTitle}>团队</div> |
|
||||
<Row gutter={36}> |
|
||||
{currentUser.notice?.map((item) => ( |
|
||||
<Col key={item.id} lg={24} xl={12}> |
|
||||
<a href={item.href}> |
|
||||
<Avatar size="small" src={item.logo} /> |
|
||||
{item.member} |
|
||||
</a> |
|
||||
</Col> |
|
||||
))} |
|
||||
</Row> |
|
||||
</div> |
|
||||
</> |
|
||||
)} |
|
||||
</Card> |
|
||||
</Col> |
|
||||
<Col lg={17} md={24}> |
|
||||
<Card |
|
||||
className={styles.tabsCard} |
|
||||
variant="borderless" |
|
||||
tabList={operationTabList} |
|
||||
activeTabKey={tabKey} |
|
||||
onTabChange={(_tabKey: string) => { |
|
||||
setTabKey(_tabKey as tabKeyType); |
|
||||
}} |
|
||||
> |
|
||||
{renderChildrenByTabKey(tabKey)} |
|
||||
</Card> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</GridContent> |
|
||||
); |
|
||||
}; |
|
||||
export default Center; |
|
||||
@ -1,110 +0,0 @@ |
|||||
import { Card } from 'antd'; |
|
||||
import type { CardProps } from 'antd/es/card'; |
|
||||
import classNames from 'classnames'; |
|
||||
import omit from 'rc-util/lib/omit'; |
|
||||
import React from 'react'; |
|
||||
import useStyles from './index.style'; |
|
||||
|
|
||||
type totalType = () => React.ReactNode; |
|
||||
|
|
||||
export type ChartCardProps = { |
|
||||
title: React.ReactNode; |
|
||||
action?: React.ReactNode; |
|
||||
total?: React.ReactNode | number | (() => React.ReactNode | number); |
|
||||
footer?: React.ReactNode; |
|
||||
contentHeight?: number; |
|
||||
avatar?: React.ReactNode; |
|
||||
style?: React.CSSProperties; |
|
||||
} & CardProps; |
|
||||
|
|
||||
const ChartCard: React.FC<ChartCardProps> = (props) => { |
|
||||
const { styles } = useStyles(); |
|
||||
const renderTotal = (total?: number | totalType | React.ReactNode) => { |
|
||||
if (!total && total !== 0) { |
|
||||
return null; |
|
||||
} |
|
||||
let totalDom: React.ReactNode | null = null; |
|
||||
switch (typeof total) { |
|
||||
case 'undefined': |
|
||||
totalDom = null; |
|
||||
break; |
|
||||
case 'function': |
|
||||
totalDom = <div className={styles.total}>{total()}</div>; |
|
||||
break; |
|
||||
default: |
|
||||
totalDom = <div className={styles.total}>{total}</div>; |
|
||||
} |
|
||||
return totalDom; |
|
||||
}; |
|
||||
const renderContent = () => { |
|
||||
const { |
|
||||
contentHeight, |
|
||||
title, |
|
||||
avatar, |
|
||||
action, |
|
||||
total, |
|
||||
footer, |
|
||||
children, |
|
||||
loading, |
|
||||
} = props; |
|
||||
if (loading) { |
|
||||
return false; |
|
||||
} |
|
||||
return ( |
|
||||
<div className={styles.chartCard}> |
|
||||
<div |
|
||||
className={classNames(styles.chartTop, { |
|
||||
[styles.chartTopMargin]: !children && !footer, |
|
||||
})} |
|
||||
> |
|
||||
<div className={styles.avatar}>{avatar}</div> |
|
||||
<div className={styles.metaWrap}> |
|
||||
<div className={styles.meta}> |
|
||||
<span>{title}</span> |
|
||||
<span className={styles.action}>{action}</span> |
|
||||
</div> |
|
||||
{renderTotal(total)} |
|
||||
</div> |
|
||||
</div> |
|
||||
{children && ( |
|
||||
<div |
|
||||
className={styles.content} |
|
||||
style={{ |
|
||||
height: contentHeight || 'auto', |
|
||||
}} |
|
||||
> |
|
||||
<div className={contentHeight ? styles.contentFixed : undefined}> |
|
||||
{children} |
|
||||
</div> |
|
||||
</div> |
|
||||
)} |
|
||||
{footer && ( |
|
||||
<div |
|
||||
className={classNames(styles.footer, { |
|
||||
[styles.footerMargin]: !children, |
|
||||
})} |
|
||||
> |
|
||||
{footer} |
|
||||
</div> |
|
||||
)} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
const { loading = false, ...rest } = props; |
|
||||
const cardProps = omit(rest, ['total', 'contentHeight', 'action']); |
|
||||
return ( |
|
||||
<Card |
|
||||
loading={loading} |
|
||||
styles={{ |
|
||||
body: { |
|
||||
padding: '20px 24px 8px 24px', |
|
||||
}, |
|
||||
}} |
|
||||
{...cardProps} |
|
||||
> |
|
||||
{renderContent()} |
|
||||
</Card> |
|
||||
); |
|
||||
}; |
|
||||
export default ChartCard; |
|
||||
@ -1,48 +0,0 @@ |
|||||
import { Tooltip } from 'antd'; |
|
||||
import React from 'react'; |
|
||||
export type MiniProgressProps = { |
|
||||
target: number; |
|
||||
targetLabel?: string; |
|
||||
color?: string; |
|
||||
size?: number; |
|
||||
percent?: number; |
|
||||
style?: React.CSSProperties; |
|
||||
}; |
|
||||
const MiniProgress: React.FC<MiniProgressProps> = ({ |
|
||||
targetLabel, |
|
||||
target, |
|
||||
color = 'rgb(19, 194, 194)', |
|
||||
size, |
|
||||
percent, |
|
||||
}) => { |
|
||||
return ( |
|
||||
<div> |
|
||||
<Tooltip title={targetLabel}> |
|
||||
<div |
|
||||
style={{ |
|
||||
left: target ? `${target}%` : undefined, |
|
||||
}} |
|
||||
> |
|
||||
<span |
|
||||
style={{ |
|
||||
backgroundColor: color || undefined, |
|
||||
}} |
|
||||
/> |
|
||||
<span |
|
||||
style={{ |
|
||||
backgroundColor: color || undefined, |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
</Tooltip> |
|
||||
<div |
|
||||
style={{ |
|
||||
backgroundColor: color || undefined, |
|
||||
width: percent ? `${percent}%` : undefined, |
|
||||
height: size || undefined, |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export default MiniProgress; |
|
||||
@ -1,168 +0,0 @@ |
|||||
import { InfoCircleOutlined } from '@ant-design/icons'; |
|
||||
import { Area, Column } from '@ant-design/plots'; |
|
||||
import { Col, Progress, Row, Tooltip } from 'antd'; |
|
||||
import numeral from 'numeral'; |
|
||||
import type { DataItem } from '../data.d'; |
|
||||
import useStyles from '../style.style'; |
|
||||
import Yuan from '../utils/Yuan'; |
|
||||
import { ChartCard, Field } from './Charts'; |
|
||||
import Trend from './Trend'; |
|
||||
|
|
||||
const topColResponsiveProps = { |
|
||||
xs: 24, |
|
||||
sm: 12, |
|
||||
md: 12, |
|
||||
lg: 12, |
|
||||
xl: 6, |
|
||||
style: { |
|
||||
marginBottom: 24, |
|
||||
}, |
|
||||
}; |
|
||||
const IntroduceRow = ({ |
|
||||
loading, |
|
||||
visitData, |
|
||||
}: { |
|
||||
loading: boolean; |
|
||||
visitData: DataItem[]; |
|
||||
}) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<Row gutter={24}> |
|
||||
<Col {...topColResponsiveProps}> |
|
||||
<ChartCard |
|
||||
variant="borderless" |
|
||||
title="总销售额" |
|
||||
action={ |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined /> |
|
||||
</Tooltip> |
|
||||
} |
|
||||
loading={loading} |
|
||||
total={() => <Yuan>126560</Yuan>} |
|
||||
footer={ |
|
||||
<Field |
|
||||
label="日销售额" |
|
||||
value={`¥${numeral(12423).format('0,0')}`} |
|
||||
/> |
|
||||
} |
|
||||
contentHeight={46} |
|
||||
> |
|
||||
<Trend |
|
||||
flag="up" |
|
||||
style={{ |
|
||||
marginRight: 16, |
|
||||
}} |
|
||||
> |
|
||||
周同比 |
|
||||
<span className={styles.trendText}>12%</span> |
|
||||
</Trend> |
|
||||
<Trend flag="down"> |
|
||||
日同比 |
|
||||
<span className={styles.trendText}>11%</span> |
|
||||
</Trend> |
|
||||
</ChartCard> |
|
||||
</Col> |
|
||||
|
|
||||
<Col {...topColResponsiveProps}> |
|
||||
<ChartCard |
|
||||
variant="borderless" |
|
||||
loading={loading} |
|
||||
title="访问量" |
|
||||
action={ |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined /> |
|
||||
</Tooltip> |
|
||||
} |
|
||||
total={numeral(8846).format('0,0')} |
|
||||
footer={ |
|
||||
<Field label="日访问量" value={numeral(1234).format('0,0')} /> |
|
||||
} |
|
||||
contentHeight={46} |
|
||||
> |
|
||||
<Area |
|
||||
xField="x" |
|
||||
yField="y" |
|
||||
shapeField="smooth" |
|
||||
height={46} |
|
||||
axis={false} |
|
||||
style={{ |
|
||||
fill: 'linear-gradient(-90deg, white 0%, #975FE4 100%)', |
|
||||
fillOpacity: 0.6, |
|
||||
width: '100%', |
|
||||
}} |
|
||||
padding={-20} |
|
||||
data={visitData} |
|
||||
/> |
|
||||
</ChartCard> |
|
||||
</Col> |
|
||||
<Col {...topColResponsiveProps}> |
|
||||
<ChartCard |
|
||||
variant="borderless" |
|
||||
loading={loading} |
|
||||
title="支付笔数" |
|
||||
action={ |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined /> |
|
||||
</Tooltip> |
|
||||
} |
|
||||
total={numeral(6560).format('0,0')} |
|
||||
footer={<Field label="转化率" value="60%" />} |
|
||||
contentHeight={46} |
|
||||
> |
|
||||
<Column |
|
||||
xField="x" |
|
||||
yField="y" |
|
||||
padding={-20} |
|
||||
axis={false} |
|
||||
height={46} |
|
||||
data={visitData} |
|
||||
scale={{ x: { paddingInner: 0.4 } }} |
|
||||
/> |
|
||||
</ChartCard> |
|
||||
</Col> |
|
||||
<Col {...topColResponsiveProps}> |
|
||||
<ChartCard |
|
||||
loading={loading} |
|
||||
variant="borderless" |
|
||||
title="运营活动效果" |
|
||||
action={ |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined /> |
|
||||
</Tooltip> |
|
||||
} |
|
||||
total="78%" |
|
||||
footer={ |
|
||||
<div |
|
||||
style={{ |
|
||||
whiteSpace: 'nowrap', |
|
||||
overflow: 'hidden', |
|
||||
}} |
|
||||
> |
|
||||
<Trend |
|
||||
flag="up" |
|
||||
style={{ |
|
||||
marginRight: 16, |
|
||||
}} |
|
||||
> |
|
||||
周同比 |
|
||||
<span className={styles.trendText}>12%</span> |
|
||||
</Trend> |
|
||||
<Trend flag="down"> |
|
||||
日同比 |
|
||||
<span className={styles.trendText}>11%</span> |
|
||||
</Trend> |
|
||||
</div> |
|
||||
} |
|
||||
contentHeight={46} |
|
||||
> |
|
||||
<Progress |
|
||||
percent={78} |
|
||||
strokeColor={{ from: '#108ee9', to: '#87d068' }} |
|
||||
status="active" |
|
||||
/> |
|
||||
</ChartCard> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
); |
|
||||
}; |
|
||||
export default IntroduceRow; |
|
||||
@ -1,110 +0,0 @@ |
|||||
import { Line, Tiny } from '@ant-design/plots'; |
|
||||
import { Card, Col, Row, Tabs } from 'antd'; |
|
||||
import type { DataItem, OfflineDataType } from '../data.d'; |
|
||||
import useStyles from '../style.style'; |
|
||||
import NumberInfo from './NumberInfo'; |
|
||||
|
|
||||
const CustomTab = ({ |
|
||||
data, |
|
||||
currentTabKey: currentKey, |
|
||||
}: { |
|
||||
data: OfflineDataType; |
|
||||
currentTabKey: string; |
|
||||
}) => ( |
|
||||
<Row |
|
||||
gutter={8} |
|
||||
style={{ |
|
||||
width: 138, |
|
||||
margin: '8px 0', |
|
||||
}} |
|
||||
> |
|
||||
<Col span={12}> |
|
||||
<NumberInfo |
|
||||
title={data.name} |
|
||||
subTitle="转化率" |
|
||||
gap={2} |
|
||||
total={`${data.cvr * 100}%`} |
|
||||
theme={currentKey !== data.name ? 'light' : undefined} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
span={12} |
|
||||
style={{ |
|
||||
paddingTop: 36, |
|
||||
}} |
|
||||
> |
|
||||
<Tiny.Ring |
|
||||
height={60} |
|
||||
width={60} |
|
||||
percent={data.cvr} |
|
||||
color={['#E8EEF4', '#5FABF4']} |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
); |
|
||||
|
|
||||
const OfflineData = ({ |
|
||||
activeKey, |
|
||||
loading, |
|
||||
offlineData, |
|
||||
offlineChartData, |
|
||||
handleTabChange, |
|
||||
}: { |
|
||||
activeKey: string; |
|
||||
loading: boolean; |
|
||||
offlineData: OfflineDataType[]; |
|
||||
offlineChartData: DataItem[]; |
|
||||
handleTabChange: (activeKey: string) => void; |
|
||||
}) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<Card |
|
||||
loading={loading} |
|
||||
className={styles.offlineCard} |
|
||||
variant="borderless" |
|
||||
style={{ |
|
||||
marginTop: 32, |
|
||||
}} |
|
||||
> |
|
||||
<Tabs |
|
||||
activeKey={activeKey} |
|
||||
onChange={handleTabChange} |
|
||||
items={offlineData.map((shop) => ({ |
|
||||
key: shop.name, |
|
||||
label: <CustomTab data={shop} currentTabKey={activeKey} />, |
|
||||
children: ( |
|
||||
<div |
|
||||
style={{ |
|
||||
padding: '0 24px', |
|
||||
}} |
|
||||
> |
|
||||
<Line |
|
||||
height={400} |
|
||||
data={offlineChartData} |
|
||||
xField="date" |
|
||||
yField="value" |
|
||||
colorField="type" |
|
||||
slider={{ x: true }} |
|
||||
axis={{ |
|
||||
x: { title: false }, |
|
||||
y: { |
|
||||
title: false, |
|
||||
gridLineDash: null, |
|
||||
gridStroke: '#ccc', |
|
||||
gridStrokeOpacity: 1, |
|
||||
}, |
|
||||
}} |
|
||||
legend={{ |
|
||||
color: { |
|
||||
layout: { justifyContent: 'center' }, |
|
||||
}, |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
), |
|
||||
}))} |
|
||||
/> |
|
||||
</Card> |
|
||||
); |
|
||||
}; |
|
||||
export default OfflineData; |
|
||||
@ -1,181 +0,0 @@ |
|||||
import { InfoCircleOutlined } from '@ant-design/icons'; |
|
||||
import { Area } from '@ant-design/plots'; |
|
||||
import { Card, Col, Row, Table, Tooltip } from 'antd'; |
|
||||
import numeral from 'numeral'; |
|
||||
import React from 'react'; |
|
||||
import type { DataItem } from '../data.d'; |
|
||||
import NumberInfo from './NumberInfo'; |
|
||||
import Trend from './Trend'; |
|
||||
|
|
||||
const TopSearch = ({ |
|
||||
loading, |
|
||||
visitData2, |
|
||||
searchData, |
|
||||
dropdownGroup, |
|
||||
}: { |
|
||||
loading: boolean; |
|
||||
visitData2: DataItem[]; |
|
||||
dropdownGroup: React.ReactNode; |
|
||||
searchData: DataItem[]; |
|
||||
}) => { |
|
||||
const columns = [ |
|
||||
{ |
|
||||
title: '排名', |
|
||||
dataIndex: 'index', |
|
||||
key: 'index', |
|
||||
}, |
|
||||
{ |
|
||||
title: '搜索关键词', |
|
||||
dataIndex: 'keyword', |
|
||||
key: 'keyword', |
|
||||
render: (text: React.ReactNode) => <a href="/">{text}</a>, |
|
||||
}, |
|
||||
{ |
|
||||
title: '用户数', |
|
||||
dataIndex: 'count', |
|
||||
key: 'count', |
|
||||
sorter: ( |
|
||||
a: { |
|
||||
count: number; |
|
||||
}, |
|
||||
b: { |
|
||||
count: number; |
|
||||
}, |
|
||||
) => a.count - b.count, |
|
||||
}, |
|
||||
{ |
|
||||
title: '周涨幅', |
|
||||
dataIndex: 'range', |
|
||||
key: 'range', |
|
||||
sorter: ( |
|
||||
a: { |
|
||||
range: number; |
|
||||
}, |
|
||||
b: { |
|
||||
range: number; |
|
||||
}, |
|
||||
) => a.range - b.range, |
|
||||
render: ( |
|
||||
text: React.ReactNode, |
|
||||
record: { |
|
||||
status: number; |
|
||||
}, |
|
||||
) => ( |
|
||||
<Trend flag={record.status === 1 ? 'down' : 'up'}> |
|
||||
<span |
|
||||
style={{ |
|
||||
marginRight: 4, |
|
||||
}} |
|
||||
> |
|
||||
{text}% |
|
||||
</span> |
|
||||
</Trend> |
|
||||
), |
|
||||
}, |
|
||||
]; |
|
||||
return ( |
|
||||
<Card |
|
||||
loading={loading} |
|
||||
variant="borderless" |
|
||||
title="线上热门搜索" |
|
||||
extra={dropdownGroup} |
|
||||
style={{ |
|
||||
height: '100%', |
|
||||
}} |
|
||||
> |
|
||||
<Row gutter={68}> |
|
||||
<Col |
|
||||
sm={12} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<NumberInfo |
|
||||
subTitle={ |
|
||||
<span> |
|
||||
搜索用户数 |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined |
|
||||
style={{ |
|
||||
marginLeft: 8, |
|
||||
}} |
|
||||
/> |
|
||||
</Tooltip> |
|
||||
</span> |
|
||||
} |
|
||||
gap={8} |
|
||||
total={numeral(12321).format('0,0')} |
|
||||
status="up" |
|
||||
subTotal={17.1} |
|
||||
/> |
|
||||
<Area |
|
||||
xField="x" |
|
||||
yField="y" |
|
||||
shapeField="smooth" |
|
||||
height={45} |
|
||||
axis={false} |
|
||||
padding={-12} |
|
||||
style={{ |
|
||||
fill: 'linear-gradient(-90deg, white 0%, #6294FA 100%)', |
|
||||
fillOpacity: 0.4, |
|
||||
}} |
|
||||
data={visitData2} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
sm={12} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<NumberInfo |
|
||||
subTitle={ |
|
||||
<span> |
|
||||
人均搜索次数 |
|
||||
<Tooltip title="指标说明"> |
|
||||
<InfoCircleOutlined |
|
||||
style={{ |
|
||||
marginLeft: 8, |
|
||||
}} |
|
||||
/> |
|
||||
</Tooltip> |
|
||||
</span> |
|
||||
} |
|
||||
total={2.7} |
|
||||
status="down" |
|
||||
subTotal={26.2} |
|
||||
gap={8} |
|
||||
/> |
|
||||
<Area |
|
||||
xField="x" |
|
||||
yField="y" |
|
||||
shapeField="smooth" |
|
||||
height={45} |
|
||||
padding={-12} |
|
||||
style={{ |
|
||||
fill: 'linear-gradient(-90deg, white 0%, #6294FA 100%)', |
|
||||
fillOpacity: 0.4, |
|
||||
}} |
|
||||
data={visitData2} |
|
||||
axis={false} |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<Table<any> |
|
||||
rowKey={(record) => record.index} |
|
||||
size="small" |
|
||||
columns={columns} |
|
||||
dataSource={searchData} |
|
||||
pagination={{ |
|
||||
style: { |
|
||||
marginBottom: 0, |
|
||||
}, |
|
||||
pageSize: 5, |
|
||||
}} |
|
||||
/> |
|
||||
</Card> |
|
||||
); |
|
||||
}; |
|
||||
export default TopSearch; |
|
||||
@ -1,203 +0,0 @@ |
|||||
import { Gauge, Liquid, WordCloud } from '@ant-design/plots'; |
|
||||
import { GridContent } from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Card, Col, Progress, Row, Statistic } from 'antd'; |
|
||||
import numeral from 'numeral'; |
|
||||
import type { FC } from 'react'; |
|
||||
import ActiveChart from './components/ActiveChart'; |
|
||||
import MonitorMap from './components/Map'; |
|
||||
import { queryTags } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const deadline = Date.now() + 1000 * 60 * 60 * 24 * 2 + 1000 * 30; // Moment is also OK
|
|
||||
|
|
||||
const Monitor: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { loading, data } = useRequest(queryTags); |
|
||||
const wordCloudData = (data?.list || []).map((item) => { |
|
||||
return { |
|
||||
id: +Date.now(), |
|
||||
word: item.name, |
|
||||
weight: item.value, |
|
||||
}; |
|
||||
}); |
|
||||
return ( |
|
||||
<GridContent> |
|
||||
<Row gutter={24}> |
|
||||
<Col |
|
||||
xl={18} |
|
||||
lg={24} |
|
||||
md={24} |
|
||||
sm={24} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<Card title="活动实时交易情况" variant="borderless"> |
|
||||
<Row> |
|
||||
<Col md={6} sm={12} xs={24}> |
|
||||
<Statistic |
|
||||
title="今日交易总额" |
|
||||
suffix="元" |
|
||||
value={numeral(124543233).format('0,0')} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col md={6} sm={12} xs={24}> |
|
||||
<Statistic title="销售目标完成率" value="92%" /> |
|
||||
</Col> |
|
||||
<Col md={6} sm={12} xs={24}> |
|
||||
<Statistic.Timer |
|
||||
type="countdown" |
|
||||
title="活动剩余时间" |
|
||||
value={deadline} |
|
||||
format="HH:mm:ss:SSS" |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col md={6} sm={12} xs={24}> |
|
||||
<Statistic |
|
||||
title="每秒交易总额" |
|
||||
suffix="元" |
|
||||
value={numeral(234).format('0,0')} |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<div className={styles.mapChart}> |
|
||||
<MonitorMap /> |
|
||||
</div> |
|
||||
</Card> |
|
||||
</Col> |
|
||||
<Col xl={6} lg={24} md={24} sm={24} xs={24}> |
|
||||
<Card |
|
||||
title="活动情况预测" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
variant="borderless" |
|
||||
> |
|
||||
<ActiveChart /> |
|
||||
</Card> |
|
||||
<Card |
|
||||
title="券核效率" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
styles={{ |
|
||||
body: { |
|
||||
textAlign: 'center', |
|
||||
}, |
|
||||
}} |
|
||||
variant="borderless" |
|
||||
> |
|
||||
<Gauge |
|
||||
height={180} |
|
||||
data={ |
|
||||
{ |
|
||||
target: 80, |
|
||||
total: 100, |
|
||||
name: 'score', |
|
||||
thresholds: [20, 40, 60, 80, 100], |
|
||||
} as any |
|
||||
} |
|
||||
padding={-16} |
|
||||
style={{ |
|
||||
textContent: () => '优', |
|
||||
}} |
|
||||
meta={{ |
|
||||
color: { |
|
||||
range: [ |
|
||||
'#6395FA', |
|
||||
'#62DAAB', |
|
||||
'#657798', |
|
||||
'#F7C128', |
|
||||
'#1F8718', |
|
||||
], |
|
||||
}, |
|
||||
}} |
|
||||
/> |
|
||||
</Card> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<Row gutter={24}> |
|
||||
<Col |
|
||||
xl={12} |
|
||||
lg={24} |
|
||||
sm={24} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<Card title="各品类占比" variant="borderless"> |
|
||||
<Row |
|
||||
style={{ |
|
||||
padding: '16px 0', |
|
||||
}} |
|
||||
> |
|
||||
<Col span={8}> |
|
||||
<Progress type="dashboard" percent={75} /> |
|
||||
</Col> |
|
||||
<Col span={8}> |
|
||||
<Progress type="dashboard" percent={48} /> |
|
||||
</Col> |
|
||||
<Col span={8}> |
|
||||
<Progress type="dashboard" percent={33} /> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</Card> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={6} |
|
||||
lg={12} |
|
||||
sm={24} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<Card |
|
||||
title="热门搜索" |
|
||||
loading={loading} |
|
||||
variant="borderless" |
|
||||
styles={{ |
|
||||
body: { |
|
||||
overflow: 'hidden', |
|
||||
}, |
|
||||
}} |
|
||||
> |
|
||||
<WordCloud |
|
||||
data={wordCloudData} |
|
||||
height={162} |
|
||||
textField="word" |
|
||||
colorField="word" |
|
||||
layout={{ spiral: 'rectangular', fontSize: [10, 20] }} |
|
||||
/> |
|
||||
</Card> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={6} |
|
||||
lg={12} |
|
||||
sm={24} |
|
||||
xs={24} |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<Card |
|
||||
title="资源剩余" |
|
||||
styles={{ |
|
||||
body: { |
|
||||
textAlign: 'center', |
|
||||
fontSize: 0, |
|
||||
}, |
|
||||
}} |
|
||||
variant="borderless" |
|
||||
> |
|
||||
<Liquid height={160} percent={0.35} /> |
|
||||
</Card> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</GridContent> |
|
||||
); |
|
||||
}; |
|
||||
export default Monitor; |
|
||||
@ -1,549 +0,0 @@ |
|||||
import { CloseCircleOutlined } from '@ant-design/icons'; |
|
||||
import type { ProColumnType } from '@ant-design/pro-components'; |
|
||||
import { |
|
||||
EditableProTable, |
|
||||
FooterToolbar, |
|
||||
PageContainer, |
|
||||
ProForm, |
|
||||
ProFormDateRangePicker, |
|
||||
ProFormSelect, |
|
||||
ProFormText, |
|
||||
ProFormTimePicker, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import { Card, Col, message, Popover, Row } from 'antd'; |
|
||||
import type { FC } from 'react'; |
|
||||
import { useState } from 'react'; |
|
||||
import { fakeSubmitForm } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
interface TableFormDateType { |
|
||||
key: string; |
|
||||
workId?: string; |
|
||||
name?: string; |
|
||||
department?: string; |
|
||||
isNew?: boolean; |
|
||||
editable?: boolean; |
|
||||
} |
|
||||
type InternalNamePath = (string | number)[]; |
|
||||
const fieldLabels = { |
|
||||
name: '仓库名', |
|
||||
url: '仓库域名', |
|
||||
owner: '仓库管理员', |
|
||||
approver: '审批人', |
|
||||
dateRange: '生效日期', |
|
||||
type: '仓库类型', |
|
||||
name2: '任务名', |
|
||||
url2: '任务描述', |
|
||||
owner2: '执行人', |
|
||||
approver2: '责任人', |
|
||||
dateRange2: '生效日期', |
|
||||
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', |
|
||||
}, |
|
||||
]; |
|
||||
interface ErrorField { |
|
||||
name: InternalNamePath; |
|
||||
errors: string[]; |
|
||||
} |
|
||||
const AdvancedForm: FC<Record<string, any>> = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const [error, setError] = useState<ErrorField[]>([]); |
|
||||
const getErrorInfo = (errors: ErrorField[]) => { |
|
||||
const errorCount = errors.filter((item) => item.errors.length > 0).length; |
|
||||
if (!errors || errorCount === 0) { |
|
||||
return null; |
|
||||
} |
|
||||
const scrollToField = (fieldKey: string) => { |
|
||||
const labelNode = document.querySelector(`label[for="${fieldKey}"]`); |
|
||||
if (labelNode) { |
|
||||
labelNode.scrollIntoView(true); |
|
||||
} |
|
||||
}; |
|
||||
const errorList = errors.map((err) => { |
|
||||
if (!err || err.errors.length === 0) { |
|
||||
return null; |
|
||||
} |
|
||||
const key = err.name[0] as |
|
||||
| 'name' |
|
||||
| 'url' |
|
||||
| 'owner' |
|
||||
| 'approver' |
|
||||
| 'dateRange' |
|
||||
| 'type'; |
|
||||
return ( |
|
||||
<li |
|
||||
key={key} |
|
||||
className={styles.errorListItem} |
|
||||
onClick={() => scrollToField(key)} |
|
||||
> |
|
||||
<CloseCircleOutlined className={styles.errorIcon} /> |
|
||||
<div>{err.errors[0]}</div> |
|
||||
<div className={styles.errorField}>{fieldLabels[key]}</div> |
|
||||
</li> |
|
||||
); |
|
||||
}); |
|
||||
return ( |
|
||||
<span className={styles.errorIcon}> |
|
||||
<Popover |
|
||||
title="表单校验信息" |
|
||||
content={errorList} |
|
||||
overlayClassName={styles.errorPopover} |
|
||||
trigger="click" |
|
||||
getPopupContainer={(trigger: HTMLElement) => { |
|
||||
if (trigger?.parentNode) { |
|
||||
return trigger.parentNode as HTMLElement; |
|
||||
} |
|
||||
return trigger; |
|
||||
}} |
|
||||
> |
|
||||
<CloseCircleOutlined /> |
|
||||
</Popover> |
|
||||
{errorCount} |
|
||||
</span> |
|
||||
); |
|
||||
}; |
|
||||
const onFinish = async (values: Record<string, any>) => { |
|
||||
setError([]); |
|
||||
try { |
|
||||
await fakeSubmitForm(values); |
|
||||
message.success('提交成功'); |
|
||||
} catch { |
|
||||
// console.log
|
|
||||
} |
|
||||
}; |
|
||||
const onFinishFailed = (errorInfo: any) => { |
|
||||
setError(errorInfo.errorFields); |
|
||||
}; |
|
||||
const columns: ProColumnType<TableFormDateType>[] = [ |
|
||||
{ |
|
||||
title: '成员姓名', |
|
||||
dataIndex: 'name', |
|
||||
key: 'name', |
|
||||
width: '20%', |
|
||||
}, |
|
||||
{ |
|
||||
title: '工号', |
|
||||
dataIndex: 'workId', |
|
||||
key: 'workId', |
|
||||
width: '20%', |
|
||||
}, |
|
||||
{ |
|
||||
title: '所属部门', |
|
||||
dataIndex: 'department', |
|
||||
key: 'department', |
|
||||
width: '40%', |
|
||||
}, |
|
||||
{ |
|
||||
title: '操作', |
|
||||
key: 'action', |
|
||||
valueType: 'option', |
|
||||
render: (_, record: TableFormDateType, _index, action) => { |
|
||||
return [ |
|
||||
<a |
|
||||
key="eidit" |
|
||||
onClick={() => { |
|
||||
action?.startEditable(record.key); |
|
||||
}} |
|
||||
> |
|
||||
编辑 |
|
||||
</a>, |
|
||||
]; |
|
||||
}, |
|
||||
}, |
|
||||
]; |
|
||||
return ( |
|
||||
<ProForm |
|
||||
layout="vertical" |
|
||||
hideRequiredMark |
|
||||
submitter={{ |
|
||||
render: (_props, dom) => { |
|
||||
return ( |
|
||||
<FooterToolbar> |
|
||||
{getErrorInfo(error)} |
|
||||
{dom} |
|
||||
</FooterToolbar> |
|
||||
); |
|
||||
}, |
|
||||
}} |
|
||||
initialValues={{ |
|
||||
members: tableData, |
|
||||
}} |
|
||||
onFinish={onFinish} |
|
||||
onFinishFailed={onFinishFailed} |
|
||||
> |
|
||||
<PageContainer content="高级表单常见于一次性输入和提交大批量数据的场景。"> |
|
||||
<Card title="仓库管理" className={styles.card} variant="borderless"> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={6} md={12} sm={24}> |
|
||||
<ProFormText |
|
||||
label={fieldLabels.name} |
|
||||
name="name" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入仓库名称', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入仓库名称" |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 6, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 8, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 12, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormText |
|
||||
label={fieldLabels.url} |
|
||||
name="url" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择', |
|
||||
}, |
|
||||
]} |
|
||||
fieldProps={{ |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
addonBefore: 'http://', |
|
||||
addonAfter: '.com', |
|
||||
}} |
|
||||
placeholder="请输入" |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 8, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 10, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 24, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.owner} |
|
||||
name="owner" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择管理员', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '付晓晓', |
|
||||
value: 'xiao', |
|
||||
}, |
|
||||
{ |
|
||||
label: '周毛毛', |
|
||||
value: 'mao', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择管理员" |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={6} md={12} sm={24}> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.approver} |
|
||||
name="approver" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择审批员', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '付晓晓', |
|
||||
value: 'xiao', |
|
||||
}, |
|
||||
{ |
|
||||
label: '周毛毛', |
|
||||
value: 'mao', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择审批员" |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 6, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 8, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 12, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormDateRangePicker |
|
||||
label={fieldLabels.dateRange} |
|
||||
name="dateRange" |
|
||||
fieldProps={{ |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
}} |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择生效日期', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 8, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 10, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 24, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.type} |
|
||||
name="type" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择仓库类型', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '私密', |
|
||||
value: 'private', |
|
||||
}, |
|
||||
{ |
|
||||
label: '公开', |
|
||||
value: 'public', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择仓库类型" |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</Card> |
|
||||
<Card title="任务管理" className={styles.card} variant="borderless"> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={6} md={12} sm={24}> |
|
||||
<ProFormText |
|
||||
label={fieldLabels.name2} |
|
||||
name="name2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 6, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 8, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 12, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormText |
|
||||
label={fieldLabels.url2} |
|
||||
name="url2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 8, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 10, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 24, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.owner2} |
|
||||
name="owner2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择管理员', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '付晓晓', |
|
||||
value: 'xiao', |
|
||||
}, |
|
||||
{ |
|
||||
label: '周毛毛', |
|
||||
value: 'mao', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={6} md={12} sm={24}> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.approver2} |
|
||||
name="approver2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择审批员', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '付晓晓', |
|
||||
value: 'xiao', |
|
||||
}, |
|
||||
{ |
|
||||
label: '周毛毛', |
|
||||
value: 'mao', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择审批员" |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 6, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 8, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 12, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormTimePicker |
|
||||
label={fieldLabels.dateRange2} |
|
||||
name="dateRange2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="提醒时间" |
|
||||
fieldProps={{ |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
}} |
|
||||
/> |
|
||||
</Col> |
|
||||
<Col |
|
||||
xl={{ |
|
||||
span: 8, |
|
||||
offset: 2, |
|
||||
}} |
|
||||
lg={{ |
|
||||
span: 10, |
|
||||
}} |
|
||||
md={{ |
|
||||
span: 24, |
|
||||
}} |
|
||||
sm={24} |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
label={fieldLabels.type2} |
|
||||
name="type2" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择仓库类型', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '私密', |
|
||||
value: 'private', |
|
||||
}, |
|
||||
{ |
|
||||
label: '公开', |
|
||||
value: 'public', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择仓库类型" |
|
||||
/> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</Card> |
|
||||
<Card title="成员管理" variant="borderless"> |
|
||||
<ProForm.Item name="members"> |
|
||||
<EditableProTable<TableFormDateType> |
|
||||
recordCreatorProps={{ |
|
||||
record: () => { |
|
||||
return { |
|
||||
key: `0${Date.now()}`, |
|
||||
}; |
|
||||
}, |
|
||||
}} |
|
||||
columns={columns} |
|
||||
rowKey="key" |
|
||||
/> |
|
||||
</ProForm.Item> |
|
||||
</Card> |
|
||||
</PageContainer> |
|
||||
</ProForm> |
|
||||
); |
|
||||
}; |
|
||||
export default AdvancedForm; |
|
||||
@ -1,194 +0,0 @@ |
|||||
import { |
|
||||
PageContainer, |
|
||||
ProForm, |
|
||||
ProFormDateRangePicker, |
|
||||
ProFormDependency, |
|
||||
ProFormDigit, |
|
||||
ProFormRadio, |
|
||||
ProFormSelect, |
|
||||
ProFormText, |
|
||||
ProFormTextArea, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Card, message } from 'antd'; |
|
||||
import type { FC } from 'react'; |
|
||||
import { fakeSubmitForm } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const BasicForm: FC<Record<string, any>> = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { run } = useRequest(fakeSubmitForm, { |
|
||||
manual: true, |
|
||||
onSuccess: () => { |
|
||||
message.success('提交成功'); |
|
||||
}, |
|
||||
}); |
|
||||
const onFinish = async (values: Record<string, any>) => { |
|
||||
run(values); |
|
||||
}; |
|
||||
return ( |
|
||||
<PageContainer content="表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。"> |
|
||||
<Card variant="borderless"> |
|
||||
<ProForm |
|
||||
hideRequiredMark |
|
||||
style={{ |
|
||||
margin: 'auto', |
|
||||
marginTop: 8, |
|
||||
maxWidth: 600, |
|
||||
}} |
|
||||
name="basic" |
|
||||
layout="vertical" |
|
||||
initialValues={{ |
|
||||
public: '1', |
|
||||
}} |
|
||||
onFinish={onFinish} |
|
||||
> |
|
||||
<ProFormText |
|
||||
width="md" |
|
||||
label="标题" |
|
||||
name="title" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入标题', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="给目标起个名字" |
|
||||
/> |
|
||||
<ProFormDateRangePicker |
|
||||
label="起止日期" |
|
||||
width="md" |
|
||||
name="date" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择起止日期', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder={['开始日期', '结束日期']} |
|
||||
/> |
|
||||
<ProFormTextArea |
|
||||
label="目标描述" |
|
||||
width="xl" |
|
||||
name="goal" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入目标描述', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入你的阶段性工作目标" |
|
||||
/> |
|
||||
|
|
||||
<ProFormTextArea |
|
||||
label="衡量标准" |
|
||||
name="standard" |
|
||||
width="xl" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入衡量标准', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入衡量标准" |
|
||||
/> |
|
||||
|
|
||||
<ProFormText |
|
||||
width="md" |
|
||||
label={ |
|
||||
<span> |
|
||||
客户 |
|
||||
<em className={styles.optional}>(选填)</em> |
|
||||
</span> |
|
||||
} |
|
||||
tooltip="目标的服务对象" |
|
||||
name="client" |
|
||||
placeholder="请描述你服务的客户,内部客户直接 @姓名/工号" |
|
||||
/> |
|
||||
|
|
||||
<ProFormText |
|
||||
width="md" |
|
||||
label={ |
|
||||
<span> |
|
||||
邀评人 |
|
||||
<em className={styles.optional}>(选填)</em> |
|
||||
</span> |
|
||||
} |
|
||||
name="invites" |
|
||||
placeholder="请直接 @姓名/工号,最多可邀请 5 人" |
|
||||
/> |
|
||||
|
|
||||
<ProFormDigit |
|
||||
label={ |
|
||||
<span> |
|
||||
权重 |
|
||||
<em className={styles.optional}>(选填)</em> |
|
||||
</span> |
|
||||
} |
|
||||
name="weight" |
|
||||
placeholder="请输入" |
|
||||
min={0} |
|
||||
max={100} |
|
||||
width="xs" |
|
||||
fieldProps={{ |
|
||||
formatter: (value) => `${value || 0}%`, |
|
||||
parser: (value) => Number(value ? value.replace('%', '') : '0'), |
|
||||
}} |
|
||||
/> |
|
||||
|
|
||||
<ProFormRadio.Group |
|
||||
options={[ |
|
||||
{ |
|
||||
value: '1', |
|
||||
label: '公开', |
|
||||
}, |
|
||||
{ |
|
||||
value: '2', |
|
||||
label: '部分公开', |
|
||||
}, |
|
||||
{ |
|
||||
value: '3', |
|
||||
label: '不公开', |
|
||||
}, |
|
||||
]} |
|
||||
label="目标公开" |
|
||||
help="客户、邀评人默认被分享" |
|
||||
name="publicType" |
|
||||
/> |
|
||||
<ProFormDependency name={['publicType']}> |
|
||||
{({ publicType }) => { |
|
||||
return ( |
|
||||
<ProFormSelect |
|
||||
width="md" |
|
||||
name="publicUsers" |
|
||||
fieldProps={{ |
|
||||
style: { |
|
||||
margin: '8px 0', |
|
||||
display: |
|
||||
publicType && publicType === '2' ? 'block' : 'none', |
|
||||
}, |
|
||||
}} |
|
||||
options={[ |
|
||||
{ |
|
||||
value: '1', |
|
||||
label: '同事甲', |
|
||||
}, |
|
||||
{ |
|
||||
value: '2', |
|
||||
label: '同事乙', |
|
||||
}, |
|
||||
{ |
|
||||
value: '3', |
|
||||
label: '同事丙', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
); |
|
||||
}} |
|
||||
</ProFormDependency> |
|
||||
</ProForm> |
|
||||
</Card> |
|
||||
</PageContainer> |
|
||||
); |
|
||||
}; |
|
||||
export default BasicForm; |
|
||||
@ -1,248 +0,0 @@ |
|||||
import { |
|
||||
PageContainer, |
|
||||
ProForm, |
|
||||
ProFormDigit, |
|
||||
ProFormSelect, |
|
||||
ProFormText, |
|
||||
StepsForm, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import type { FormInstance } from 'antd'; |
|
||||
import { |
|
||||
Alert, |
|
||||
Button, |
|
||||
Card, |
|
||||
Descriptions, |
|
||||
Divider, |
|
||||
Result, |
|
||||
Statistic, |
|
||||
} from 'antd'; |
|
||||
import React, { useRef, useState } from 'react'; |
|
||||
import type { StepDataType } from './data.d'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const StepDescriptions: React.FC<{ |
|
||||
stepData: StepDataType; |
|
||||
bordered?: boolean; |
|
||||
}> = ({ stepData, bordered }) => { |
|
||||
const { payAccount, receiverAccount, receiverName, amount } = stepData; |
|
||||
return ( |
|
||||
<Descriptions column={1} bordered={bordered}> |
|
||||
<Descriptions.Item label="付款账户"> {payAccount}</Descriptions.Item> |
|
||||
<Descriptions.Item label="收款账户"> {receiverAccount}</Descriptions.Item> |
|
||||
<Descriptions.Item label="收款人姓名"> {receiverName}</Descriptions.Item> |
|
||||
<Descriptions.Item label="转账金额"> |
|
||||
<Statistic |
|
||||
value={amount} |
|
||||
suffix={ |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
元 |
|
||||
</span> |
|
||||
} |
|
||||
precision={2} |
|
||||
/> |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
); |
|
||||
}; |
|
||||
const StepResult: React.FC<{ |
|
||||
onFinish: () => Promise<void>; |
|
||||
children?: React.ReactNode; |
|
||||
}> = (props) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<Result |
|
||||
status="success" |
|
||||
title="操作成功" |
|
||||
subTitle="预计两小时内到账" |
|
||||
extra={ |
|
||||
<> |
|
||||
<Button type="primary" onClick={props.onFinish}> |
|
||||
再转一笔 |
|
||||
</Button> |
|
||||
<Button>查看账单</Button> |
|
||||
</> |
|
||||
} |
|
||||
className={styles.result} |
|
||||
> |
|
||||
{props.children} |
|
||||
</Result> |
|
||||
); |
|
||||
}; |
|
||||
const StepForm: React.FC<Record<string, any>> = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const [stepData, setStepData] = useState<StepDataType>({ |
|
||||
payAccount: 'ant-design@alipay.com', |
|
||||
receiverAccount: 'test@example.com', |
|
||||
receiverName: 'Alex', |
|
||||
amount: '500', |
|
||||
receiverMode: 'alipay', |
|
||||
}); |
|
||||
const [current, setCurrent] = useState(0); |
|
||||
const formRef = useRef<FormInstance>(null); |
|
||||
return ( |
|
||||
<PageContainer content="将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。"> |
|
||||
<Card variant="borderless"> |
|
||||
<StepsForm |
|
||||
current={current} |
|
||||
onCurrentChange={setCurrent} |
|
||||
submitter={{ |
|
||||
render: (props, dom) => { |
|
||||
if (props.step === 2) { |
|
||||
return null; |
|
||||
} |
|
||||
return dom; |
|
||||
}, |
|
||||
}} |
|
||||
> |
|
||||
<StepsForm.StepForm<StepDataType> |
|
||||
formRef={formRef} |
|
||||
title="填写转账信息" |
|
||||
initialValues={stepData} |
|
||||
onFinish={async (values) => { |
|
||||
setStepData(values); |
|
||||
return true; |
|
||||
}} |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
label="付款账户" |
|
||||
width="md" |
|
||||
name="payAccount" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择付款账户', |
|
||||
}, |
|
||||
]} |
|
||||
valueEnum={{ |
|
||||
'ant-design@alipay.com': 'ant-design@alipay.com', |
|
||||
}} |
|
||||
/> |
|
||||
|
|
||||
<ProForm.Group title="收款账户" size={8}> |
|
||||
<ProFormSelect |
|
||||
name="receiverMode" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择付款账户', |
|
||||
}, |
|
||||
]} |
|
||||
valueEnum={{ |
|
||||
alipay: '支付宝', |
|
||||
bank: '银行账户', |
|
||||
}} |
|
||||
/> |
|
||||
<ProFormText |
|
||||
name="receiverAccount" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入收款人账户', |
|
||||
}, |
|
||||
{ |
|
||||
type: 'email', |
|
||||
message: '账户名应为邮箱格式', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="test@example.com" |
|
||||
/> |
|
||||
</ProForm.Group> |
|
||||
<ProFormText |
|
||||
label="收款人姓名" |
|
||||
width="md" |
|
||||
name="receiverName" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入收款人姓名', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入收款人姓名" |
|
||||
/> |
|
||||
<ProFormDigit |
|
||||
label="转账金额" |
|
||||
name="amount" |
|
||||
width="md" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入转账金额', |
|
||||
}, |
|
||||
{ |
|
||||
pattern: /^(\d+)((?:\.\d+)?)$/, |
|
||||
message: '请输入合法金额数字', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入金额" |
|
||||
fieldProps={{ |
|
||||
prefix: '¥', |
|
||||
}} |
|
||||
/> |
|
||||
</StepsForm.StepForm> |
|
||||
|
|
||||
<StepsForm.StepForm title="确认转账信息"> |
|
||||
<div className={styles.result}> |
|
||||
<Alert |
|
||||
closable |
|
||||
showIcon |
|
||||
message="确认转账后,资金将直接打入对方账户,无法退回。" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
/> |
|
||||
<StepDescriptions stepData={stepData} bordered /> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
margin: '24px 0', |
|
||||
}} |
|
||||
/> |
|
||||
<ProFormText.Password |
|
||||
label="支付密码" |
|
||||
width="md" |
|
||||
name="password" |
|
||||
required={false} |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '需要支付密码才能进行支付', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</div> |
|
||||
</StepsForm.StepForm> |
|
||||
<StepsForm.StepForm title="完成"> |
|
||||
<StepResult |
|
||||
onFinish={async () => { |
|
||||
setCurrent(0); |
|
||||
formRef.current?.resetFields(); |
|
||||
}} |
|
||||
> |
|
||||
<StepDescriptions stepData={stepData} /> |
|
||||
</StepResult> |
|
||||
</StepsForm.StepForm> |
|
||||
</StepsForm> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
margin: '40px 0 24px', |
|
||||
}} |
|
||||
/> |
|
||||
<div> |
|
||||
<h3>说明</h3> |
|
||||
<h4>转账到支付宝账户</h4> |
|
||||
<p> |
|
||||
如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 |
|
||||
</p> |
|
||||
<h4>转账到银行卡</h4> |
|
||||
<p> |
|
||||
如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。如果需要,这里可以放一些关于产品的常见问题说明。 |
|
||||
</p> |
|
||||
</div> |
|
||||
</Card> |
|
||||
</PageContainer> |
|
||||
); |
|
||||
}; |
|
||||
export default StepForm; |
|
||||
@ -1,129 +0,0 @@ |
|||||
import { |
|
||||
ModalForm, |
|
||||
ProFormDateTimePicker, |
|
||||
ProFormSelect, |
|
||||
ProFormText, |
|
||||
ProFormTextArea, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import { Button, Result } from 'antd'; |
|
||||
import type { FC } from 'react'; |
|
||||
import type { BasicListItemDataType } from '../data.d'; |
|
||||
import useStyles from '../style.style'; |
|
||||
|
|
||||
type OperationModalProps = { |
|
||||
done: boolean; |
|
||||
open: boolean; |
|
||||
current: Partial<BasicListItemDataType> | undefined; |
|
||||
onDone: () => void; |
|
||||
onSubmit: (values: BasicListItemDataType) => void; |
|
||||
children?: React.ReactNode; |
|
||||
}; |
|
||||
const OperationModal: FC<OperationModalProps> = (props) => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { done, open, current, onDone, onSubmit, children } = props; |
|
||||
if (!open) { |
|
||||
return null; |
|
||||
} |
|
||||
return ( |
|
||||
<ModalForm<BasicListItemDataType> |
|
||||
open={open} |
|
||||
title={done ? null : `任务${current ? '编辑' : '添加'}`} |
|
||||
className={styles.standardListForm} |
|
||||
width={640} |
|
||||
onFinish={async (values) => { |
|
||||
onSubmit(values); |
|
||||
}} |
|
||||
initialValues={current} |
|
||||
submitter={{ |
|
||||
render: (_, dom) => (done ? null : dom), |
|
||||
}} |
|
||||
trigger={children} |
|
||||
modalProps={{ |
|
||||
onCancel: () => onDone(), |
|
||||
destroyOnHidden: true, |
|
||||
bodyStyle: done |
|
||||
? { |
|
||||
padding: '72px 0', |
|
||||
} |
|
||||
: {}, |
|
||||
}} |
|
||||
> |
|
||||
{!done ? ( |
|
||||
<> |
|
||||
<ProFormText |
|
||||
name="title" |
|
||||
label="任务名称" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入任务名称', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入" |
|
||||
/> |
|
||||
<ProFormDateTimePicker |
|
||||
name="createdAt" |
|
||||
label="开始时间" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择开始时间', |
|
||||
}, |
|
||||
]} |
|
||||
fieldProps={{ |
|
||||
style: { |
|
||||
width: '100%', |
|
||||
}, |
|
||||
}} |
|
||||
placeholder="请选择" |
|
||||
/> |
|
||||
<ProFormSelect |
|
||||
name="owner" |
|
||||
label="任务负责人" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择任务负责人', |
|
||||
}, |
|
||||
]} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '付晓晓', |
|
||||
value: 'xiao', |
|
||||
}, |
|
||||
{ |
|
||||
label: '周毛毛', |
|
||||
value: 'mao', |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请选择管理员" |
|
||||
/> |
|
||||
<ProFormTextArea |
|
||||
name="subDescription" |
|
||||
label="产品描述" |
|
||||
rules={[ |
|
||||
{ |
|
||||
message: '请输入至少五个字符的产品描述!', |
|
||||
min: 5, |
|
||||
}, |
|
||||
]} |
|
||||
placeholder="请输入至少五个字符" |
|
||||
/> |
|
||||
</> |
|
||||
) : ( |
|
||||
<Result |
|
||||
status="success" |
|
||||
title="操作成功" |
|
||||
subTitle="一系列的信息描述,很短同样也可以带标点。" |
|
||||
extra={ |
|
||||
<Button type="primary" onClick={onDone}> |
|
||||
知道了 |
|
||||
</Button> |
|
||||
} |
|
||||
className={styles.formResult} |
|
||||
/> |
|
||||
)} |
|
||||
</ModalForm> |
|
||||
); |
|
||||
}; |
|
||||
export default OperationModal; |
|
||||
@ -1,280 +0,0 @@ |
|||||
import { DownOutlined, PlusOutlined } from '@ant-design/icons'; |
|
||||
import { PageContainer } from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { |
|
||||
Avatar, |
|
||||
Button, |
|
||||
Card, |
|
||||
Col, |
|
||||
Dropdown, |
|
||||
Input, |
|
||||
List, |
|
||||
Modal, |
|
||||
Progress, |
|
||||
Row, |
|
||||
Segmented, |
|
||||
} from 'antd'; |
|
||||
import dayjs from 'dayjs'; |
|
||||
import type { FC } from 'react'; |
|
||||
import React, { useState } from 'react'; |
|
||||
import OperationModal from './components/OperationModal'; |
|
||||
import type { BasicListItemDataType } from './data.d'; |
|
||||
import { |
|
||||
addFakeList, |
|
||||
queryFakeList, |
|
||||
removeFakeList, |
|
||||
updateFakeList, |
|
||||
} from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const { Search } = Input; |
|
||||
const Info: FC<{ |
|
||||
title: React.ReactNode; |
|
||||
value: React.ReactNode; |
|
||||
bordered?: boolean; |
|
||||
}> = ({ title, value, bordered }) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<div className={styles.headerInfo}> |
|
||||
<span>{title}</span> |
|
||||
<p>{value}</p> |
|
||||
{bordered && <em />} |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
const ListContent = ({ |
|
||||
data: { owner, createdAt, percent, status }, |
|
||||
}: { |
|
||||
data: BasicListItemDataType; |
|
||||
}) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<div> |
|
||||
<div className={styles.listContentItem}> |
|
||||
<span>Owner</span> |
|
||||
<p>{owner}</p> |
|
||||
</div> |
|
||||
<div className={styles.listContentItem}> |
|
||||
<span>开始时间</span> |
|
||||
<p>{dayjs(createdAt).format('YYYY-MM-DD HH:mm')}</p> |
|
||||
</div> |
|
||||
<div className={styles.listContentItem}> |
|
||||
<Progress |
|
||||
percent={percent} |
|
||||
status={status} |
|
||||
size={6} |
|
||||
style={{ |
|
||||
width: 180, |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export const BasicList: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const [done, setDone] = useState<boolean>(false); |
|
||||
const [open, setVisible] = useState<boolean>(false); |
|
||||
const [current, setCurrent] = useState< |
|
||||
Partial<BasicListItemDataType> | undefined |
|
||||
>(undefined); |
|
||||
const { |
|
||||
data: listData, |
|
||||
loading, |
|
||||
mutate, |
|
||||
} = useRequest(() => { |
|
||||
return queryFakeList({ |
|
||||
count: 50, |
|
||||
}); |
|
||||
}); |
|
||||
const { run: postRun } = useRequest( |
|
||||
(method, params) => { |
|
||||
if (method === 'remove') { |
|
||||
return removeFakeList(params); |
|
||||
} |
|
||||
if (method === 'update') { |
|
||||
return updateFakeList(params); |
|
||||
} |
|
||||
return addFakeList(params); |
|
||||
}, |
|
||||
{ |
|
||||
manual: true, |
|
||||
onSuccess: (result) => { |
|
||||
mutate(result); |
|
||||
}, |
|
||||
}, |
|
||||
); |
|
||||
const list = listData?.list || []; |
|
||||
const paginationProps = { |
|
||||
showSizeChanger: true, |
|
||||
showQuickJumper: true, |
|
||||
pageSize: 5, |
|
||||
total: list.length, |
|
||||
}; |
|
||||
const showEditModal = (item: BasicListItemDataType) => { |
|
||||
setVisible(true); |
|
||||
setCurrent(item); |
|
||||
}; |
|
||||
const deleteItem = (id: string) => { |
|
||||
postRun('remove', { |
|
||||
id, |
|
||||
}); |
|
||||
}; |
|
||||
const editAndDelete = ( |
|
||||
key: string | number, |
|
||||
currentItem: BasicListItemDataType, |
|
||||
) => { |
|
||||
if (key === 'edit') showEditModal(currentItem); |
|
||||
else if (key === 'delete') { |
|
||||
Modal.confirm({ |
|
||||
title: '删除任务', |
|
||||
content: '确定删除该任务吗?', |
|
||||
okText: '确认', |
|
||||
cancelText: '取消', |
|
||||
onOk: () => deleteItem(currentItem.id), |
|
||||
}); |
|
||||
} |
|
||||
}; |
|
||||
const extraContent = ( |
|
||||
<div> |
|
||||
<Segmented |
|
||||
defaultValue="all" |
|
||||
options={[ |
|
||||
{ label: '全部', value: 'all' }, |
|
||||
{ label: '进行中', value: 'progress' }, |
|
||||
{ label: '等待中', value: 'waiting' }, |
|
||||
]} |
|
||||
// 如有需要可添加 onChange 事件
|
|
||||
/> |
|
||||
<Search |
|
||||
className={styles.extraContentSearch} |
|
||||
placeholder="请输入" |
|
||||
onSearch={() => ({})} |
|
||||
variant="filled" |
|
||||
/> |
|
||||
</div> |
|
||||
); |
|
||||
const MoreBtn: React.FC<{ |
|
||||
item: BasicListItemDataType; |
|
||||
}> = ({ item }) => ( |
|
||||
<Dropdown |
|
||||
menu={{ |
|
||||
onClick: ({ key }) => editAndDelete(key, item), |
|
||||
items: [ |
|
||||
{ |
|
||||
key: 'edit', |
|
||||
label: '编辑', |
|
||||
}, |
|
||||
{ |
|
||||
key: 'delete', |
|
||||
label: '删除', |
|
||||
}, |
|
||||
], |
|
||||
}} |
|
||||
> |
|
||||
<a> |
|
||||
更多 <DownOutlined /> |
|
||||
</a> |
|
||||
</Dropdown> |
|
||||
); |
|
||||
const handleDone = () => { |
|
||||
setDone(false); |
|
||||
setVisible(false); |
|
||||
setCurrent({}); |
|
||||
}; |
|
||||
const handleSubmit = (values: BasicListItemDataType) => { |
|
||||
setDone(true); |
|
||||
const method = values?.id ? 'update' : 'add'; |
|
||||
postRun(method, values); |
|
||||
}; |
|
||||
return ( |
|
||||
<div> |
|
||||
<PageContainer> |
|
||||
<div className={styles.standardList}> |
|
||||
<Card variant="borderless"> |
|
||||
<Row> |
|
||||
<Col sm={8} xs={24}> |
|
||||
<Info title="我的待办" value="8个任务" bordered /> |
|
||||
</Col> |
|
||||
<Col sm={8} xs={24}> |
|
||||
<Info title="本周任务平均处理时间" value="32分钟" bordered /> |
|
||||
</Col> |
|
||||
<Col sm={8} xs={24}> |
|
||||
<Info title="本周完成任务数" value="24个任务" /> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</Card> |
|
||||
|
|
||||
<Card |
|
||||
className={styles.listCard} |
|
||||
variant="borderless" |
|
||||
title="基本列表" |
|
||||
style={{ |
|
||||
marginTop: 24, |
|
||||
}} |
|
||||
styles={{ |
|
||||
body: { |
|
||||
padding: '0 32px 40px 32px', |
|
||||
}, |
|
||||
}} |
|
||||
extra={extraContent} |
|
||||
> |
|
||||
<List |
|
||||
size="large" |
|
||||
rowKey="id" |
|
||||
loading={loading} |
|
||||
pagination={paginationProps} |
|
||||
dataSource={list} |
|
||||
renderItem={(item) => ( |
|
||||
<List.Item |
|
||||
actions={[ |
|
||||
<a |
|
||||
key="edit" |
|
||||
onClick={(e) => { |
|
||||
e.preventDefault(); |
|
||||
showEditModal(item); |
|
||||
}} |
|
||||
> |
|
||||
编辑 |
|
||||
</a>, |
|
||||
<MoreBtn key="more" item={item} />, |
|
||||
]} |
|
||||
> |
|
||||
<List.Item.Meta |
|
||||
avatar={ |
|
||||
<Avatar src={item.logo} shape="square" size="large" /> |
|
||||
} |
|
||||
title={<a href={item.href}>{item.title}</a>} |
|
||||
description={item.subDescription} |
|
||||
/> |
|
||||
<ListContent data={item} /> |
|
||||
</List.Item> |
|
||||
)} |
|
||||
/> |
|
||||
</Card> |
|
||||
</div> |
|
||||
</PageContainer> |
|
||||
<Button |
|
||||
type="dashed" |
|
||||
onClick={() => { |
|
||||
setVisible(true); |
|
||||
}} |
|
||||
style={{ |
|
||||
width: '100%', |
|
||||
marginBottom: 8, |
|
||||
}} |
|
||||
> |
|
||||
<PlusOutlined /> |
|
||||
添加 |
|
||||
</Button> |
|
||||
<OperationModal |
|
||||
done={done} |
|
||||
open={open} |
|
||||
current={current} |
|
||||
onDone={handleDone} |
|
||||
onSubmit={handleSubmit} |
|
||||
/> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export default BasicList; |
|
||||
@ -1,239 +0,0 @@ |
|||||
import { |
|
||||
DownloadOutlined, |
|
||||
EditOutlined, |
|
||||
EllipsisOutlined, |
|
||||
ShareAltOutlined, |
|
||||
} from '@ant-design/icons'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { |
|
||||
Avatar, |
|
||||
Card, |
|
||||
Col, |
|
||||
Dropdown, |
|
||||
Form, |
|
||||
List, |
|
||||
Row, |
|
||||
Select, |
|
||||
Tooltip, |
|
||||
} from 'antd'; |
|
||||
import numeral from 'numeral'; |
|
||||
import type { FC } from 'react'; |
|
||||
import React from 'react'; |
|
||||
import { categoryOptions } from '../../mock'; |
|
||||
import StandardFormRow from './components/StandardFormRow'; |
|
||||
import TagSelect from './components/TagSelect'; |
|
||||
import type { ListItemDataType } from './data.d'; |
|
||||
import { queryFakeList } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
export function formatWan(val: number) { |
|
||||
const v = val * 1; |
|
||||
if (!v || Number.isNaN(v)) return ''; |
|
||||
let result: React.ReactNode = val; |
|
||||
if (val > 10000) { |
|
||||
result = ( |
|
||||
<span> |
|
||||
{Math.floor(val / 10000)} |
|
||||
<span |
|
||||
style={{ |
|
||||
position: 'relative', |
|
||||
top: -2, |
|
||||
fontSize: 14, |
|
||||
fontStyle: 'normal', |
|
||||
marginLeft: 2, |
|
||||
}} |
|
||||
> |
|
||||
万 |
|
||||
</span> |
|
||||
</span> |
|
||||
); |
|
||||
} |
|
||||
return result; |
|
||||
} |
|
||||
const formItemLayout = { |
|
||||
wrapperCol: { |
|
||||
xs: { |
|
||||
span: 24, |
|
||||
}, |
|
||||
sm: { |
|
||||
span: 16, |
|
||||
}, |
|
||||
}, |
|
||||
}; |
|
||||
const CardInfo: React.FC<{ |
|
||||
activeUser: React.ReactNode; |
|
||||
newUser: React.ReactNode; |
|
||||
}> = ({ activeUser, newUser }) => { |
|
||||
const { styles } = useStyles(); |
|
||||
return ( |
|
||||
<div className={styles.cardInfo}> |
|
||||
<div> |
|
||||
<p>活跃用户</p> |
|
||||
<p>{activeUser}</p> |
|
||||
</div> |
|
||||
<div> |
|
||||
<p>新增用户</p> |
|
||||
<p>{newUser}</p> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export const Applications: FC<Record<string, any>> = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { data, loading, run } = useRequest((values: any) => { |
|
||||
console.log('form data', values); |
|
||||
return queryFakeList({ |
|
||||
count: 8, |
|
||||
}); |
|
||||
}); |
|
||||
|
|
||||
const list = data?.list || []; |
|
||||
|
|
||||
return ( |
|
||||
<div className={styles.filterCardList}> |
|
||||
<Card variant="borderless"> |
|
||||
<Form |
|
||||
onValuesChange={(_, values) => { |
|
||||
run(values); |
|
||||
}} |
|
||||
> |
|
||||
<StandardFormRow |
|
||||
title="所属类目" |
|
||||
block |
|
||||
style={{ |
|
||||
paddingBottom: 11, |
|
||||
}} |
|
||||
> |
|
||||
<Form.Item name="category"> |
|
||||
<TagSelect expandable> |
|
||||
{categoryOptions |
|
||||
.filter( |
|
||||
( |
|
||||
category, |
|
||||
): category is { value: string | number; label: string } => |
|
||||
category.value !== undefined && category.value !== null, |
|
||||
) |
|
||||
.map((category) => ( |
|
||||
<TagSelect.Option |
|
||||
value={category.value} |
|
||||
key={category.value} |
|
||||
> |
|
||||
{category.label} |
|
||||
</TagSelect.Option> |
|
||||
))} |
|
||||
</TagSelect> |
|
||||
</Form.Item> |
|
||||
</StandardFormRow> |
|
||||
<StandardFormRow title="其它选项" grid last> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={8} md={10} sm={10} xs={24}> |
|
||||
<Form.Item {...formItemLayout} name="author" label="作者"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ |
|
||||
maxWidth: 200, |
|
||||
width: '100%', |
|
||||
}} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '王昭君', |
|
||||
value: 'lisa', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Form.Item> |
|
||||
</Col> |
|
||||
<Col lg={8} md={10} sm={10} xs={24}> |
|
||||
<Form.Item {...formItemLayout} name="rate" label="好评度"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ |
|
||||
maxWidth: 200, |
|
||||
width: '100%', |
|
||||
}} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '优秀', |
|
||||
value: 'good', |
|
||||
}, |
|
||||
{ |
|
||||
label: '普通', |
|
||||
value: 'normal', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</Form.Item> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</StandardFormRow> |
|
||||
</Form> |
|
||||
</Card> |
|
||||
<br /> |
|
||||
<List<ListItemDataType> |
|
||||
rowKey="id" |
|
||||
grid={{ |
|
||||
gutter: 16, |
|
||||
xs: 1, |
|
||||
sm: 2, |
|
||||
md: 3, |
|
||||
lg: 3, |
|
||||
xl: 4, |
|
||||
xxl: 4, |
|
||||
}} |
|
||||
loading={loading} |
|
||||
dataSource={list} |
|
||||
renderItem={(item) => ( |
|
||||
<List.Item key={item.id}> |
|
||||
<Card |
|
||||
hoverable |
|
||||
styles={{ |
|
||||
body: { |
|
||||
paddingBottom: 20, |
|
||||
}, |
|
||||
}} |
|
||||
actions={[ |
|
||||
<Tooltip key="download" title="下载"> |
|
||||
<DownloadOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Tooltip key="edit" title="编辑"> |
|
||||
<EditOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Tooltip title="分享" key="share"> |
|
||||
<ShareAltOutlined /> |
|
||||
</Tooltip>, |
|
||||
<Dropdown |
|
||||
key="ellipsis" |
|
||||
menu={{ |
|
||||
items: [ |
|
||||
{ |
|
||||
key: '1', |
|
||||
title: '1st menu item', |
|
||||
}, |
|
||||
{ |
|
||||
key: '2', |
|
||||
title: '2st menu item', |
|
||||
}, |
|
||||
], |
|
||||
}} |
|
||||
> |
|
||||
<EllipsisOutlined /> |
|
||||
</Dropdown>, |
|
||||
]} |
|
||||
> |
|
||||
<Card.Meta |
|
||||
avatar={<Avatar size="small" src={item.avatar} />} |
|
||||
title={item.title} |
|
||||
/> |
|
||||
<div> |
|
||||
<CardInfo |
|
||||
activeUser={formatWan(item.activeUser)} |
|
||||
newUser={numeral(item.newUser).format('0,0')} |
|
||||
/> |
|
||||
</div> |
|
||||
</Card> |
|
||||
</List.Item> |
|
||||
)} |
|
||||
/> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export default Applications; |
|
||||
@ -1,261 +0,0 @@ |
|||||
import { |
|
||||
LikeOutlined, |
|
||||
LoadingOutlined, |
|
||||
MessageOutlined, |
|
||||
StarOutlined, |
|
||||
} from '@ant-design/icons'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Button, Card, Col, Form, List, Row, Select, Tag } from 'antd'; |
|
||||
import type { DefaultOptionType } from 'antd/es/select'; |
|
||||
import type { FC } from 'react'; |
|
||||
import React, { useMemo } from 'react'; |
|
||||
import { categoryOptions } from '../../mock'; |
|
||||
import ArticleListContent from './components/ArticleListContent'; |
|
||||
import StandardFormRow from './components/StandardFormRow'; |
|
||||
import TagSelect from './components/TagSelect'; |
|
||||
import type { ListItemDataType } from './data.d'; |
|
||||
import { queryFakeList } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const FormItem = Form.Item; |
|
||||
|
|
||||
const pageSize = 5; |
|
||||
|
|
||||
const Articles: FC = () => { |
|
||||
const [form] = Form.useForm(); |
|
||||
|
|
||||
const { styles } = useStyles(); |
|
||||
|
|
||||
const { data, reload, loading, loadMore, loadingMore } = useRequest( |
|
||||
() => { |
|
||||
return queryFakeList({ |
|
||||
count: pageSize, |
|
||||
}); |
|
||||
}, |
|
||||
{ |
|
||||
loadMore: true, |
|
||||
}, |
|
||||
); |
|
||||
|
|
||||
const list = data?.list || []; |
|
||||
|
|
||||
const setOwner = () => { |
|
||||
form.setFieldsValue({ |
|
||||
owner: ['wzj'], |
|
||||
}); |
|
||||
}; |
|
||||
|
|
||||
const owners = [ |
|
||||
{ |
|
||||
id: 'wzj', |
|
||||
name: '我自己', |
|
||||
}, |
|
||||
{ |
|
||||
id: 'wjh', |
|
||||
name: '吴家豪', |
|
||||
}, |
|
||||
{ |
|
||||
id: 'zxx', |
|
||||
name: '周星星', |
|
||||
}, |
|
||||
{ |
|
||||
id: 'zly', |
|
||||
name: '赵丽颖', |
|
||||
}, |
|
||||
{ |
|
||||
id: 'ym', |
|
||||
name: '姚明', |
|
||||
}, |
|
||||
]; |
|
||||
|
|
||||
const IconText: React.FC<{ |
|
||||
type: string; |
|
||||
text: React.ReactNode; |
|
||||
}> = ({ type, text }) => { |
|
||||
switch (type) { |
|
||||
case 'star-o': |
|
||||
return ( |
|
||||
<span> |
|
||||
<StarOutlined style={{ marginRight: 8 }} /> |
|
||||
{text} |
|
||||
</span> |
|
||||
); |
|
||||
case 'like-o': |
|
||||
return ( |
|
||||
<span> |
|
||||
<LikeOutlined style={{ marginRight: 8 }} /> |
|
||||
{text} |
|
||||
</span> |
|
||||
); |
|
||||
case 'message': |
|
||||
return ( |
|
||||
<span> |
|
||||
<MessageOutlined style={{ marginRight: 8 }} /> |
|
||||
{text} |
|
||||
</span> |
|
||||
); |
|
||||
default: |
|
||||
return null; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const formItemLayout = { |
|
||||
wrapperCol: { |
|
||||
xs: { span: 24 }, |
|
||||
sm: { span: 24 }, |
|
||||
md: { span: 12 }, |
|
||||
}, |
|
||||
}; |
|
||||
|
|
||||
const loadMoreDom = list.length > 0 && ( |
|
||||
<div style={{ textAlign: 'center', marginTop: 16 }}> |
|
||||
<Button onClick={loadMore} style={{ paddingLeft: 48, paddingRight: 48 }}> |
|
||||
{loadingMore ? ( |
|
||||
<span> |
|
||||
<LoadingOutlined /> 加载中... |
|
||||
</span> |
|
||||
) : ( |
|
||||
'加载更多' |
|
||||
)} |
|
||||
</Button> |
|
||||
</div> |
|
||||
); |
|
||||
|
|
||||
const ownerOptions = useMemo<DefaultOptionType[]>( |
|
||||
() => |
|
||||
owners.map((item) => ({ |
|
||||
label: item.name, |
|
||||
value: item.id, |
|
||||
})), |
|
||||
[], |
|
||||
); |
|
||||
|
|
||||
return ( |
|
||||
<> |
|
||||
<Card variant="borderless"> |
|
||||
<Form |
|
||||
layout="inline" |
|
||||
form={form} |
|
||||
initialValues={{ |
|
||||
owner: ['wjh', 'zxx'], |
|
||||
}} |
|
||||
onValuesChange={reload} |
|
||||
> |
|
||||
<StandardFormRow title="所属类目" block style={{ paddingBottom: 11 }}> |
|
||||
<FormItem name="category"> |
|
||||
<TagSelect expandable> |
|
||||
{categoryOptions |
|
||||
.filter( |
|
||||
( |
|
||||
category, |
|
||||
): category is { value: string | number; label: string } => |
|
||||
category.value !== undefined && category.value !== null, |
|
||||
) |
|
||||
.map((category) => ( |
|
||||
<TagSelect.Option |
|
||||
value={category.value} |
|
||||
key={category.value} |
|
||||
> |
|
||||
{category.label} |
|
||||
</TagSelect.Option> |
|
||||
))} |
|
||||
</TagSelect> |
|
||||
</FormItem> |
|
||||
</StandardFormRow> |
|
||||
<StandardFormRow title="owner" grid> |
|
||||
<FormItem name="owner" noStyle> |
|
||||
<Select |
|
||||
mode="multiple" |
|
||||
placeholder="选择 owner" |
|
||||
style={{ minWidth: '6rem' }} |
|
||||
options={ownerOptions} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
<a className={styles.selfTrigger} onClick={setOwner}> |
|
||||
只看自己的 |
|
||||
</a> |
|
||||
</StandardFormRow> |
|
||||
<StandardFormRow title="其它选项" grid last> |
|
||||
<Row gutter={16}> |
|
||||
<Col xl={8} lg={10} md={12} sm={24} xs={24}> |
|
||||
<FormItem {...formItemLayout} label="活跃用户" name="user"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ maxWidth: 200, width: '100%' }} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '李三', |
|
||||
value: 'lisa', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
</Col> |
|
||||
<Col xl={8} lg={10} md={12} sm={24} xs={24}> |
|
||||
<FormItem {...formItemLayout} label="好评度" name="rate"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ maxWidth: 200, width: '100%' }} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '优秀', |
|
||||
value: 'good', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</StandardFormRow> |
|
||||
</Form> |
|
||||
</Card> |
|
||||
<Card |
|
||||
style={{ marginTop: 24 }} |
|
||||
variant="borderless" |
|
||||
styles={{ |
|
||||
body: { |
|
||||
padding: '8px 32px 32px 32px', |
|
||||
}, |
|
||||
}} |
|
||||
> |
|
||||
<List<ListItemDataType> |
|
||||
size="large" |
|
||||
loading={loading} |
|
||||
rowKey="id" |
|
||||
itemLayout="vertical" |
|
||||
loadMore={loadMoreDom} |
|
||||
dataSource={list} |
|
||||
renderItem={(item) => ( |
|
||||
<List.Item |
|
||||
key={item.id} |
|
||||
actions={[ |
|
||||
<IconText key="star" type="star-o" text={item.star} />, |
|
||||
<IconText key="like" type="like-o" text={item.like} />, |
|
||||
<IconText key="message" type="message" text={item.message} />, |
|
||||
]} |
|
||||
extra={<div className={styles.listItemExtra} />} |
|
||||
> |
|
||||
<List.Item.Meta |
|
||||
title={ |
|
||||
<a className={styles.listItemMetaTitle} href={item.href}> |
|
||||
{item.title} |
|
||||
</a> |
|
||||
} |
|
||||
description={ |
|
||||
<span> |
|
||||
<Tag>Ant Design</Tag> |
|
||||
<Tag>设计语言</Tag> |
|
||||
<Tag>蚂蚁金服</Tag> |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
<ArticleListContent data={item} /> |
|
||||
</List.Item> |
|
||||
)} |
|
||||
/> |
|
||||
</Card> |
|
||||
</> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default Articles; |
|
||||
@ -1,176 +0,0 @@ |
|||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Card, Col, Form, List, Row, Select, Typography } from 'antd'; |
|
||||
import dayjs from 'dayjs'; |
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'; |
|
||||
import type { FC } from 'react'; |
|
||||
import { categoryOptions } from '../../mock'; |
|
||||
import AvatarList from './components/AvatarList'; |
|
||||
import StandardFormRow from './components/StandardFormRow'; |
|
||||
import TagSelect from './components/TagSelect'; |
|
||||
import type { ListItemDataType } from './data.d'; |
|
||||
import { queryFakeList } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
dayjs.extend(relativeTime); |
|
||||
|
|
||||
const FormItem = Form.Item; |
|
||||
const { Paragraph } = Typography; |
|
||||
const getKey = (id: string, index: number) => `${id}-${index}`; |
|
||||
const Projects: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { data, loading, run } = useRequest((values: any) => { |
|
||||
console.log('form data', values); |
|
||||
return queryFakeList({ |
|
||||
count: 8, |
|
||||
}); |
|
||||
}); |
|
||||
const list = data?.list || []; |
|
||||
const cardList = list && ( |
|
||||
<List<ListItemDataType> |
|
||||
rowKey="id" |
|
||||
loading={loading} |
|
||||
grid={{ |
|
||||
gutter: 16, |
|
||||
xs: 1, |
|
||||
sm: 2, |
|
||||
md: 3, |
|
||||
lg: 3, |
|
||||
xl: 4, |
|
||||
xxl: 4, |
|
||||
}} |
|
||||
dataSource={list} |
|
||||
renderItem={(item) => ( |
|
||||
<List.Item> |
|
||||
<Card |
|
||||
className={styles.card} |
|
||||
hoverable |
|
||||
cover={<img alt={item.title} src={item.cover} />} |
|
||||
> |
|
||||
<Card.Meta |
|
||||
title={<a>{item.title}</a>} |
|
||||
description={ |
|
||||
<Paragraph |
|
||||
ellipsis={{ |
|
||||
rows: 2, |
|
||||
}} |
|
||||
> |
|
||||
{item.subDescription} |
|
||||
</Paragraph> |
|
||||
} |
|
||||
/> |
|
||||
<div className={styles.cardItemContent}> |
|
||||
<span>{dayjs(item.updatedAt).fromNow()}</span> |
|
||||
<div className={styles.avatarList}> |
|
||||
<AvatarList size="small"> |
|
||||
{item.members.map((member, i) => ( |
|
||||
<AvatarList.Item |
|
||||
key={getKey(item.id, i)} |
|
||||
src={member.avatar} |
|
||||
tips={member.name} |
|
||||
/> |
|
||||
))} |
|
||||
</AvatarList> |
|
||||
</div> |
|
||||
</div> |
|
||||
</Card> |
|
||||
</List.Item> |
|
||||
)} |
|
||||
/> |
|
||||
); |
|
||||
const formItemLayout = { |
|
||||
wrapperCol: { |
|
||||
xs: { |
|
||||
span: 24, |
|
||||
}, |
|
||||
sm: { |
|
||||
span: 16, |
|
||||
}, |
|
||||
}, |
|
||||
}; |
|
||||
return ( |
|
||||
<div className={styles.coverCardList}> |
|
||||
<Card variant="borderless"> |
|
||||
<Form |
|
||||
layout="inline" |
|
||||
onValuesChange={(_, values) => { |
|
||||
// 表单项变化时请求数据
|
|
||||
// 模拟查询表单生效
|
|
||||
run(values); |
|
||||
}} |
|
||||
> |
|
||||
<StandardFormRow |
|
||||
title="所属类目" |
|
||||
block |
|
||||
style={{ |
|
||||
paddingBottom: 11, |
|
||||
}} |
|
||||
> |
|
||||
<FormItem name="category"> |
|
||||
<TagSelect expandable> |
|
||||
{categoryOptions |
|
||||
.filter( |
|
||||
( |
|
||||
category, |
|
||||
): category is { value: string | number; label: string } => |
|
||||
category.value !== undefined && category.value !== null, |
|
||||
) |
|
||||
.map((category) => ( |
|
||||
<TagSelect.Option |
|
||||
value={category.value} |
|
||||
key={category.value} |
|
||||
> |
|
||||
{category.label} |
|
||||
</TagSelect.Option> |
|
||||
))} |
|
||||
</TagSelect> |
|
||||
</FormItem> |
|
||||
</StandardFormRow> |
|
||||
<StandardFormRow title="其它选项" grid last> |
|
||||
<Row gutter={16}> |
|
||||
<Col lg={8} md={10} sm={10} xs={24}> |
|
||||
<FormItem {...formItemLayout} label="作者" name="author"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ |
|
||||
maxWidth: 200, |
|
||||
width: '100%', |
|
||||
}} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '王昭君', |
|
||||
value: 'lisa', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
</Col> |
|
||||
<Col lg={8} md={10} sm={10} xs={24}> |
|
||||
<FormItem {...formItemLayout} label="好评度" name="rate"> |
|
||||
<Select |
|
||||
placeholder="不限" |
|
||||
style={{ |
|
||||
maxWidth: 200, |
|
||||
width: '100%', |
|
||||
}} |
|
||||
options={[ |
|
||||
{ |
|
||||
label: '优秀', |
|
||||
value: 'good', |
|
||||
}, |
|
||||
{ |
|
||||
label: '普通', |
|
||||
value: 'normal', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</FormItem> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
</StandardFormRow> |
|
||||
</Form> |
|
||||
</Card> |
|
||||
<div className={styles.cardList}>{cardList}</div> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export default Projects; |
|
||||
@ -1,26 +0,0 @@ |
|||||
import { Modal } from 'antd'; |
|
||||
import React from 'react'; |
|
||||
|
|
||||
type CreateFormProps = { |
|
||||
modalVisible: boolean; |
|
||||
children?: React.ReactNode; |
|
||||
onCancel: () => void; |
|
||||
}; |
|
||||
|
|
||||
const CreateForm: React.FC<CreateFormProps> = (props) => { |
|
||||
const { modalVisible, onCancel } = props; |
|
||||
|
|
||||
return ( |
|
||||
<Modal |
|
||||
destroyOnHidden |
|
||||
title="新建规则" |
|
||||
open={modalVisible} |
|
||||
onCancel={() => onCancel()} |
|
||||
footer={null} |
|
||||
> |
|
||||
{props.children} |
|
||||
</Modal> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default CreateForm; |
|
||||
@ -1,161 +0,0 @@ |
|||||
import { |
|
||||
ProFormDateTimePicker, |
|
||||
ProFormRadio, |
|
||||
ProFormSelect, |
|
||||
ProFormText, |
|
||||
ProFormTextArea, |
|
||||
StepsForm, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import { Modal } from 'antd'; |
|
||||
import React from 'react'; |
|
||||
import type { TableListItem } from '../data'; |
|
||||
|
|
||||
export type FormValueType = { |
|
||||
target?: string; |
|
||||
template?: string; |
|
||||
type?: string; |
|
||||
time?: string; |
|
||||
frequency?: string; |
|
||||
} & Partial<TableListItem>; |
|
||||
|
|
||||
export type UpdateFormProps = { |
|
||||
onCancel: (flag?: boolean, formVals?: FormValueType) => void; |
|
||||
onSubmit: (values: FormValueType) => Promise<void>; |
|
||||
updateModalVisible: boolean; |
|
||||
values: Partial<TableListItem>; |
|
||||
}; |
|
||||
|
|
||||
const UpdateForm: React.FC<UpdateFormProps> = (props) => { |
|
||||
return ( |
|
||||
<StepsForm |
|
||||
stepsProps={{ |
|
||||
size: 'small', |
|
||||
}} |
|
||||
stepsFormRender={(dom, submitter) => { |
|
||||
return ( |
|
||||
<Modal |
|
||||
width={640} |
|
||||
styles={{ |
|
||||
body: { |
|
||||
padding: '32px 40px 48px', |
|
||||
}, |
|
||||
}} |
|
||||
destroyOnHidden |
|
||||
title="规则配置" |
|
||||
open={props.updateModalVisible} |
|
||||
footer={submitter} |
|
||||
onCancel={() => { |
|
||||
props.onCancel(); |
|
||||
}} |
|
||||
> |
|
||||
{dom} |
|
||||
</Modal> |
|
||||
); |
|
||||
}} |
|
||||
onFinish={props.onSubmit} |
|
||||
> |
|
||||
<StepsForm.StepForm |
|
||||
initialValues={{ |
|
||||
name: props.values.name, |
|
||||
desc: props.values.desc, |
|
||||
}} |
|
||||
title="基本信息" |
|
||||
> |
|
||||
<ProFormText |
|
||||
name="name" |
|
||||
label="规则名称" |
|
||||
width="md" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入规则名称!', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
<ProFormTextArea |
|
||||
name="desc" |
|
||||
width="md" |
|
||||
label="规则描述" |
|
||||
placeholder="请输入至少五个字符" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入至少五个字符的规则描述!', |
|
||||
min: 5, |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</StepsForm.StepForm> |
|
||||
<StepsForm.StepForm |
|
||||
initialValues={{ |
|
||||
target: '0', |
|
||||
template: '0', |
|
||||
}} |
|
||||
title="配置规则属性" |
|
||||
> |
|
||||
<ProFormSelect |
|
||||
name="target" |
|
||||
width="md" |
|
||||
label="监控对象" |
|
||||
valueEnum={{ |
|
||||
0: '表一', |
|
||||
1: '表二', |
|
||||
}} |
|
||||
/> |
|
||||
<ProFormSelect |
|
||||
name="template" |
|
||||
width="md" |
|
||||
label="规则模板" |
|
||||
valueEnum={{ |
|
||||
0: '规则模板一', |
|
||||
1: '规则模板二', |
|
||||
}} |
|
||||
/> |
|
||||
<ProFormRadio.Group |
|
||||
name="type" |
|
||||
label="规则类型" |
|
||||
options={[ |
|
||||
{ |
|
||||
value: '0', |
|
||||
label: '强', |
|
||||
}, |
|
||||
{ |
|
||||
value: '1', |
|
||||
label: '弱', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
</StepsForm.StepForm> |
|
||||
<StepsForm.StepForm |
|
||||
initialValues={{ |
|
||||
type: '1', |
|
||||
frequency: 'month', |
|
||||
}} |
|
||||
title="设定调度周期" |
|
||||
> |
|
||||
<ProFormDateTimePicker |
|
||||
name="time" |
|
||||
width="md" |
|
||||
label="开始时间" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请选择开始时间!', |
|
||||
}, |
|
||||
]} |
|
||||
/> |
|
||||
<ProFormSelect |
|
||||
name="frequency" |
|
||||
label="监控对象" |
|
||||
width="md" |
|
||||
valueEnum={{ |
|
||||
month: '月', |
|
||||
week: '周', |
|
||||
}} |
|
||||
/> |
|
||||
</StepsForm.StepForm> |
|
||||
</StepsForm> |
|
||||
); |
|
||||
}; |
|
||||
|
|
||||
export default UpdateForm; |
|
||||
@ -1,493 +0,0 @@ |
|||||
import { |
|
||||
DingdingOutlined, |
|
||||
DownOutlined, |
|
||||
EllipsisOutlined, |
|
||||
InfoCircleOutlined, |
|
||||
} from '@ant-design/icons'; |
|
||||
import { |
|
||||
GridContent, |
|
||||
PageContainer, |
|
||||
RouteContext, |
|
||||
} from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { |
|
||||
Badge, |
|
||||
Button, |
|
||||
Card, |
|
||||
Descriptions, |
|
||||
Divider, |
|
||||
Dropdown, |
|
||||
Empty, |
|
||||
Popover, |
|
||||
Space, |
|
||||
Statistic, |
|
||||
Steps, |
|
||||
Table, |
|
||||
Tooltip, |
|
||||
} from 'antd'; |
|
||||
import classNames from 'classnames'; |
|
||||
import type { FC } from 'react'; |
|
||||
import React, { useState } from 'react'; |
|
||||
import type { AdvancedProfileData } from './data.d'; |
|
||||
import { queryAdvancedProfile } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const { Step } = Steps; |
|
||||
|
|
||||
const action = ( |
|
||||
<RouteContext.Consumer> |
|
||||
{({ isMobile }) => { |
|
||||
if (isMobile) { |
|
||||
return ( |
|
||||
<Dropdown.Button |
|
||||
type="primary" |
|
||||
icon={<DownOutlined />} |
|
||||
menu={{ |
|
||||
items: [ |
|
||||
{ |
|
||||
key: '1', |
|
||||
label: '操作一', |
|
||||
}, |
|
||||
{ |
|
||||
key: '2', |
|
||||
label: '操作二', |
|
||||
}, |
|
||||
{ |
|
||||
key: '3', |
|
||||
label: '操作三', |
|
||||
}, |
|
||||
], |
|
||||
}} |
|
||||
placement="bottomRight" |
|
||||
> |
|
||||
主操作 |
|
||||
</Dropdown.Button> |
|
||||
); |
|
||||
} |
|
||||
return ( |
|
||||
<Space> |
|
||||
<Space.Compact> |
|
||||
<Button>操作一</Button> |
|
||||
<Button>操作二</Button> |
|
||||
<Dropdown |
|
||||
menu={{ |
|
||||
items: [ |
|
||||
{ |
|
||||
key: '1', |
|
||||
label: '选项一', |
|
||||
}, |
|
||||
{ |
|
||||
key: '2', |
|
||||
label: '选项二', |
|
||||
}, |
|
||||
{ |
|
||||
key: '3', |
|
||||
label: '选项三', |
|
||||
}, |
|
||||
], |
|
||||
}} |
|
||||
placement="bottomRight" |
|
||||
> |
|
||||
<Button> |
|
||||
<EllipsisOutlined /> |
|
||||
</Button> |
|
||||
</Dropdown> |
|
||||
</Space.Compact> |
|
||||
<Button type="primary">主操作</Button> |
|
||||
</Space> |
|
||||
); |
|
||||
}} |
|
||||
</RouteContext.Consumer> |
|
||||
); |
|
||||
|
|
||||
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: string) => { |
|
||||
if (text === 'agree') { |
|
||||
return <Badge status="success" text="成功" />; |
|
||||
} |
|
||||
return <Badge status="error" text="驳回" />; |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
title: '操作时间', |
|
||||
dataIndex: 'updatedAt', |
|
||||
key: 'updatedAt', |
|
||||
}, |
|
||||
{ |
|
||||
title: '备注', |
|
||||
dataIndex: 'memo', |
|
||||
key: 'memo', |
|
||||
}, |
|
||||
]; |
|
||||
type AdvancedState = { |
|
||||
operationKey: 'tab1' | 'tab2' | 'tab3'; |
|
||||
tabActiveKey: string; |
|
||||
}; |
|
||||
const Advanced: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
|
|
||||
const extra = ( |
|
||||
<div className={styles.moreInfo}> |
|
||||
<Statistic title="状态" value="待审批" /> |
|
||||
<Statistic title="订单金额" value={568.08} prefix="¥" /> |
|
||||
</div> |
|
||||
); |
|
||||
const description = ( |
|
||||
<RouteContext.Consumer> |
|
||||
{({ isMobile }) => ( |
|
||||
<Descriptions |
|
||||
className={styles.headerList} |
|
||||
size="small" |
|
||||
column={isMobile ? 1 : 2} |
|
||||
> |
|
||||
<Descriptions.Item label="创建人">曲丽丽</Descriptions.Item> |
|
||||
<Descriptions.Item label="订购产品">XX 服务</Descriptions.Item> |
|
||||
<Descriptions.Item label="创建时间">2017-07-07</Descriptions.Item> |
|
||||
<Descriptions.Item label="关联单据"> |
|
||||
<a href="">12421</a> |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="生效日期"> |
|
||||
2017-07-07 ~ 2017-08-08 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="备注"> |
|
||||
请于两个工作日内确认 |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
)} |
|
||||
</RouteContext.Consumer> |
|
||||
); |
|
||||
const desc1 = ( |
|
||||
<div className={classNames(styles.stepDescription)}> |
|
||||
曲丽丽 |
|
||||
<DingdingOutlined |
|
||||
style={{ |
|
||||
marginLeft: 8, |
|
||||
}} |
|
||||
/> |
|
||||
<div>2016-12-12 12:32</div> |
|
||||
</div> |
|
||||
); |
|
||||
const desc2 = ( |
|
||||
<div className={styles.stepDescription}> |
|
||||
周毛毛 |
|
||||
<DingdingOutlined |
|
||||
style={{ |
|
||||
color: '#00A0E9', |
|
||||
marginLeft: 8, |
|
||||
}} |
|
||||
/> |
|
||||
<div> |
|
||||
<a href="">催一下</a> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
|
|
||||
const [tabStatus, seTabStatus] = useState<AdvancedState>({ |
|
||||
operationKey: 'tab1', |
|
||||
tabActiveKey: 'detail', |
|
||||
}); |
|
||||
|
|
||||
const customDot = ( |
|
||||
dot: React.ReactNode, |
|
||||
{ |
|
||||
status, |
|
||||
}: { |
|
||||
status: string; |
|
||||
}, |
|
||||
) => { |
|
||||
const popoverContent = ( |
|
||||
<div |
|
||||
style={{ |
|
||||
width: 160, |
|
||||
}} |
|
||||
> |
|
||||
吴加号 |
|
||||
<span |
|
||||
style={{ |
|
||||
float: 'right', |
|
||||
}} |
|
||||
> |
|
||||
<Badge |
|
||||
status="default" |
|
||||
text={ |
|
||||
<span |
|
||||
style={{ |
|
||||
color: 'rgba(0, 0, 0, 0.45)', |
|
||||
}} |
|
||||
> |
|
||||
未响应 |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
</span> |
|
||||
<div |
|
||||
style={{ |
|
||||
marginTop: 4, |
|
||||
}} |
|
||||
> |
|
||||
耗时:2小时25分钟 |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
if (status === 'process') { |
|
||||
return ( |
|
||||
<Popover |
|
||||
placement="topLeft" |
|
||||
arrow={{ |
|
||||
pointAtCenter: true, |
|
||||
}} |
|
||||
content={popoverContent} |
|
||||
> |
|
||||
<span>{dot}</span> |
|
||||
</Popover> |
|
||||
); |
|
||||
} |
|
||||
return dot; |
|
||||
}; |
|
||||
|
|
||||
const { data = {}, loading } = useRequest<{ |
|
||||
data: AdvancedProfileData; |
|
||||
}>(queryAdvancedProfile); |
|
||||
const { advancedOperation1, advancedOperation2, advancedOperation3 } = data; |
|
||||
const contentList = { |
|
||||
tab1: ( |
|
||||
<Table |
|
||||
pagination={false} |
|
||||
loading={loading} |
|
||||
dataSource={advancedOperation1} |
|
||||
columns={columns} |
|
||||
/> |
|
||||
), |
|
||||
tab2: ( |
|
||||
<Table |
|
||||
pagination={false} |
|
||||
loading={loading} |
|
||||
dataSource={advancedOperation2} |
|
||||
columns={columns} |
|
||||
/> |
|
||||
), |
|
||||
tab3: ( |
|
||||
<Table |
|
||||
pagination={false} |
|
||||
loading={loading} |
|
||||
dataSource={advancedOperation3} |
|
||||
columns={columns} |
|
||||
/> |
|
||||
), |
|
||||
}; |
|
||||
const onTabChange = (tabActiveKey: string) => { |
|
||||
seTabStatus({ |
|
||||
...tabStatus, |
|
||||
tabActiveKey, |
|
||||
}); |
|
||||
}; |
|
||||
const onOperationTabChange = (key: string) => { |
|
||||
seTabStatus({ |
|
||||
...tabStatus, |
|
||||
operationKey: key as 'tab1', |
|
||||
}); |
|
||||
}; |
|
||||
return ( |
|
||||
<PageContainer |
|
||||
title="单号:234231029431" |
|
||||
extra={action} |
|
||||
className={styles.pageHeader} |
|
||||
content={description} |
|
||||
extraContent={extra} |
|
||||
tabActiveKey={tabStatus.tabActiveKey} |
|
||||
onTabChange={onTabChange} |
|
||||
tabList={[ |
|
||||
{ |
|
||||
key: 'detail', |
|
||||
tab: '详情', |
|
||||
}, |
|
||||
{ |
|
||||
key: 'rule', |
|
||||
tab: '规则', |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<div className={styles.main}> |
|
||||
<GridContent> |
|
||||
<Card |
|
||||
title="流程进度" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<RouteContext.Consumer> |
|
||||
{({ isMobile }) => ( |
|
||||
<Steps |
|
||||
direction={isMobile ? 'vertical' : 'horizontal'} |
|
||||
progressDot={customDot} |
|
||||
current={1} |
|
||||
> |
|
||||
<Step title="创建项目" description={desc1} /> |
|
||||
<Step title="部门初审" description={desc2} /> |
|
||||
<Step title="财务复核" /> |
|
||||
<Step title="完成" /> |
|
||||
</Steps> |
|
||||
)} |
|
||||
</RouteContext.Consumer> |
|
||||
</Card> |
|
||||
<Card |
|
||||
title="用户信息" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
variant="borderless" |
|
||||
> |
|
||||
<Descriptions |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
> |
|
||||
<Descriptions.Item label="用户姓名">付小小</Descriptions.Item> |
|
||||
<Descriptions.Item label="会员卡号"> |
|
||||
32943898021309809423 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="身份证"> |
|
||||
3321944288191034921 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="联系方式"> |
|
||||
18112345678 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="联系地址"> |
|
||||
曲丽丽 18100000000 浙江省杭州市西湖区黄姑山路工专路交叉路口 |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<Descriptions |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
title="信息组" |
|
||||
> |
|
||||
<Descriptions.Item label="某某数据">725</Descriptions.Item> |
|
||||
<Descriptions.Item label="该数据更新时间"> |
|
||||
2017-08-08 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item |
|
||||
label={ |
|
||||
<span> |
|
||||
某某数据 |
|
||||
<Tooltip title="数据说明"> |
|
||||
<InfoCircleOutlined |
|
||||
style={{ |
|
||||
color: 'rgba(0, 0, 0, 0.43)', |
|
||||
marginLeft: 4, |
|
||||
}} |
|
||||
/> |
|
||||
</Tooltip> |
|
||||
</span> |
|
||||
} |
|
||||
> |
|
||||
725 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="该数据更新时间"> |
|
||||
2017-08-08 |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<h4 |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
> |
|
||||
信息组 |
|
||||
</h4> |
|
||||
<Card type="inner" title="多层级信息组"> |
|
||||
<Descriptions |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
title="组名称" |
|
||||
> |
|
||||
<Descriptions.Item label="负责人">林东东</Descriptions.Item> |
|
||||
<Descriptions.Item label="角色码">1234567</Descriptions.Item> |
|
||||
<Descriptions.Item label="所属部门"> |
|
||||
XX公司 - YY部 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="过期时间"> |
|
||||
2017-08-08 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="描述"> |
|
||||
这段描述很长很长很长很长很长很长很长很长很长很长很长很长很长很长... |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
margin: '16px 0', |
|
||||
}} |
|
||||
/> |
|
||||
<Descriptions |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
title="组名称" |
|
||||
column={1} |
|
||||
> |
|
||||
<Descriptions.Item label="学名"> |
|
||||
Citrullus lanatus (Thunb.) Matsum. et |
|
||||
Nakai一年生蔓生藤本;茎、枝粗壮,具明显的棱。卷须较粗.. |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
margin: '16px 0', |
|
||||
}} |
|
||||
/> |
|
||||
<Descriptions title="组名称"> |
|
||||
<Descriptions.Item label="负责人">付小小</Descriptions.Item> |
|
||||
<Descriptions.Item label="角色码">1234568</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
</Card> |
|
||||
</Card> |
|
||||
<Card |
|
||||
title="用户近半年来电记录" |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
variant="borderless" |
|
||||
> |
|
||||
<Empty /> |
|
||||
</Card> |
|
||||
<Card |
|
||||
variant="borderless" |
|
||||
tabList={operationTabList} |
|
||||
onTabChange={onOperationTabChange} |
|
||||
> |
|
||||
{contentList[tabStatus.operationKey] as React.ReactNode} |
|
||||
</Card> |
|
||||
</GridContent> |
|
||||
</div> |
|
||||
</PageContainer> |
|
||||
); |
|
||||
}; |
|
||||
export default Advanced; |
|
||||
@ -1,236 +0,0 @@ |
|||||
import type { ProColumns } from '@ant-design/pro-components'; |
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components'; |
|
||||
import { useRequest } from '@umijs/max'; |
|
||||
import { Badge, Card, Descriptions, Divider } from 'antd'; |
|
||||
import type { FC } from 'react'; |
|
||||
import React from 'react'; |
|
||||
import type { BasicGood, BasicProgress } from './data.d'; |
|
||||
import { queryBasicProfile } from './service'; |
|
||||
import useStyles from './style.style'; |
|
||||
|
|
||||
const progressColumns: ProColumns<BasicProgress>[] = [ |
|
||||
{ |
|
||||
title: '时间', |
|
||||
dataIndex: 'time', |
|
||||
key: 'time', |
|
||||
}, |
|
||||
{ |
|
||||
title: '当前进度', |
|
||||
dataIndex: 'rate', |
|
||||
key: 'rate', |
|
||||
}, |
|
||||
{ |
|
||||
title: '状态', |
|
||||
dataIndex: 'status', |
|
||||
key: 'status', |
|
||||
render: (text: React.ReactNode) => { |
|
||||
if (text === 'success') { |
|
||||
return <Badge status="success" text="成功" />; |
|
||||
} |
|
||||
return <Badge status="processing" text="进行中" />; |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
title: '操作员ID', |
|
||||
dataIndex: 'operator', |
|
||||
key: 'operator', |
|
||||
}, |
|
||||
{ |
|
||||
title: '耗时', |
|
||||
dataIndex: 'cost', |
|
||||
key: 'cost', |
|
||||
}, |
|
||||
]; |
|
||||
const Basic: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const { data, loading } = useRequest(() => { |
|
||||
return queryBasicProfile(); |
|
||||
}); |
|
||||
const { basicGoods, basicProgress } = data || { |
|
||||
basicGoods: [], |
|
||||
basicProgress: [], |
|
||||
}; |
|
||||
let goodsData: typeof basicGoods = []; |
|
||||
if (basicGoods.length) { |
|
||||
let num = 0; |
|
||||
let amount = 0; |
|
||||
basicGoods.forEach((item) => { |
|
||||
num += Number(item.num); |
|
||||
amount += Number(item.amount); |
|
||||
}); |
|
||||
goodsData = basicGoods.concat({ |
|
||||
id: '总计', |
|
||||
num, |
|
||||
amount, |
|
||||
}); |
|
||||
} |
|
||||
const renderContent = (value: any, _: any, index: any) => { |
|
||||
const obj: { |
|
||||
children: any; |
|
||||
props: { |
|
||||
colSpan?: number; |
|
||||
}; |
|
||||
} = { |
|
||||
children: value, |
|
||||
props: {}, |
|
||||
}; |
|
||||
if (index === basicGoods.length) { |
|
||||
obj.props.colSpan = 0; |
|
||||
} |
|
||||
return obj; |
|
||||
}; |
|
||||
const goodsColumns: ProColumns<BasicGood>[] = [ |
|
||||
{ |
|
||||
title: '商品编号', |
|
||||
dataIndex: 'id', |
|
||||
key: 'id', |
|
||||
render: (text: React.ReactNode, _: any, index: number) => { |
|
||||
if (index < basicGoods.length) { |
|
||||
return <span>{text}</span>; |
|
||||
} |
|
||||
return { |
|
||||
children: ( |
|
||||
<span |
|
||||
style={{ |
|
||||
fontWeight: 600, |
|
||||
}} |
|
||||
> |
|
||||
总计 |
|
||||
</span> |
|
||||
), |
|
||||
props: { |
|
||||
colSpan: 4, |
|
||||
}, |
|
||||
}; |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
title: '商品名称', |
|
||||
dataIndex: 'name', |
|
||||
key: 'name', |
|
||||
render: renderContent, |
|
||||
}, |
|
||||
{ |
|
||||
title: '商品条码', |
|
||||
dataIndex: 'barcode', |
|
||||
key: 'barcode', |
|
||||
render: renderContent, |
|
||||
}, |
|
||||
{ |
|
||||
title: '单价', |
|
||||
dataIndex: 'price', |
|
||||
key: 'price', |
|
||||
align: 'right' as 'left' | 'right' | 'center', |
|
||||
render: renderContent, |
|
||||
}, |
|
||||
{ |
|
||||
title: '数量(件)', |
|
||||
dataIndex: 'num', |
|
||||
key: 'num', |
|
||||
align: 'right' as 'left' | 'right' | 'center', |
|
||||
render: (text: React.ReactNode, _: any, index: number) => { |
|
||||
if (index < basicGoods.length) { |
|
||||
return text; |
|
||||
} |
|
||||
return ( |
|
||||
<span |
|
||||
style={{ |
|
||||
fontWeight: 600, |
|
||||
}} |
|
||||
> |
|
||||
{text} |
|
||||
</span> |
|
||||
); |
|
||||
}, |
|
||||
}, |
|
||||
{ |
|
||||
title: '金额', |
|
||||
dataIndex: 'amount', |
|
||||
key: 'amount', |
|
||||
align: 'right' as 'left' | 'right' | 'center', |
|
||||
render: (text: React.ReactNode, _: any, index: number) => { |
|
||||
if (index < basicGoods.length) { |
|
||||
return text; |
|
||||
} |
|
||||
return ( |
|
||||
<span |
|
||||
style={{ |
|
||||
fontWeight: 600, |
|
||||
}} |
|
||||
> |
|
||||
{text} |
|
||||
</span> |
|
||||
); |
|
||||
}, |
|
||||
}, |
|
||||
]; |
|
||||
return ( |
|
||||
<PageContainer> |
|
||||
<Card variant="borderless"> |
|
||||
<Descriptions |
|
||||
title="退款申请" |
|
||||
style={{ |
|
||||
marginBottom: 32, |
|
||||
}} |
|
||||
> |
|
||||
<Descriptions.Item label="取货单号">1000000000</Descriptions.Item> |
|
||||
<Descriptions.Item label="状态">已取货</Descriptions.Item> |
|
||||
<Descriptions.Item label="销售单号">1234123421</Descriptions.Item> |
|
||||
<Descriptions.Item label="子订单">3214321432</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
marginBottom: 32, |
|
||||
}} |
|
||||
/> |
|
||||
<Descriptions |
|
||||
title="用户信息" |
|
||||
style={{ |
|
||||
marginBottom: 32, |
|
||||
}} |
|
||||
> |
|
||||
<Descriptions.Item label="用户姓名">付小小</Descriptions.Item> |
|
||||
<Descriptions.Item label="联系电话">18100000000</Descriptions.Item> |
|
||||
<Descriptions.Item label="常用快递">菜鸟仓储</Descriptions.Item> |
|
||||
<Descriptions.Item label="取货地址"> |
|
||||
浙江省杭州市西湖区万塘路18号 |
|
||||
</Descriptions.Item> |
|
||||
<Descriptions.Item label="备注">无</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<Divider |
|
||||
style={{ |
|
||||
marginBottom: 32, |
|
||||
}} |
|
||||
/> |
|
||||
<div className={styles.title}>退货商品</div> |
|
||||
<ProTable |
|
||||
style={{ |
|
||||
marginBottom: 24, |
|
||||
}} |
|
||||
pagination={false} |
|
||||
search={false} |
|
||||
loading={loading} |
|
||||
options={false} |
|
||||
toolBarRender={false} |
|
||||
dataSource={goodsData} |
|
||||
columns={goodsColumns} |
|
||||
rowKey="id" |
|
||||
/> |
|
||||
<div className={styles.title}>退货进度</div> |
|
||||
<ProTable |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
pagination={false} |
|
||||
loading={loading} |
|
||||
search={false} |
|
||||
options={false} |
|
||||
toolBarRender={false} |
|
||||
dataSource={basicProgress} |
|
||||
columns={progressColumns} |
|
||||
/> |
|
||||
</Card> |
|
||||
</PageContainer> |
|
||||
); |
|
||||
}; |
|
||||
export default Basic; |
|
||||
@ -1,75 +0,0 @@ |
|||||
import { CloseCircleOutlined, RightOutlined } from '@ant-design/icons'; |
|
||||
import { GridContent } from '@ant-design/pro-components'; |
|
||||
import { Button, Card, Result } from 'antd'; |
|
||||
import useStyles from './index.style'; |
|
||||
|
|
||||
export default () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const Content = ( |
|
||||
<> |
|
||||
<div className={styles.title}> |
|
||||
<span>您提交的内容有如下错误:</span> |
|
||||
</div> |
|
||||
<div |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
> |
|
||||
<CloseCircleOutlined |
|
||||
style={{ |
|
||||
marginRight: 8, |
|
||||
}} |
|
||||
className={styles.error_icon} |
|
||||
/> |
|
||||
<span>您的账户已被冻结</span> |
|
||||
<a |
|
||||
style={{ |
|
||||
marginLeft: 16, |
|
||||
}} |
|
||||
> |
|
||||
<span>立即解冻</span> |
|
||||
<RightOutlined /> |
|
||||
</a> |
|
||||
</div> |
|
||||
<div> |
|
||||
<CloseCircleOutlined |
|
||||
style={{ |
|
||||
marginRight: 8, |
|
||||
}} |
|
||||
className={styles.error_icon} |
|
||||
/> |
|
||||
<span>您的账户还不具备申请资格</span> |
|
||||
<a |
|
||||
style={{ |
|
||||
marginLeft: 16, |
|
||||
}} |
|
||||
> |
|
||||
<span>立即升级</span> |
|
||||
<RightOutlined /> |
|
||||
</a> |
|
||||
</div> |
|
||||
</> |
|
||||
); |
|
||||
return ( |
|
||||
<GridContent> |
|
||||
<Card variant="borderless"> |
|
||||
<Result |
|
||||
status="error" |
|
||||
title="提交失败" |
|
||||
subTitle="请核对并修改以下信息后,再重新提交。" |
|
||||
extra={ |
|
||||
<Button type="primary"> |
|
||||
<span>返回修改</span> |
|
||||
</Button> |
|
||||
} |
|
||||
style={{ |
|
||||
marginTop: 48, |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
> |
|
||||
{Content} |
|
||||
</Result> |
|
||||
</Card> |
|
||||
</GridContent> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,137 +0,0 @@ |
|||||
import { DingdingOutlined } from '@ant-design/icons'; |
|
||||
import { GridContent } from '@ant-design/pro-components'; |
|
||||
import { Button, Card, Descriptions, Result, Steps } from 'antd'; |
|
||||
import useStyles from './index.style'; |
|
||||
|
|
||||
const { Step } = Steps; |
|
||||
|
|
||||
export default () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const desc1 = ( |
|
||||
<div className={styles.title}> |
|
||||
<div |
|
||||
style={{ |
|
||||
margin: '8px 0 4px', |
|
||||
}} |
|
||||
> |
|
||||
<span>曲丽丽</span> |
|
||||
<DingdingOutlined |
|
||||
style={{ |
|
||||
marginLeft: 8, |
|
||||
color: '#00A0E9', |
|
||||
}} |
|
||||
/> |
|
||||
</div> |
|
||||
<div>2016-12-12 12:32</div> |
|
||||
</div> |
|
||||
); |
|
||||
const desc2 = ( |
|
||||
<div |
|
||||
style={{ |
|
||||
fontSize: 12, |
|
||||
}} |
|
||||
className={styles.title} |
|
||||
> |
|
||||
<div |
|
||||
style={{ |
|
||||
margin: '8px 0 4px', |
|
||||
}} |
|
||||
> |
|
||||
<span>周毛毛</span> |
|
||||
<a href=""> |
|
||||
<DingdingOutlined |
|
||||
style={{ |
|
||||
color: '#00A0E9', |
|
||||
marginLeft: 8, |
|
||||
}} |
|
||||
/> |
|
||||
<span>催一下</span> |
|
||||
</a> |
|
||||
</div> |
|
||||
</div> |
|
||||
); |
|
||||
const content = ( |
|
||||
<> |
|
||||
<Descriptions title="项目名称"> |
|
||||
<Descriptions.Item label="项目 ID">23421</Descriptions.Item> |
|
||||
<Descriptions.Item label="负责人">曲丽丽</Descriptions.Item> |
|
||||
<Descriptions.Item label="生效时间"> |
|
||||
2016-12-12 ~ 2017-12-12 |
|
||||
</Descriptions.Item> |
|
||||
</Descriptions> |
|
||||
<br /> |
|
||||
<Steps progressDot current={1}> |
|
||||
<Step |
|
||||
title={ |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
创建项目 |
|
||||
</span> |
|
||||
} |
|
||||
description={desc1} |
|
||||
/> |
|
||||
<Step |
|
||||
title={ |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
部门初审 |
|
||||
</span> |
|
||||
} |
|
||||
description={desc2} |
|
||||
/> |
|
||||
<Step |
|
||||
title={ |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
财务复核 |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
<Step |
|
||||
title={ |
|
||||
<span |
|
||||
style={{ |
|
||||
fontSize: 14, |
|
||||
}} |
|
||||
> |
|
||||
完成 |
|
||||
</span> |
|
||||
} |
|
||||
/> |
|
||||
</Steps> |
|
||||
</> |
|
||||
); |
|
||||
const extra = ( |
|
||||
<> |
|
||||
<Button type="primary">返回列表</Button> |
|
||||
<Button>查看项目</Button> |
|
||||
<Button>打印</Button> |
|
||||
</> |
|
||||
); |
|
||||
return ( |
|
||||
<GridContent> |
|
||||
<Card variant="borderless"> |
|
||||
<Result |
|
||||
status="success" |
|
||||
title="提交成功" |
|
||||
subTitle="提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。" |
|
||||
extra={extra} |
|
||||
style={{ |
|
||||
marginBottom: 16, |
|
||||
}} |
|
||||
> |
|
||||
{content} |
|
||||
</Result> |
|
||||
</Card> |
|
||||
</GridContent> |
|
||||
); |
|
||||
}; |
|
||||
@ -1,309 +0,0 @@ |
|||||
import { history, Link, useRequest } from '@umijs/max'; |
|
||||
import { |
|
||||
Button, |
|
||||
Col, |
|
||||
Form, |
|
||||
Input, |
|
||||
message, |
|
||||
Popover, |
|
||||
Progress, |
|
||||
Row, |
|
||||
Select, |
|
||||
Space, |
|
||||
} from 'antd'; |
|
||||
import type { Store } from 'antd/es/form/interface'; |
|
||||
import type { FC } from 'react'; |
|
||||
import { useEffect, useState } from 'react'; |
|
||||
import type { StateType } from './service'; |
|
||||
import { fakeRegister } from './service'; |
|
||||
import useStyles from './styles'; |
|
||||
|
|
||||
const FormItem = Form.Item; |
|
||||
const { Option } = Select; |
|
||||
|
|
||||
const passwordProgressMap: { |
|
||||
ok: 'success'; |
|
||||
pass: 'normal'; |
|
||||
poor: 'exception'; |
|
||||
} = { |
|
||||
ok: 'success', |
|
||||
pass: 'normal', |
|
||||
poor: 'exception', |
|
||||
}; |
|
||||
const Register: FC = () => { |
|
||||
const { styles } = useStyles(); |
|
||||
const [count, setCount]: [number, any] = useState(0); |
|
||||
const [open, setVisible]: [boolean, any] = useState(false); |
|
||||
const [prefix, setPrefix]: [string, any] = useState('86'); |
|
||||
const [popover, setPopover]: [boolean, any] = useState(false); |
|
||||
const confirmDirty = false; |
|
||||
let interval: number | undefined; |
|
||||
|
|
||||
const passwordStatusMap = { |
|
||||
ok: ( |
|
||||
<div className={styles.success}> |
|
||||
<span>强度:强</span> |
|
||||
</div> |
|
||||
), |
|
||||
pass: ( |
|
||||
<div className={styles.warning}> |
|
||||
<span>强度:中</span> |
|
||||
</div> |
|
||||
), |
|
||||
poor: ( |
|
||||
<div className={styles.error}> |
|
||||
<span>强度:太短</span> |
|
||||
</div> |
|
||||
), |
|
||||
}; |
|
||||
|
|
||||
const [form] = Form.useForm(); |
|
||||
useEffect( |
|
||||
() => () => { |
|
||||
clearInterval(interval); |
|
||||
}, |
|
||||
[interval], |
|
||||
); |
|
||||
const onGetCaptcha = () => { |
|
||||
let counts = 59; |
|
||||
setCount(counts); |
|
||||
interval = window.setInterval(() => { |
|
||||
counts -= 1; |
|
||||
setCount(counts); |
|
||||
if (counts === 0) { |
|
||||
clearInterval(interval); |
|
||||
} |
|
||||
}, 1000); |
|
||||
}; |
|
||||
const getPasswordStatus = () => { |
|
||||
const value = form.getFieldValue('password'); |
|
||||
if (value && value.length > 9) { |
|
||||
return 'ok'; |
|
||||
} |
|
||||
if (value && value.length > 5) { |
|
||||
return 'pass'; |
|
||||
} |
|
||||
return 'poor'; |
|
||||
}; |
|
||||
const { loading: submitting, run: register } = useRequest<{ |
|
||||
data: StateType; |
|
||||
}>(fakeRegister, { |
|
||||
manual: true, |
|
||||
onSuccess: (data, params) => { |
|
||||
if (data.status === 'ok') { |
|
||||
message.success('注册成功!'); |
|
||||
history.push({ |
|
||||
pathname: `/user/register-result?account=${params[0].email}`, |
|
||||
}); |
|
||||
} |
|
||||
}, |
|
||||
}); |
|
||||
const onFinish = (values: Store) => { |
|
||||
register(values); |
|
||||
}; |
|
||||
const checkConfirm = (_: any, value: string) => { |
|
||||
const promise = Promise; |
|
||||
if (value && value !== form.getFieldValue('password')) { |
|
||||
return promise.reject('两次输入的密码不匹配!'); |
|
||||
} |
|
||||
return promise.resolve(); |
|
||||
}; |
|
||||
const checkPassword = (_: any, value: string) => { |
|
||||
const promise = Promise; |
|
||||
// 没有值的情况
|
|
||||
if (!value) { |
|
||||
setVisible(!!value); |
|
||||
return promise.reject('请输入密码!'); |
|
||||
} |
|
||||
// 有值的情况
|
|
||||
if (!open) { |
|
||||
setVisible(!!value); |
|
||||
} |
|
||||
setPopover(!popover); |
|
||||
if (value.length < 6) { |
|
||||
return promise.reject(''); |
|
||||
} |
|
||||
if (value && confirmDirty) { |
|
||||
form.validateFields(['confirm']); |
|
||||
} |
|
||||
return promise.resolve(); |
|
||||
}; |
|
||||
const changePrefix = (value: string) => { |
|
||||
setPrefix(value); |
|
||||
}; |
|
||||
const renderPasswordProgress = () => { |
|
||||
const value = form.getFieldValue('password'); |
|
||||
const passwordStatus = getPasswordStatus(); |
|
||||
return value?.length ? ( |
|
||||
<div |
|
||||
className={styles[`progress-${passwordStatus}` as keyof typeof styles]} |
|
||||
> |
|
||||
<Progress |
|
||||
status={passwordProgressMap[passwordStatus]} |
|
||||
size={6} |
|
||||
percent={value.length * 10 > 100 ? 100 : value.length * 10} |
|
||||
showInfo={false} |
|
||||
/> |
|
||||
</div> |
|
||||
) : null; |
|
||||
}; |
|
||||
return ( |
|
||||
<div className={styles.main}> |
|
||||
<h3>注册</h3> |
|
||||
<Form form={form} name="UserRegister" onFinish={onFinish}> |
|
||||
<FormItem |
|
||||
name="email" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入邮箱地址!', |
|
||||
}, |
|
||||
{ |
|
||||
type: 'email', |
|
||||
message: '邮箱地址格式错误!', |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<Input size="large" placeholder="邮箱" /> |
|
||||
</FormItem> |
|
||||
<Popover |
|
||||
getPopupContainer={(node) => { |
|
||||
if (node?.parentNode) { |
|
||||
return node.parentNode as HTMLElement; |
|
||||
} |
|
||||
return node; |
|
||||
}} |
|
||||
content={ |
|
||||
open && ( |
|
||||
<div |
|
||||
style={{ |
|
||||
padding: '4px 0', |
|
||||
}} |
|
||||
> |
|
||||
{passwordStatusMap[getPasswordStatus()]} |
|
||||
{renderPasswordProgress()} |
|
||||
<div |
|
||||
style={{ |
|
||||
marginTop: 10, |
|
||||
}} |
|
||||
> |
|
||||
<span>请至少输入 6 个字符。请不要使用容易被猜到的密码。</span> |
|
||||
</div> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
overlayStyle={{ |
|
||||
width: 240, |
|
||||
}} |
|
||||
placement="right" |
|
||||
open={open} |
|
||||
> |
|
||||
<FormItem |
|
||||
name="password" |
|
||||
className={ |
|
||||
form.getFieldValue('password') && |
|
||||
form.getFieldValue('password').length > 0 && |
|
||||
styles.password |
|
||||
} |
|
||||
rules={[ |
|
||||
{ |
|
||||
validator: checkPassword, |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<Input |
|
||||
size="large" |
|
||||
type="password" |
|
||||
placeholder="至少6位密码,区分大小写" |
|
||||
/> |
|
||||
</FormItem> |
|
||||
</Popover> |
|
||||
<FormItem |
|
||||
name="confirm" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '确认密码', |
|
||||
}, |
|
||||
{ |
|
||||
validator: checkConfirm, |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<Input size="large" type="password" placeholder="确认密码" /> |
|
||||
</FormItem> |
|
||||
<FormItem |
|
||||
name="mobile" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入手机号!', |
|
||||
}, |
|
||||
{ |
|
||||
pattern: /^\d{11}$/, |
|
||||
message: '手机号格式错误!', |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<Space.Compact style={{ width: '100%' }}> |
|
||||
<Select |
|
||||
size="large" |
|
||||
value={prefix} |
|
||||
onChange={changePrefix} |
|
||||
style={{ |
|
||||
width: '30%', |
|
||||
}} |
|
||||
> |
|
||||
<Option value="86">+86</Option> |
|
||||
<Option value="87">+87</Option> |
|
||||
</Select> |
|
||||
|
|
||||
<Input size="large" placeholder="手机号" /> |
|
||||
</Space.Compact> |
|
||||
</FormItem> |
|
||||
<Row gutter={8}> |
|
||||
<Col span={16}> |
|
||||
<FormItem |
|
||||
name="captcha" |
|
||||
rules={[ |
|
||||
{ |
|
||||
required: true, |
|
||||
message: '请输入验证码!', |
|
||||
}, |
|
||||
]} |
|
||||
> |
|
||||
<Input size="large" placeholder="验证码" /> |
|
||||
</FormItem> |
|
||||
</Col> |
|
||||
<Col span={8}> |
|
||||
<Button |
|
||||
size="large" |
|
||||
disabled={!!count} |
|
||||
className={styles.getCaptcha} |
|
||||
onClick={onGetCaptcha} |
|
||||
> |
|
||||
{count ? `${count} s` : '获取验证码'} |
|
||||
</Button> |
|
||||
</Col> |
|
||||
</Row> |
|
||||
<FormItem> |
|
||||
<div className={styles.footer}> |
|
||||
<Button |
|
||||
size="large" |
|
||||
loading={submitting} |
|
||||
className={styles.submit} |
|
||||
type="primary" |
|
||||
htmlType="submit" |
|
||||
> |
|
||||
<span>注册</span> |
|
||||
</Button> |
|
||||
<Link to="/user/login"> |
|
||||
<span>使用已有账户登录</span> |
|
||||
</Link> |
|
||||
</div> |
|
||||
</FormItem> |
|
||||
</Form> |
|
||||
</div> |
|
||||
); |
|
||||
}; |
|
||||
export default Register; |
|
||||
Loading…
Reference in new issue