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